@hdriel/aws-utils 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -89,7 +89,8 @@ __export(index_exports, {
89
89
  LambdaUtil: () => LambdaUtil,
90
90
  S3LocalstackUtil: () => S3LocalstackUtil,
91
91
  S3Util: () => S3Util,
92
- SNSUtil: () => SNSUtil
92
+ SNSUtil: () => SNSUtil,
93
+ SUPPORTED_IFRAME_EXTENSIONS: () => SUPPORTED_IFRAME_EXTENSIONS
93
94
  });
94
95
  module.exports = __toCommonJS(index_exports);
95
96
 
@@ -289,6 +290,91 @@ var ACLs = /* @__PURE__ */ ((ACLs2) => {
289
290
  ACLs2["publicReadWrite"] = "public-read-write";
290
291
  return ACLs2;
291
292
  })(ACLs || {});
293
+ var SUPPORTED_IFRAME_EXTENSIONS = [
294
+ // Images
295
+ "jpg",
296
+ "jpeg",
297
+ "png",
298
+ "gif",
299
+ "bmp",
300
+ "webp",
301
+ "svg",
302
+ "ico",
303
+ "tif",
304
+ "tiff",
305
+ "heic",
306
+ "heif",
307
+ "raw",
308
+ "cr2",
309
+ "nef",
310
+ "arw",
311
+ // Videos
312
+ "mp4",
313
+ "avi",
314
+ "mov",
315
+ "wmv",
316
+ "flv",
317
+ "mkv",
318
+ "webm",
319
+ "mpeg",
320
+ "mpg",
321
+ "m4v",
322
+ "3gp",
323
+ "ogv",
324
+ "ts",
325
+ "mts",
326
+ "m2ts",
327
+ // Documents
328
+ "pdf",
329
+ // Text
330
+ "txt",
331
+ "csv",
332
+ "json",
333
+ "xml",
334
+ "md",
335
+ "log",
336
+ "yaml",
337
+ "yml",
338
+ "ini",
339
+ "conf",
340
+ "cfg",
341
+ // Code
342
+ "js",
343
+ "ts",
344
+ "jsx",
345
+ "tsx",
346
+ "py",
347
+ "java",
348
+ "c",
349
+ "cpp",
350
+ "h",
351
+ "cs",
352
+ "php",
353
+ "rb",
354
+ "go",
355
+ "rs",
356
+ "swift",
357
+ "kt",
358
+ "scala",
359
+ // Audio
360
+ "mp3",
361
+ "wav",
362
+ "ogg",
363
+ "flac",
364
+ "aac",
365
+ "m4a",
366
+ "wma",
367
+ "aiff",
368
+ "ape",
369
+ "opus",
370
+ // Web
371
+ "html",
372
+ "htm",
373
+ "css",
374
+ "scss",
375
+ "sass",
376
+ "less"
377
+ ];
292
378
 
293
379
  // src/utils/helpers.ts
294
380
  var import_bytes = __toESM(require("bytes"), 1);
@@ -335,6 +421,15 @@ var getUnitBytes = (bytes2, unit) => {
335
421
  return bytes2;
336
422
  }
337
423
  };
424
+ function hasNonAscii(str) {
425
+ return /[^\x00-\x7F]/.test(str);
426
+ }
427
+ function encodeS3Metadata(value) {
428
+ if (hasNonAscii(value)) {
429
+ return Buffer.from(value, "utf8").toString("base64");
430
+ }
431
+ return value;
432
+ }
338
433
 
339
434
  // src/aws/s3/s3-file.ts
340
435
  var import_buffer = require("buffer");
@@ -1238,7 +1333,7 @@ var S3Stream = class _S3Stream extends S3File {
1238
1333
  queryField = "file",
1239
1334
  paramsField = "file",
1240
1335
  headerField = "x-fileKey",
1241
- cachingAgeSeconds = "1y"
1336
+ cachingAge: _cachingAge = "1y"
1242
1337
  } = {}) => {
1243
1338
  return (req, res, next) => __async(this, null, function* () {
1244
1339
  var _a2, _b, _c, _d, _e, _f, _g, _h;
@@ -1263,8 +1358,10 @@ var S3Stream = class _S3Stream extends S3File {
1263
1358
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1264
1359
  res.setHeader("Content-Type", contentType);
1265
1360
  res.setHeader("Content-Length", imageBuffer.length);
1266
- const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1267
- if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1361
+ const cachingAge = !_cachingAge || typeof _cachingAge === "number" ? _cachingAge : getTotalSeconds(_cachingAge);
1362
+ if (cachingAge) {
1363
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1364
+ }
1268
1365
  res.status(200).send(imageBuffer);
1269
1366
  } catch (error) {
1270
1367
  (_h = this.logger) == null ? void 0 : _h.warn(req.id, "image fileKey not found", __spreadValues({
@@ -1274,19 +1371,20 @@ var S3Stream = class _S3Stream extends S3File {
1274
1371
  }
1275
1372
  });
1276
1373
  });
1277
- __publicField(this, "streamPdfFileCtrl", ({
1374
+ __publicField(this, "streamBufferFileCtrl", ({
1278
1375
  fileKey: _fileKey,
1376
+ filename: _filename,
1279
1377
  queryField = "file",
1280
1378
  paramsField = "file",
1281
1379
  headerField = "x-fileKey",
1282
- cachingAgeSeconds = "1y"
1380
+ cachingAge: _cachingAge = "1h"
1283
1381
  } = {}) => {
1284
1382
  return (req, res, next) => __async(this, null, function* () {
1285
1383
  var _a2, _b, _c, _d, _e, _f, _g, _h;
1286
1384
  let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramsField]) ? decodeURIComponent((_b = req.params) == null ? void 0 : _b[paramsField]) : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? decodeURIComponent((_d = req.query) == null ? void 0 : _d[queryField]) : void 0) || (((_e = req.headers) == null ? void 0 : _e[headerField]) ? decodeURIComponent((_f = req.headers) == null ? void 0 : _f[headerField]) : void 0);
1287
1385
  if (!fileKey) {
1288
- (_g = this.logger) == null ? void 0 : _g.warn(req.id, "pdf fileKey is required");
1289
- next(Error("pdf fileKey is required"));
1386
+ (_g = this.logger) == null ? void 0 : _g.warn(req.id, "iframe fileKey is required");
1387
+ next(Error("iframe fileKey is required"));
1290
1388
  return;
1291
1389
  }
1292
1390
  try {
@@ -1303,12 +1401,14 @@ var S3Stream = class _S3Stream extends S3File {
1303
1401
  pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1304
1402
  };
1305
1403
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1306
- const filename = (0, import_pathe2.basename)(fileKey);
1404
+ const filename = _filename || (0, import_pathe2.basename)(fileKey);
1307
1405
  res.setHeader("Content-Type", contentType);
1308
1406
  res.setHeader("Content-Disposition", `inline; filename="${encodeURIComponent(filename)}"`);
1309
- res.setHeader("Content-Length", fileBuffer.length);
1310
- const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1311
- res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1407
+ res.setHeader("Content-Length", String(fileBuffer.length));
1408
+ const cachingAge = !_cachingAge || typeof _cachingAge === "number" ? _cachingAge : getTotalSeconds(_cachingAge);
1409
+ if (cachingAge) {
1410
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1411
+ }
1312
1412
  res.status(200).send(fileBuffer);
1313
1413
  } catch (error) {
1314
1414
  (_h = this.logger) == null ? void 0 : _h.warn(req.id, "pdf fileKey not found", __spreadValues({
@@ -1512,11 +1612,13 @@ var S3Stream = class _S3Stream extends S3File {
1512
1612
  forDownloading = false,
1513
1613
  paramsField = "file",
1514
1614
  queryField = "file",
1515
- headerField = "x-fileKey"
1615
+ headerField = "x-fileKey",
1616
+ streamMethod,
1617
+ cachingAge: _cachingAge = "1h"
1516
1618
  } = {}) {
1517
1619
  return (req, res, next) => __async(this, null, function* () {
1518
1620
  var _a2, _b, _c, _d, _e, _f, _g, _h, _i;
1519
- let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramsField]) ? (_b = req.params) == null ? void 0 : _b[paramsField] : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? (_d = req.query) == null ? void 0 : _d[queryField] : void 0) || (((_e = req.headers) == null ? void 0 : _e[headerField]) ? decodeURIComponent((_f = req.headers) == null ? void 0 : _f[headerField]) : void 0);
1621
+ const fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramsField]) ? (_b = req.params) == null ? void 0 : _b[paramsField] : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? (_d = req.query) == null ? void 0 : _d[queryField] : void 0) || (((_e = req.headers) == null ? void 0 : _e[headerField]) ? decodeURIComponent((_f = req.headers) == null ? void 0 : _f[headerField]) : void 0);
1520
1622
  if (!fileKey || fileKey === "/") {
1521
1623
  (_g = this.logger) == null ? void 0 : _g.warn(req.id, "fileKey stream is required");
1522
1624
  next(Error("fileKey stream is required"));
@@ -1550,13 +1652,23 @@ var S3Stream = class _S3Stream extends S3File {
1550
1652
  }
1551
1653
  const fileInfo = yield this.fileInfo(normalizedKey);
1552
1654
  const fileName = filename || normalizedKey.split("/").pop() || "download";
1553
- res.setHeader("Content-Type", fileInfo.ContentType || "application/octet-stream");
1554
- if (forDownloading) {
1555
- res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(fileName)}"`);
1556
- }
1655
+ const contentType = fileInfo.ContentType || "application/octet-stream";
1656
+ const ext = (0, import_pathe2.extname)(fileKey).slice(1).toLowerCase();
1657
+ const inlineTypes = ["text/", "image/", "application/pdf", "video/", "audio/"];
1658
+ const canDisplayInline = SUPPORTED_IFRAME_EXTENSIONS.includes(ext) || inlineTypes.some((type) => contentType.startsWith(type));
1659
+ res.setHeader("Content-Type", contentType);
1557
1660
  if (fileInfo.ContentLength) {
1558
1661
  res.setHeader("Content-Length", String(fileInfo.ContentLength));
1559
1662
  }
1663
+ if (forDownloading || !canDisplayInline) {
1664
+ res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(fileName)}"`);
1665
+ } else {
1666
+ res.setHeader("Content-Disposition", `inline; filename="${encodeURIComponent(fileName)}"`);
1667
+ }
1668
+ const cachingAge = !_cachingAge || typeof _cachingAge === "number" ? _cachingAge : getTotalSeconds(_cachingAge);
1669
+ if (cachingAge) {
1670
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1671
+ }
1560
1672
  stream.on("error", (err) => {
1561
1673
  var _a3, _b2;
1562
1674
  (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { fileKey: normalizedKey, error: err });
@@ -1568,7 +1680,12 @@ var S3Stream = class _S3Stream extends S3File {
1568
1680
  (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1569
1681
  req.off("close", onClose);
1570
1682
  });
1571
- yield pump(stream, res);
1683
+ streamMethod || (streamMethod = canDisplayInline ? "pipe" : "pipeline");
1684
+ if (streamMethod === "pipeline") {
1685
+ yield pump(stream, res);
1686
+ } else {
1687
+ stream.pipe(res);
1688
+ }
1572
1689
  req.off("close", onClose);
1573
1690
  } catch (error) {
1574
1691
  abort.abort();
@@ -1775,10 +1892,22 @@ var S3Stream = class _S3Stream extends S3File {
1775
1892
  bucket: this.bucket,
1776
1893
  contentType: import_multer_s3.default.AUTO_CONTENT_TYPE,
1777
1894
  metadata: (req, file, cb) => __async(this, null, function* () {
1778
- const baseMetadata = __spreadProps(__spreadValues({}, file), { directory: normalizedPath });
1895
+ const originalName = decodeURIComponent(file.originalname);
1896
+ const baseMetadata = __spreadProps(__spreadValues({}, file), {
1897
+ directory: normalizedPath,
1898
+ // Encode non-ASCII characters for S3 metadata
1899
+ originalname: encodeS3Metadata(originalName),
1900
+ // Optional: Add a flag to know it's encoded
1901
+ // @ts-ignore
1902
+ "originalname-encoded": hasNonAscii(originalName) ? "base64" : "plain"
1903
+ });
1779
1904
  if (customMetadata) {
1780
1905
  const additionalMetadata = typeof customMetadata === "function" ? yield customMetadata(req, file) : customMetadata;
1781
- Object.assign(baseMetadata, additionalMetadata);
1906
+ const sanitizedMetadata = {};
1907
+ for (const [key, value] of Object.entries(additionalMetadata)) {
1908
+ sanitizedMetadata[key] = typeof value === "string" ? encodeS3Metadata(value) : String(value);
1909
+ }
1910
+ Object.assign(baseMetadata, sanitizedMetadata);
1782
1911
  }
1783
1912
  cb(null, baseMetadata);
1784
1913
  }),
@@ -1896,6 +2025,14 @@ var S3Stream = class _S3Stream extends S3File {
1896
2025
  * Middleware for uploading multiple files with different field names
1897
2026
  * Adds the uploaded files info to req.s3FilesByField
1898
2027
  */
2028
+ /*
2029
+ example
2030
+ uploadFieldsFiles([
2031
+ { name: 'cardPosterSrc', maxCount: 1 },
2032
+ { name: 'sectionPosterSrc', maxCount: 1 },
2033
+ { name: 'imageSrc', maxCount: 1 },
2034
+ ]) as any,
2035
+ */
1899
2036
  // uploadFieldsFiles(
1900
2037
  // fields: Array<{ name: string; directory: string; maxCount?: number; options?: S3UploadOptions }>
1901
2038
  // ): RequestHandler {
@@ -2083,5 +2220,6 @@ var SNSUtil = class {
2083
2220
  LambdaUtil,
2084
2221
  S3LocalstackUtil,
2085
2222
  S3Util,
2086
- SNSUtil
2223
+ SNSUtil,
2224
+ SUPPORTED_IFRAME_EXTENSIONS
2087
2225
  });
package/dist/index.d.cts CHANGED
@@ -76,6 +76,7 @@ declare enum ACLs {
76
76
  publicRead = "public-read",
77
77
  publicReadWrite = "public-read-write"
78
78
  }
79
+ declare const SUPPORTED_IFRAME_EXTENSIONS: string[];
79
80
 
80
81
  interface ContentFile {
81
82
  Key: string;
@@ -311,27 +312,30 @@ declare class S3Stream extends S3File {
311
312
  headerField?: string;
312
313
  streamTimeoutMS?: number | undefined;
313
314
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<any>>;
314
- streamImageFileCtrl: ({ fileKey: _fileKey, queryField, paramsField, headerField, cachingAgeSeconds, }?: {
315
+ streamImageFileCtrl: ({ fileKey: _fileKey, queryField, paramsField, headerField, cachingAge: _cachingAge, }?: {
315
316
  fileKey?: string;
316
317
  queryField?: string;
317
318
  paramsField?: string;
318
319
  headerField?: string;
319
- cachingAgeSeconds?: null | number | StringValue;
320
+ cachingAge?: null | number | StringValue;
320
321
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
321
- streamPdfFileCtrl: ({ fileKey: _fileKey, queryField, paramsField, headerField, cachingAgeSeconds, }?: {
322
+ streamBufferFileCtrl: ({ fileKey: _fileKey, filename: _filename, queryField, paramsField, headerField, cachingAge: _cachingAge, }?: {
322
323
  fileKey?: string;
324
+ filename?: string;
323
325
  queryField?: string;
324
326
  paramsField?: string;
325
327
  headerField?: string;
326
- cachingAgeSeconds?: null | number | StringValue;
328
+ cachingAge?: null | number | StringValue;
327
329
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
328
- streamFileCtrl({ fileKey: _fileKey, filename, forDownloading, paramsField, queryField, headerField, }?: {
330
+ streamFileCtrl({ fileKey: _fileKey, filename, forDownloading, paramsField, queryField, headerField, streamMethod, cachingAge: _cachingAge, }?: {
329
331
  fileKey?: string;
330
332
  filename?: string;
331
333
  forDownloading?: boolean;
332
334
  paramsField?: string;
333
335
  queryField?: string;
334
336
  headerField?: string;
337
+ cachingAge?: null | number | StringValue;
338
+ streamMethod?: 'pipe' | 'pipeline';
335
339
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>>;
336
340
  streamZipFileCtr({ fileKey: _fileKey, filename: _filename, queryField, paramsField, headerField, compressionLevel, }?: {
337
341
  filename?: string;
@@ -422,4 +426,4 @@ declare class AWSConfigSharingUtil {
422
426
  };
423
427
  }
424
428
 
425
- export { ACLs, AWSConfigSharingUtil, type BucketInfo, type FILE_EXT, type FILE_TYPE, IAMUtil, LambdaUtil, S3LocalstackUtil, S3Util, type S3UtilProps, SNSUtil, type TreeDirectoryItem, type TreeFileItem, type UploadedS3File };
429
+ export { ACLs, AWSConfigSharingUtil, type BucketInfo, type FILE_EXT, type FILE_TYPE, IAMUtil, LambdaUtil, S3LocalstackUtil, S3Util, type S3UtilProps, SNSUtil, SUPPORTED_IFRAME_EXTENSIONS, type TreeDirectoryItem, type TreeFileItem, type UploadedS3File };
package/dist/index.d.ts CHANGED
@@ -76,6 +76,7 @@ declare enum ACLs {
76
76
  publicRead = "public-read",
77
77
  publicReadWrite = "public-read-write"
78
78
  }
79
+ declare const SUPPORTED_IFRAME_EXTENSIONS: string[];
79
80
 
80
81
  interface ContentFile {
81
82
  Key: string;
@@ -311,27 +312,30 @@ declare class S3Stream extends S3File {
311
312
  headerField?: string;
312
313
  streamTimeoutMS?: number | undefined;
313
314
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<any>>;
314
- streamImageFileCtrl: ({ fileKey: _fileKey, queryField, paramsField, headerField, cachingAgeSeconds, }?: {
315
+ streamImageFileCtrl: ({ fileKey: _fileKey, queryField, paramsField, headerField, cachingAge: _cachingAge, }?: {
315
316
  fileKey?: string;
316
317
  queryField?: string;
317
318
  paramsField?: string;
318
319
  headerField?: string;
319
- cachingAgeSeconds?: null | number | StringValue;
320
+ cachingAge?: null | number | StringValue;
320
321
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
321
- streamPdfFileCtrl: ({ fileKey: _fileKey, queryField, paramsField, headerField, cachingAgeSeconds, }?: {
322
+ streamBufferFileCtrl: ({ fileKey: _fileKey, filename: _filename, queryField, paramsField, headerField, cachingAge: _cachingAge, }?: {
322
323
  fileKey?: string;
324
+ filename?: string;
323
325
  queryField?: string;
324
326
  paramsField?: string;
325
327
  headerField?: string;
326
- cachingAgeSeconds?: null | number | StringValue;
328
+ cachingAge?: null | number | StringValue;
327
329
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
328
- streamFileCtrl({ fileKey: _fileKey, filename, forDownloading, paramsField, queryField, headerField, }?: {
330
+ streamFileCtrl({ fileKey: _fileKey, filename, forDownloading, paramsField, queryField, headerField, streamMethod, cachingAge: _cachingAge, }?: {
329
331
  fileKey?: string;
330
332
  filename?: string;
331
333
  forDownloading?: boolean;
332
334
  paramsField?: string;
333
335
  queryField?: string;
334
336
  headerField?: string;
337
+ cachingAge?: null | number | StringValue;
338
+ streamMethod?: 'pipe' | 'pipeline';
335
339
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>>;
336
340
  streamZipFileCtr({ fileKey: _fileKey, filename: _filename, queryField, paramsField, headerField, compressionLevel, }?: {
337
341
  filename?: string;
@@ -422,4 +426,4 @@ declare class AWSConfigSharingUtil {
422
426
  };
423
427
  }
424
428
 
425
- export { ACLs, AWSConfigSharingUtil, type BucketInfo, type FILE_EXT, type FILE_TYPE, IAMUtil, LambdaUtil, S3LocalstackUtil, S3Util, type S3UtilProps, SNSUtil, type TreeDirectoryItem, type TreeFileItem, type UploadedS3File };
429
+ export { ACLs, AWSConfigSharingUtil, type BucketInfo, type FILE_EXT, type FILE_TYPE, IAMUtil, LambdaUtil, S3LocalstackUtil, S3Util, type S3UtilProps, SNSUtil, SUPPORTED_IFRAME_EXTENSIONS, type TreeDirectoryItem, type TreeFileItem, type UploadedS3File };
package/dist/index.js CHANGED
@@ -250,6 +250,91 @@ var ACLs = /* @__PURE__ */ ((ACLs2) => {
250
250
  ACLs2["publicReadWrite"] = "public-read-write";
251
251
  return ACLs2;
252
252
  })(ACLs || {});
253
+ var SUPPORTED_IFRAME_EXTENSIONS = [
254
+ // Images
255
+ "jpg",
256
+ "jpeg",
257
+ "png",
258
+ "gif",
259
+ "bmp",
260
+ "webp",
261
+ "svg",
262
+ "ico",
263
+ "tif",
264
+ "tiff",
265
+ "heic",
266
+ "heif",
267
+ "raw",
268
+ "cr2",
269
+ "nef",
270
+ "arw",
271
+ // Videos
272
+ "mp4",
273
+ "avi",
274
+ "mov",
275
+ "wmv",
276
+ "flv",
277
+ "mkv",
278
+ "webm",
279
+ "mpeg",
280
+ "mpg",
281
+ "m4v",
282
+ "3gp",
283
+ "ogv",
284
+ "ts",
285
+ "mts",
286
+ "m2ts",
287
+ // Documents
288
+ "pdf",
289
+ // Text
290
+ "txt",
291
+ "csv",
292
+ "json",
293
+ "xml",
294
+ "md",
295
+ "log",
296
+ "yaml",
297
+ "yml",
298
+ "ini",
299
+ "conf",
300
+ "cfg",
301
+ // Code
302
+ "js",
303
+ "ts",
304
+ "jsx",
305
+ "tsx",
306
+ "py",
307
+ "java",
308
+ "c",
309
+ "cpp",
310
+ "h",
311
+ "cs",
312
+ "php",
313
+ "rb",
314
+ "go",
315
+ "rs",
316
+ "swift",
317
+ "kt",
318
+ "scala",
319
+ // Audio
320
+ "mp3",
321
+ "wav",
322
+ "ogg",
323
+ "flac",
324
+ "aac",
325
+ "m4a",
326
+ "wma",
327
+ "aiff",
328
+ "ape",
329
+ "opus",
330
+ // Web
331
+ "html",
332
+ "htm",
333
+ "css",
334
+ "scss",
335
+ "sass",
336
+ "less"
337
+ ];
253
338
 
254
339
  // src/utils/helpers.ts
255
340
  import bytes from "bytes";
@@ -296,6 +381,15 @@ var getUnitBytes = (bytes2, unit) => {
296
381
  return bytes2;
297
382
  }
298
383
  };
384
+ function hasNonAscii(str) {
385
+ return /[^\x00-\x7F]/.test(str);
386
+ }
387
+ function encodeS3Metadata(value) {
388
+ if (hasNonAscii(value)) {
389
+ return Buffer.from(value, "utf8").toString("base64");
390
+ }
391
+ return value;
392
+ }
299
393
 
300
394
  // src/aws/s3/s3-file.ts
301
395
  import { Buffer as Buffer2 } from "buffer";
@@ -1227,7 +1321,7 @@ var S3Stream = class _S3Stream extends S3File {
1227
1321
  queryField = "file",
1228
1322
  paramsField = "file",
1229
1323
  headerField = "x-fileKey",
1230
- cachingAgeSeconds = "1y"
1324
+ cachingAge: _cachingAge = "1y"
1231
1325
  } = {}) => {
1232
1326
  return (req, res, next) => __async(this, null, function* () {
1233
1327
  var _a2, _b, _c, _d, _e, _f, _g, _h;
@@ -1252,8 +1346,10 @@ var S3Stream = class _S3Stream extends S3File {
1252
1346
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1253
1347
  res.setHeader("Content-Type", contentType);
1254
1348
  res.setHeader("Content-Length", imageBuffer.length);
1255
- const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1256
- if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1349
+ const cachingAge = !_cachingAge || typeof _cachingAge === "number" ? _cachingAge : getTotalSeconds(_cachingAge);
1350
+ if (cachingAge) {
1351
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1352
+ }
1257
1353
  res.status(200).send(imageBuffer);
1258
1354
  } catch (error) {
1259
1355
  (_h = this.logger) == null ? void 0 : _h.warn(req.id, "image fileKey not found", __spreadValues({
@@ -1263,19 +1359,20 @@ var S3Stream = class _S3Stream extends S3File {
1263
1359
  }
1264
1360
  });
1265
1361
  });
1266
- __publicField(this, "streamPdfFileCtrl", ({
1362
+ __publicField(this, "streamBufferFileCtrl", ({
1267
1363
  fileKey: _fileKey,
1364
+ filename: _filename,
1268
1365
  queryField = "file",
1269
1366
  paramsField = "file",
1270
1367
  headerField = "x-fileKey",
1271
- cachingAgeSeconds = "1y"
1368
+ cachingAge: _cachingAge = "1h"
1272
1369
  } = {}) => {
1273
1370
  return (req, res, next) => __async(this, null, function* () {
1274
1371
  var _a2, _b, _c, _d, _e, _f, _g, _h;
1275
1372
  let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramsField]) ? decodeURIComponent((_b = req.params) == null ? void 0 : _b[paramsField]) : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? decodeURIComponent((_d = req.query) == null ? void 0 : _d[queryField]) : void 0) || (((_e = req.headers) == null ? void 0 : _e[headerField]) ? decodeURIComponent((_f = req.headers) == null ? void 0 : _f[headerField]) : void 0);
1276
1373
  if (!fileKey) {
1277
- (_g = this.logger) == null ? void 0 : _g.warn(req.id, "pdf fileKey is required");
1278
- next(Error("pdf fileKey is required"));
1374
+ (_g = this.logger) == null ? void 0 : _g.warn(req.id, "iframe fileKey is required");
1375
+ next(Error("iframe fileKey is required"));
1279
1376
  return;
1280
1377
  }
1281
1378
  try {
@@ -1292,12 +1389,14 @@ var S3Stream = class _S3Stream extends S3File {
1292
1389
  pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1293
1390
  };
1294
1391
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1295
- const filename = basename2(fileKey);
1392
+ const filename = _filename || basename2(fileKey);
1296
1393
  res.setHeader("Content-Type", contentType);
1297
1394
  res.setHeader("Content-Disposition", `inline; filename="${encodeURIComponent(filename)}"`);
1298
- res.setHeader("Content-Length", fileBuffer.length);
1299
- const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1300
- res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1395
+ res.setHeader("Content-Length", String(fileBuffer.length));
1396
+ const cachingAge = !_cachingAge || typeof _cachingAge === "number" ? _cachingAge : getTotalSeconds(_cachingAge);
1397
+ if (cachingAge) {
1398
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1399
+ }
1301
1400
  res.status(200).send(fileBuffer);
1302
1401
  } catch (error) {
1303
1402
  (_h = this.logger) == null ? void 0 : _h.warn(req.id, "pdf fileKey not found", __spreadValues({
@@ -1501,11 +1600,13 @@ var S3Stream = class _S3Stream extends S3File {
1501
1600
  forDownloading = false,
1502
1601
  paramsField = "file",
1503
1602
  queryField = "file",
1504
- headerField = "x-fileKey"
1603
+ headerField = "x-fileKey",
1604
+ streamMethod,
1605
+ cachingAge: _cachingAge = "1h"
1505
1606
  } = {}) {
1506
1607
  return (req, res, next) => __async(this, null, function* () {
1507
1608
  var _a2, _b, _c, _d, _e, _f, _g, _h, _i;
1508
- let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramsField]) ? (_b = req.params) == null ? void 0 : _b[paramsField] : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? (_d = req.query) == null ? void 0 : _d[queryField] : void 0) || (((_e = req.headers) == null ? void 0 : _e[headerField]) ? decodeURIComponent((_f = req.headers) == null ? void 0 : _f[headerField]) : void 0);
1609
+ const fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramsField]) ? (_b = req.params) == null ? void 0 : _b[paramsField] : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? (_d = req.query) == null ? void 0 : _d[queryField] : void 0) || (((_e = req.headers) == null ? void 0 : _e[headerField]) ? decodeURIComponent((_f = req.headers) == null ? void 0 : _f[headerField]) : void 0);
1509
1610
  if (!fileKey || fileKey === "/") {
1510
1611
  (_g = this.logger) == null ? void 0 : _g.warn(req.id, "fileKey stream is required");
1511
1612
  next(Error("fileKey stream is required"));
@@ -1539,13 +1640,23 @@ var S3Stream = class _S3Stream extends S3File {
1539
1640
  }
1540
1641
  const fileInfo = yield this.fileInfo(normalizedKey);
1541
1642
  const fileName = filename || normalizedKey.split("/").pop() || "download";
1542
- res.setHeader("Content-Type", fileInfo.ContentType || "application/octet-stream");
1543
- if (forDownloading) {
1544
- res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(fileName)}"`);
1545
- }
1643
+ const contentType = fileInfo.ContentType || "application/octet-stream";
1644
+ const ext = extname(fileKey).slice(1).toLowerCase();
1645
+ const inlineTypes = ["text/", "image/", "application/pdf", "video/", "audio/"];
1646
+ const canDisplayInline = SUPPORTED_IFRAME_EXTENSIONS.includes(ext) || inlineTypes.some((type) => contentType.startsWith(type));
1647
+ res.setHeader("Content-Type", contentType);
1546
1648
  if (fileInfo.ContentLength) {
1547
1649
  res.setHeader("Content-Length", String(fileInfo.ContentLength));
1548
1650
  }
1651
+ if (forDownloading || !canDisplayInline) {
1652
+ res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(fileName)}"`);
1653
+ } else {
1654
+ res.setHeader("Content-Disposition", `inline; filename="${encodeURIComponent(fileName)}"`);
1655
+ }
1656
+ const cachingAge = !_cachingAge || typeof _cachingAge === "number" ? _cachingAge : getTotalSeconds(_cachingAge);
1657
+ if (cachingAge) {
1658
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1659
+ }
1549
1660
  stream.on("error", (err) => {
1550
1661
  var _a3, _b2;
1551
1662
  (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { fileKey: normalizedKey, error: err });
@@ -1557,7 +1668,12 @@ var S3Stream = class _S3Stream extends S3File {
1557
1668
  (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1558
1669
  req.off("close", onClose);
1559
1670
  });
1560
- yield pump(stream, res);
1671
+ streamMethod || (streamMethod = canDisplayInline ? "pipe" : "pipeline");
1672
+ if (streamMethod === "pipeline") {
1673
+ yield pump(stream, res);
1674
+ } else {
1675
+ stream.pipe(res);
1676
+ }
1561
1677
  req.off("close", onClose);
1562
1678
  } catch (error) {
1563
1679
  abort.abort();
@@ -1764,10 +1880,22 @@ var S3Stream = class _S3Stream extends S3File {
1764
1880
  bucket: this.bucket,
1765
1881
  contentType: multerS3.AUTO_CONTENT_TYPE,
1766
1882
  metadata: (req, file, cb) => __async(this, null, function* () {
1767
- const baseMetadata = __spreadProps(__spreadValues({}, file), { directory: normalizedPath });
1883
+ const originalName = decodeURIComponent(file.originalname);
1884
+ const baseMetadata = __spreadProps(__spreadValues({}, file), {
1885
+ directory: normalizedPath,
1886
+ // Encode non-ASCII characters for S3 metadata
1887
+ originalname: encodeS3Metadata(originalName),
1888
+ // Optional: Add a flag to know it's encoded
1889
+ // @ts-ignore
1890
+ "originalname-encoded": hasNonAscii(originalName) ? "base64" : "plain"
1891
+ });
1768
1892
  if (customMetadata) {
1769
1893
  const additionalMetadata = typeof customMetadata === "function" ? yield customMetadata(req, file) : customMetadata;
1770
- Object.assign(baseMetadata, additionalMetadata);
1894
+ const sanitizedMetadata = {};
1895
+ for (const [key, value] of Object.entries(additionalMetadata)) {
1896
+ sanitizedMetadata[key] = typeof value === "string" ? encodeS3Metadata(value) : String(value);
1897
+ }
1898
+ Object.assign(baseMetadata, sanitizedMetadata);
1771
1899
  }
1772
1900
  cb(null, baseMetadata);
1773
1901
  }),
@@ -1885,6 +2013,14 @@ var S3Stream = class _S3Stream extends S3File {
1885
2013
  * Middleware for uploading multiple files with different field names
1886
2014
  * Adds the uploaded files info to req.s3FilesByField
1887
2015
  */
2016
+ /*
2017
+ example
2018
+ uploadFieldsFiles([
2019
+ { name: 'cardPosterSrc', maxCount: 1 },
2020
+ { name: 'sectionPosterSrc', maxCount: 1 },
2021
+ { name: 'imageSrc', maxCount: 1 },
2022
+ ]) as any,
2023
+ */
1888
2024
  // uploadFieldsFiles(
1889
2025
  // fields: Array<{ name: string; directory: string; maxCount?: number; options?: S3UploadOptions }>
1890
2026
  // ): RequestHandler {
@@ -2071,5 +2207,6 @@ export {
2071
2207
  LambdaUtil,
2072
2208
  S3LocalstackUtil,
2073
2209
  S3Util,
2074
- SNSUtil
2210
+ SNSUtil,
2211
+ SUPPORTED_IFRAME_EXTENSIONS
2075
2212
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hdriel/aws-utils",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Simplified AWS SDK (v3) utilities for S3 (upload, download, streaming) with TypeScript support",
5
5
  "author": "Hadriel Benjo (https://github.com/hdriel)",
6
6
  "type": "module",