@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 +160 -22
- package/dist/index.d.cts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +158 -21
- package/package.json +1 -1
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
|
-
|
|
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 = !
|
|
1267
|
-
if (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, "
|
|
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
|
-
|
|
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, "
|
|
1289
|
-
next(Error("
|
|
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 = !
|
|
1311
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
320
|
+
cachingAge?: null | number | StringValue;
|
|
320
321
|
}) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
|
|
321
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
320
|
+
cachingAge?: null | number | StringValue;
|
|
320
321
|
}) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = !
|
|
1256
|
-
if (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, "
|
|
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
|
-
|
|
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, "
|
|
1278
|
-
next(Error("
|
|
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 = !
|
|
1300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|