@hdriel/aws-utils 1.0.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/LICENSE +21 -0
- package/dist/index.cjs +1648 -0
- package/dist/index.d.cts +320 -0
- package/dist/index.d.ts +320 -0
- package/dist/index.js +1631 -0
- package/package.json +76 -0
package/dist/index.js
ADDED
@@ -0,0 +1,1631 @@
|
|
1
|
+
var __defProp = Object.defineProperty;
|
2
|
+
var __defProps = Object.defineProperties;
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
7
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
8
|
+
var __pow = Math.pow;
|
9
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
10
|
+
var __spreadValues = (a, b) => {
|
11
|
+
for (var prop in b || (b = {}))
|
12
|
+
if (__hasOwnProp.call(b, prop))
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
14
|
+
if (__getOwnPropSymbols)
|
15
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
16
|
+
if (__propIsEnum.call(b, prop))
|
17
|
+
__defNormalProp(a, prop, b[prop]);
|
18
|
+
}
|
19
|
+
return a;
|
20
|
+
};
|
21
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
22
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
23
|
+
var __async = (__this, __arguments, generator) => {
|
24
|
+
return new Promise((resolve, reject) => {
|
25
|
+
var fulfilled = (value) => {
|
26
|
+
try {
|
27
|
+
step(generator.next(value));
|
28
|
+
} catch (e) {
|
29
|
+
reject(e);
|
30
|
+
}
|
31
|
+
};
|
32
|
+
var rejected = (value) => {
|
33
|
+
try {
|
34
|
+
step(generator.throw(value));
|
35
|
+
} catch (e) {
|
36
|
+
reject(e);
|
37
|
+
}
|
38
|
+
};
|
39
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
40
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
41
|
+
});
|
42
|
+
};
|
43
|
+
var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it);
|
44
|
+
|
45
|
+
// src/aws/iam.ts
|
46
|
+
import { IAMClient, ListUsersCommand } from "@aws-sdk/client-iam";
|
47
|
+
|
48
|
+
// src/aws/configuration.ts
|
49
|
+
var _AWSConfigSharingUtil = class _AWSConfigSharingUtil {
|
50
|
+
constructor() {
|
51
|
+
}
|
52
|
+
static setConfig({
|
53
|
+
accessKeyId,
|
54
|
+
secretAccessKey,
|
55
|
+
endpoint,
|
56
|
+
region
|
57
|
+
}) {
|
58
|
+
_AWSConfigSharingUtil.accessKeyId = accessKeyId;
|
59
|
+
_AWSConfigSharingUtil.secretAccessKey = secretAccessKey;
|
60
|
+
_AWSConfigSharingUtil.endpoint = endpoint;
|
61
|
+
_AWSConfigSharingUtil.region = region;
|
62
|
+
}
|
63
|
+
static getConfig() {
|
64
|
+
return {
|
65
|
+
accessKeyId: _AWSConfigSharingUtil.accessKeyId,
|
66
|
+
secretAccessKey: _AWSConfigSharingUtil.secretAccessKey,
|
67
|
+
region: _AWSConfigSharingUtil.region,
|
68
|
+
endpoint: _AWSConfigSharingUtil.endpoint
|
69
|
+
};
|
70
|
+
}
|
71
|
+
};
|
72
|
+
__publicField(_AWSConfigSharingUtil, "accessKeyId");
|
73
|
+
__publicField(_AWSConfigSharingUtil, "secretAccessKey");
|
74
|
+
__publicField(_AWSConfigSharingUtil, "endpoint");
|
75
|
+
__publicField(_AWSConfigSharingUtil, "region");
|
76
|
+
var AWSConfigSharingUtil = _AWSConfigSharingUtil;
|
77
|
+
|
78
|
+
// src/aws/iam.ts
|
79
|
+
var IAMUtil = class {
|
80
|
+
constructor({
|
81
|
+
accessKeyId = AWSConfigSharingUtil.accessKeyId,
|
82
|
+
secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
|
83
|
+
endpoint = AWSConfigSharingUtil.endpoint,
|
84
|
+
region = AWSConfigSharingUtil.region,
|
85
|
+
debug = false
|
86
|
+
} = {}) {
|
87
|
+
__publicField(this, "iam");
|
88
|
+
const credentials = { accessKeyId, secretAccessKey };
|
89
|
+
const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
|
90
|
+
if (debug) {
|
91
|
+
console.log("IAMUtil client options", options);
|
92
|
+
}
|
93
|
+
this.iam = new IAMClient(options);
|
94
|
+
}
|
95
|
+
get client() {
|
96
|
+
return this.iam;
|
97
|
+
}
|
98
|
+
getUserList() {
|
99
|
+
return __async(this, null, function* () {
|
100
|
+
const command = new ListUsersCommand({});
|
101
|
+
return this.iam.send(command);
|
102
|
+
});
|
103
|
+
}
|
104
|
+
listUsers(maxItems) {
|
105
|
+
return __async(this, null, function* () {
|
106
|
+
try {
|
107
|
+
const command = new ListUsersCommand({ MaxItems: maxItems });
|
108
|
+
const response = yield this.iam.send(command);
|
109
|
+
return response.Users;
|
110
|
+
} catch (error) {
|
111
|
+
console.error("Error listing IAM users:", error);
|
112
|
+
return null;
|
113
|
+
}
|
114
|
+
});
|
115
|
+
}
|
116
|
+
};
|
117
|
+
|
118
|
+
// src/aws/lambda.ts
|
119
|
+
import { Lambda, InvocationType, InvokeCommand } from "@aws-sdk/client-lambda";
|
120
|
+
|
121
|
+
// src/utils/logger.ts
|
122
|
+
import { Logger } from "stack-trace-logger";
|
123
|
+
var includeCloudWatchOptions = process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY && process.env.AWS_REGION && process.env.AWS_LOG_GROUP_NAME && process.env.AWS_LOG_STREAM_NAME;
|
124
|
+
var _a;
|
125
|
+
var logger = new Logger(__spreadValues({
|
126
|
+
serviceName: process.env.SERVICE_NAME || "SERVER",
|
127
|
+
loggingModeLevel: process.env.LOGGING_MODE,
|
128
|
+
lineTraceLevels: (_a = process.env.LOGGING_STACK_TRACE_LEVELS) == null ? void 0 : _a.split(","),
|
129
|
+
stackTraceLines: { error: 3, warn: 3, info: 1 },
|
130
|
+
tags: ["reqId?", "url?"],
|
131
|
+
runLocally: true
|
132
|
+
}, includeCloudWatchOptions && {
|
133
|
+
transportCloudWatchOptions: {
|
134
|
+
awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
135
|
+
awsSecretKey: process.env.AWS_SECRET_ACCESS_KEY,
|
136
|
+
awsRegion: process.env.AWS_REGION,
|
137
|
+
logGroupName: process.env.AWS_LOG_GROUP_NAME,
|
138
|
+
logStreamName: process.env.AWS_LOG_STREAM_NAME,
|
139
|
+
retentionInDays: +process.env.AWS_LOG_RETENTION_IN_DAY
|
140
|
+
}
|
141
|
+
}));
|
142
|
+
|
143
|
+
// src/aws/lambda.ts
|
144
|
+
var LambdaUtil = class {
|
145
|
+
constructor({
|
146
|
+
accessKeyId = AWSConfigSharingUtil.accessKeyId,
|
147
|
+
secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
|
148
|
+
endpoint = AWSConfigSharingUtil.endpoint,
|
149
|
+
region = AWSConfigSharingUtil.region,
|
150
|
+
serviceFunctionName,
|
151
|
+
debug = false
|
152
|
+
}) {
|
153
|
+
__publicField(this, "lambda");
|
154
|
+
__publicField(this, "serviceFunctionName");
|
155
|
+
const credentials = { accessKeyId, secretAccessKey };
|
156
|
+
const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
|
157
|
+
if (debug) {
|
158
|
+
console.log("LambdaUtil client options", options);
|
159
|
+
}
|
160
|
+
this.serviceFunctionName = serviceFunctionName;
|
161
|
+
this.lambda = new Lambda(__spreadValues(__spreadValues(__spreadValues({}, credentials && { credentials }), endpoint && { endpoint }), region && { region }));
|
162
|
+
}
|
163
|
+
directInvoke(_0) {
|
164
|
+
return __async(this, arguments, function* ({
|
165
|
+
payload = {},
|
166
|
+
invocationType = InvocationType.Event
|
167
|
+
}) {
|
168
|
+
var _a2;
|
169
|
+
const Payload = JSON.stringify(payload);
|
170
|
+
try {
|
171
|
+
const command = new InvokeCommand({
|
172
|
+
FunctionName: this.serviceFunctionName,
|
173
|
+
Payload,
|
174
|
+
InvocationType: invocationType
|
175
|
+
});
|
176
|
+
const data = yield this.lambda.send(command);
|
177
|
+
if (invocationType === InvocationType.RequestResponse) {
|
178
|
+
logger.info(null, "directInvoke lambda function response", { FunctionName, data });
|
179
|
+
}
|
180
|
+
const status = (_a2 = data.StatusCode) != null ? _a2 : 200;
|
181
|
+
const result = data.Payload;
|
182
|
+
return status >= 200 && status < 300 ? result : Promise.reject(result);
|
183
|
+
} catch (err) {
|
184
|
+
logger.error(null, "failed to directInvoke lambda function", {
|
185
|
+
err,
|
186
|
+
Payload,
|
187
|
+
FunctionName
|
188
|
+
});
|
189
|
+
throw err;
|
190
|
+
}
|
191
|
+
});
|
192
|
+
}
|
193
|
+
runLambdaInDryRunMode(payload) {
|
194
|
+
return __async(this, null, function* () {
|
195
|
+
return this.directInvoke({
|
196
|
+
payload,
|
197
|
+
invocationType: InvocationType.DryRun
|
198
|
+
});
|
199
|
+
});
|
200
|
+
}
|
201
|
+
triggerLambdaEvent(payload) {
|
202
|
+
return __async(this, null, function* () {
|
203
|
+
return this.directInvoke({
|
204
|
+
payload,
|
205
|
+
invocationType: InvocationType.Event
|
206
|
+
});
|
207
|
+
});
|
208
|
+
}
|
209
|
+
runAndGetLambdaResponse(payload) {
|
210
|
+
return __async(this, null, function* () {
|
211
|
+
return this.directInvoke({
|
212
|
+
payload,
|
213
|
+
invocationType: InvocationType.RequestResponse
|
214
|
+
});
|
215
|
+
});
|
216
|
+
}
|
217
|
+
};
|
218
|
+
|
219
|
+
// src/aws/s3-bucket.ts
|
220
|
+
import ms from "ms";
|
221
|
+
import path from "pathe";
|
222
|
+
import http from "http";
|
223
|
+
import https from "https";
|
224
|
+
import { pipeline } from "stream";
|
225
|
+
import { promisify } from "util";
|
226
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
227
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
228
|
+
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
229
|
+
import { Buffer as Buffer2 } from "buffer";
|
230
|
+
import archiver from "archiver";
|
231
|
+
import { Readable } from "stream";
|
232
|
+
import {
|
233
|
+
CreateBucketCommand,
|
234
|
+
GetObjectCommand,
|
235
|
+
S3Client,
|
236
|
+
DeleteBucketCommand,
|
237
|
+
HeadBucketCommand,
|
238
|
+
PutPublicAccessBlockCommand,
|
239
|
+
PutBucketPolicyCommand,
|
240
|
+
ListBucketsCommand,
|
241
|
+
GetBucketPolicyCommand,
|
242
|
+
GetBucketVersioningCommand,
|
243
|
+
GetBucketEncryptionCommand,
|
244
|
+
GetPublicAccessBlockCommand,
|
245
|
+
GetBucketAclCommand,
|
246
|
+
PutObjectCommand,
|
247
|
+
HeadObjectCommand,
|
248
|
+
ListObjectsCommand,
|
249
|
+
PutObjectTaggingCommand,
|
250
|
+
GetObjectTaggingCommand,
|
251
|
+
DeleteObjectCommand,
|
252
|
+
ListObjectsV2Command,
|
253
|
+
DeleteObjectsCommand
|
254
|
+
} from "@aws-sdk/client-s3";
|
255
|
+
|
256
|
+
// src/utils/consts.ts
|
257
|
+
var ACLs = /* @__PURE__ */ ((ACLs2) => {
|
258
|
+
ACLs2["private"] = "private";
|
259
|
+
ACLs2["publicRead"] = "public-read";
|
260
|
+
ACLs2["publicReadWrite"] = "public-read-write";
|
261
|
+
return ACLs2;
|
262
|
+
})(ACLs || {});
|
263
|
+
|
264
|
+
// src/utils/concurrency.ts
|
265
|
+
import pLimit from "p-limit";
|
266
|
+
var s3Limiter = pLimit(4);
|
267
|
+
|
268
|
+
// src/aws/s3-bucket.ts
|
269
|
+
import multer from "multer";
|
270
|
+
import multerS3 from "multer-s3";
|
271
|
+
import bytes from "bytes";
|
272
|
+
var pump = promisify(pipeline);
|
273
|
+
var parseRangeHeader = (range, contentLength, chunkSize) => {
|
274
|
+
if (!range || !range.startsWith("bytes=")) return null;
|
275
|
+
const rangeParts = range.replace("bytes=", "").split("-");
|
276
|
+
const start = parseInt(rangeParts[0], 10);
|
277
|
+
let end = parseInt(rangeParts[1], 10);
|
278
|
+
end = end || start + chunkSize - 1;
|
279
|
+
if (isNaN(start) || start < 0 || start >= contentLength) return null;
|
280
|
+
if (isNaN(end) || end < start || end >= contentLength) {
|
281
|
+
return [start, contentLength - 1];
|
282
|
+
}
|
283
|
+
return [start, Math.min(end, end)];
|
284
|
+
};
|
285
|
+
var S3BucketUtil = class _S3BucketUtil {
|
286
|
+
constructor({
|
287
|
+
logger: logger2,
|
288
|
+
bucket,
|
289
|
+
reqId,
|
290
|
+
accessKeyId = AWSConfigSharingUtil.accessKeyId,
|
291
|
+
secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
|
292
|
+
endpoint = AWSConfigSharingUtil.endpoint,
|
293
|
+
region = AWSConfigSharingUtil.region,
|
294
|
+
s3ForcePathStyle = true,
|
295
|
+
maxUploadFileSizeRestriction = "10GB"
|
296
|
+
}) {
|
297
|
+
__publicField(this, "s3Client");
|
298
|
+
__publicField(this, "bucket");
|
299
|
+
__publicField(this, "endpoint");
|
300
|
+
__publicField(this, "region");
|
301
|
+
__publicField(this, "logger");
|
302
|
+
__publicField(this, "reqId");
|
303
|
+
__publicField(this, "maxUploadFileSizeRestriction");
|
304
|
+
const credentials = { accessKeyId, secretAccessKey };
|
305
|
+
const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
|
306
|
+
this.endpoint = endpoint;
|
307
|
+
this.region = region;
|
308
|
+
this.bucket = bucket;
|
309
|
+
this.logger = logger2;
|
310
|
+
this.reqId = reqId != null ? reqId : null;
|
311
|
+
this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
|
312
|
+
const s3ClientParams = __spreadProps(__spreadValues(__spreadValues({}, options), s3ForcePathStyle && { forcePathStyle: s3ForcePathStyle }), {
|
313
|
+
requestHandler: new NodeHttpHandler({
|
314
|
+
httpAgent: new http.Agent({ keepAlive: true, maxSockets: 300 }),
|
315
|
+
httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 300 }),
|
316
|
+
connectionTimeout: 3e3,
|
317
|
+
socketTimeout: 3e4
|
318
|
+
})
|
319
|
+
});
|
320
|
+
this.s3Client = new S3Client(s3ClientParams);
|
321
|
+
}
|
322
|
+
get link() {
|
323
|
+
return this.endpoint === "http://localhost:4566" ? `${this.endpoint}/${this.bucket}/` : `https://s3.${this.region}.amazonaws.com/${this.bucket}/`;
|
324
|
+
}
|
325
|
+
execute(command, options) {
|
326
|
+
return __async(this, null, function* () {
|
327
|
+
return this.s3Client.send(command, options);
|
328
|
+
});
|
329
|
+
}
|
330
|
+
// ##### BUCKET BLOCK ##########################
|
331
|
+
getBucketList() {
|
332
|
+
return __async(this, arguments, function* (options = {}, includePublicAccess = false) {
|
333
|
+
const command = new ListBucketsCommand(options);
|
334
|
+
const response = yield this.execute(command);
|
335
|
+
const responseData = (response == null ? void 0 : response.Buckets) || null;
|
336
|
+
if (!responseData) return null;
|
337
|
+
if (includePublicAccess) {
|
338
|
+
yield Promise.allSettled(
|
339
|
+
responseData.map((data) => __async(this, null, function* () {
|
340
|
+
const result = yield this.execute(
|
341
|
+
new GetPublicAccessBlockCommand({ Bucket: data.Name })
|
342
|
+
);
|
343
|
+
data.PublicAccessBlockConfiguration = result.PublicAccessBlockConfiguration;
|
344
|
+
}))
|
345
|
+
);
|
346
|
+
}
|
347
|
+
return responseData;
|
348
|
+
});
|
349
|
+
}
|
350
|
+
isBucketExists() {
|
351
|
+
return __async(this, null, function* () {
|
352
|
+
var _a2, _b;
|
353
|
+
const bucketName = this.bucket;
|
354
|
+
try {
|
355
|
+
yield this.execute(new HeadBucketCommand({ Bucket: bucketName }));
|
356
|
+
return true;
|
357
|
+
} catch (err) {
|
358
|
+
if (err.name !== "NotFound" && ((_a2 = err.$metadata) == null ? void 0 : _a2.httpStatusCode) !== 404) {
|
359
|
+
(_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Error checking bucket:", err);
|
360
|
+
throw err;
|
361
|
+
} else {
|
362
|
+
return false;
|
363
|
+
}
|
364
|
+
}
|
365
|
+
});
|
366
|
+
}
|
367
|
+
initAsPublicBucket() {
|
368
|
+
return __async(this, null, function* () {
|
369
|
+
var _a2, _b;
|
370
|
+
const bucketName = this.bucket;
|
371
|
+
const isExists = yield this.isBucketExists();
|
372
|
+
if (isExists) {
|
373
|
+
(_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
|
374
|
+
return;
|
375
|
+
}
|
376
|
+
const data = yield this.execute(new CreateBucketCommand({ Bucket: bucketName }));
|
377
|
+
CREATE_PUBLICK_ACCESS_BLOCK: {
|
378
|
+
const command = new PutPublicAccessBlockCommand({
|
379
|
+
Bucket: bucketName,
|
380
|
+
PublicAccessBlockConfiguration: {
|
381
|
+
BlockPublicAcls: false,
|
382
|
+
IgnorePublicAcls: false,
|
383
|
+
BlockPublicPolicy: false,
|
384
|
+
RestrictPublicBuckets: false
|
385
|
+
}
|
386
|
+
});
|
387
|
+
yield this.execute(command);
|
388
|
+
}
|
389
|
+
UPDATE_PUBLICK_ACCESS_POLICY: {
|
390
|
+
const policy = {
|
391
|
+
Version: "2012-10-17",
|
392
|
+
Statement: [
|
393
|
+
{
|
394
|
+
Sid: "PublicReadGetObject",
|
395
|
+
Effect: "Allow",
|
396
|
+
Principal: "*",
|
397
|
+
Action: "s3:GetObject",
|
398
|
+
Resource: `arn:aws:s3:::${bucketName}/*`
|
399
|
+
}
|
400
|
+
]
|
401
|
+
};
|
402
|
+
const command = new PutBucketPolicyCommand({ Bucket: bucketName, Policy: JSON.stringify(policy) });
|
403
|
+
yield this.execute(command);
|
404
|
+
}
|
405
|
+
(_b = this.logger) == null ? void 0 : _b.info(this.reqId, `Public bucket created successfully.`, { bucketName });
|
406
|
+
return data;
|
407
|
+
});
|
408
|
+
}
|
409
|
+
initAsPrivateBucket(includeConstraintLocation) {
|
410
|
+
return __async(this, null, function* () {
|
411
|
+
var _a2, _b;
|
412
|
+
const bucketName = this.bucket;
|
413
|
+
const isExists = yield this.isBucketExists();
|
414
|
+
if (isExists) {
|
415
|
+
(_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
|
416
|
+
return;
|
417
|
+
}
|
418
|
+
const createParams = __spreadValues({
|
419
|
+
Bucket: bucketName
|
420
|
+
}, includeConstraintLocation && {
|
421
|
+
CreateBucketConfiguration: { LocationConstraint: this.region }
|
422
|
+
});
|
423
|
+
const data = yield this.execute(new CreateBucketCommand(createParams));
|
424
|
+
(_b = this.logger) == null ? void 0 : _b.info(this.reqId, `Private bucket created successfully.`, { bucketName });
|
425
|
+
return data;
|
426
|
+
});
|
427
|
+
}
|
428
|
+
initBucket() {
|
429
|
+
return __async(this, arguments, function* (acl = "private" /* private */, includeConstraintLocation = false) {
|
430
|
+
var _a2;
|
431
|
+
const bucketName = this.bucket;
|
432
|
+
const isExists = yield this.isBucketExists();
|
433
|
+
if (isExists) {
|
434
|
+
(_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
|
435
|
+
return;
|
436
|
+
}
|
437
|
+
const data = acl === "private" /* private */ ? yield this.initAsPrivateBucket(includeConstraintLocation) : yield this.initAsPublicBucket();
|
438
|
+
return data;
|
439
|
+
});
|
440
|
+
}
|
441
|
+
emptyBucket() {
|
442
|
+
return __async(this, null, function* () {
|
443
|
+
let ContinuationToken = void 0;
|
444
|
+
do {
|
445
|
+
const listResp = yield this.execute(
|
446
|
+
new ListObjectsV2Command({
|
447
|
+
Bucket: this.bucket,
|
448
|
+
ContinuationToken
|
449
|
+
})
|
450
|
+
);
|
451
|
+
if (listResp.Contents && listResp.Contents.length > 0) {
|
452
|
+
yield this.execute(
|
453
|
+
new DeleteObjectsCommand({
|
454
|
+
Bucket: this.bucket,
|
455
|
+
Delete: {
|
456
|
+
Objects: listResp.Contents.map((obj) => ({ Key: obj.Key }))
|
457
|
+
}
|
458
|
+
})
|
459
|
+
);
|
460
|
+
}
|
461
|
+
ContinuationToken = listResp.NextContinuationToken;
|
462
|
+
} while (ContinuationToken);
|
463
|
+
});
|
464
|
+
}
|
465
|
+
bucketInfo(options) {
|
466
|
+
return __async(this, null, function* () {
|
467
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
|
468
|
+
const bucketName = this.bucket;
|
469
|
+
const info = {
|
470
|
+
name: bucketName,
|
471
|
+
region: this.region,
|
472
|
+
endpoint: this.endpoint,
|
473
|
+
exists: false
|
474
|
+
};
|
475
|
+
try {
|
476
|
+
try {
|
477
|
+
const headBucketResponse = yield this.execute(
|
478
|
+
new HeadBucketCommand(__spreadValues({ Bucket: bucketName }, options))
|
479
|
+
);
|
480
|
+
(_a2 = this.logger) == null ? void 0 : _a2.debug("bucketInfo", "HeadBucketCommandOutput", headBucketResponse);
|
481
|
+
info.exists = true;
|
482
|
+
info.bucketRegion = headBucketResponse.BucketRegion;
|
483
|
+
info.accessPointAlias = headBucketResponse.AccessPointAlias;
|
484
|
+
} catch (err) {
|
485
|
+
if (err.name === "NotFound" || ((_b = err.$metadata) == null ? void 0 : _b.httpStatusCode) === 404) {
|
486
|
+
return info;
|
487
|
+
}
|
488
|
+
throw err;
|
489
|
+
}
|
490
|
+
try {
|
491
|
+
const buckets = yield this.getBucketList({ Prefix: this.bucket, BucketRegion: this.region });
|
492
|
+
(_c = this.logger) == null ? void 0 : _c.debug("bucketInfo", "getBucketList", { buckets });
|
493
|
+
const bucket = buckets == null ? void 0 : buckets.find((b) => b.Name === bucketName);
|
494
|
+
if (bucket == null ? void 0 : bucket.CreationDate) {
|
495
|
+
info.creationDate = bucket.CreationDate;
|
496
|
+
}
|
497
|
+
} catch (error) {
|
498
|
+
(_d = this.logger) == null ? void 0 : _d.warn(this.reqId, "Failed to get bucket creation date", { bucketName, error });
|
499
|
+
}
|
500
|
+
try {
|
501
|
+
const aclResponse = yield this.execute(
|
502
|
+
new GetBucketAclCommand({ Bucket: bucketName })
|
503
|
+
);
|
504
|
+
(_e = this.logger) == null ? void 0 : _e.debug("bucketInfo", "GetBucketAclCommandOutput", aclResponse);
|
505
|
+
info.acl = (_f = aclResponse.Grants) == null ? void 0 : _f.map((grant) => {
|
506
|
+
var _a3;
|
507
|
+
return {
|
508
|
+
grantee: (_a3 = grant.Grantee) == null ? void 0 : _a3.Type,
|
509
|
+
permission: grant.Permission
|
510
|
+
};
|
511
|
+
});
|
512
|
+
} catch (error) {
|
513
|
+
(_g = this.logger) == null ? void 0 : _g.warn(this.reqId, "Failed to get bucket ACL", { bucketName, error });
|
514
|
+
}
|
515
|
+
try {
|
516
|
+
const publicAccessResponse = yield this.execute(
|
517
|
+
new GetPublicAccessBlockCommand({ Bucket: bucketName })
|
518
|
+
);
|
519
|
+
(_h = this.logger) == null ? void 0 : _h.debug("bucketInfo", "GetPublicAccessBlockCommandOutput", publicAccessResponse);
|
520
|
+
info.publicAccessBlock = publicAccessResponse.PublicAccessBlockConfiguration;
|
521
|
+
} catch (error) {
|
522
|
+
if (error.name !== "NoSuchPublicAccessBlockConfiguration") {
|
523
|
+
(_i = this.logger) == null ? void 0 : _i.warn(this.reqId, "Failed to get public access block", { bucketName, error });
|
524
|
+
}
|
525
|
+
}
|
526
|
+
try {
|
527
|
+
const policyResponse = yield this.execute(
|
528
|
+
new GetBucketPolicyCommand({ Bucket: bucketName })
|
529
|
+
);
|
530
|
+
(_j = this.logger) == null ? void 0 : _j.debug("bucketInfo", "GetBucketPolicyCommandOutput", policyResponse);
|
531
|
+
if (policyResponse.Policy) {
|
532
|
+
info.policy = JSON.parse(policyResponse.Policy);
|
533
|
+
}
|
534
|
+
} catch (error) {
|
535
|
+
if (error.name !== "NoSuchBucketPolicy") {
|
536
|
+
(_k = this.logger) == null ? void 0 : _k.warn(this.reqId, "Failed to get bucket policy", { bucketName, error });
|
537
|
+
}
|
538
|
+
}
|
539
|
+
try {
|
540
|
+
const versioningResponse = yield this.execute(
|
541
|
+
new GetBucketVersioningCommand({ Bucket: bucketName })
|
542
|
+
);
|
543
|
+
(_l = this.logger) == null ? void 0 : _l.debug("bucketInfo", "GetBucketVersioningCommandOutput", versioningResponse);
|
544
|
+
info.versioning = versioningResponse.Status || "Disabled";
|
545
|
+
} catch (error) {
|
546
|
+
(_m = this.logger) == null ? void 0 : _m.warn(this.reqId, "Failed to get bucket versioning", { bucketName, error });
|
547
|
+
}
|
548
|
+
try {
|
549
|
+
const encryptionResponse = yield this.execute(
|
550
|
+
new GetBucketEncryptionCommand({ Bucket: bucketName })
|
551
|
+
);
|
552
|
+
(_n = this.logger) == null ? void 0 : _n.debug("bucketInfo", "GetBucketEncryptionCommandOutput", encryptionResponse);
|
553
|
+
info.encryption = {
|
554
|
+
enabled: true,
|
555
|
+
type: (_r = (_q = (_p = (_o = encryptionResponse.ServerSideEncryptionConfiguration) == null ? void 0 : _o.Rules) == null ? void 0 : _p[0]) == null ? void 0 : _q.ApplyServerSideEncryptionByDefault) == null ? void 0 : _r.SSEAlgorithm
|
556
|
+
};
|
557
|
+
} catch (error) {
|
558
|
+
if (error.name === "ServerSideEncryptionConfigurationNotFoundError") {
|
559
|
+
info.encryption = { enabled: false };
|
560
|
+
} else {
|
561
|
+
(_s = this.logger) == null ? void 0 : _s.warn(this.reqId, "Failed to get bucket encryption", { bucketName, error });
|
562
|
+
info.encryption = { enabled: false };
|
563
|
+
}
|
564
|
+
}
|
565
|
+
(_t = this.logger) == null ? void 0 : _t.debug("bucketInfo", "bucket info response", info);
|
566
|
+
return info;
|
567
|
+
} catch (error) {
|
568
|
+
(_u = this.logger) == null ? void 0 : _u.error(this.reqId, "Failed to get bucket info", { bucketName, error });
|
569
|
+
throw error;
|
570
|
+
}
|
571
|
+
});
|
572
|
+
}
|
573
|
+
destroyBucket(forceDeleteAllFilesBeforeDestroyBucket = false) {
|
574
|
+
return __async(this, null, function* () {
|
575
|
+
var _a2;
|
576
|
+
const bucketName = this.bucket;
|
577
|
+
const isExists = yield this.isBucketExists();
|
578
|
+
if (!isExists) {
|
579
|
+
(_a2 = this.logger) == null ? void 0 : _a2.debug(this.reqId, `Bucket not exists.`, { bucketName });
|
580
|
+
return;
|
581
|
+
}
|
582
|
+
if (forceDeleteAllFilesBeforeDestroyBucket) {
|
583
|
+
yield this.emptyBucket();
|
584
|
+
}
|
585
|
+
const createParams = { Bucket: bucketName };
|
586
|
+
const data = yield this.execute(new DeleteBucketCommand(createParams));
|
587
|
+
return data;
|
588
|
+
});
|
589
|
+
}
|
590
|
+
// ##### DIRECTORY BLOCK ##########################
|
591
|
+
createDirectory(directoryPath) {
|
592
|
+
return __async(this, null, function* () {
|
593
|
+
let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
594
|
+
if (!normalizedPath) throw new Error("No directory path provided");
|
595
|
+
if (normalizedPath === "/") normalizedPath = "";
|
596
|
+
const command = new PutObjectCommand({ Bucket: this.bucket, Key: `${normalizedPath}/` });
|
597
|
+
const result = yield this.execute(command);
|
598
|
+
return result;
|
599
|
+
});
|
600
|
+
}
|
601
|
+
deleteDirectory(directoryPath) {
|
602
|
+
return __async(this, null, function* () {
|
603
|
+
var _a2, _b, _c, _d, _e, _f, _g;
|
604
|
+
let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
605
|
+
if (!normalizedPath) {
|
606
|
+
throw new Error("No directory path provided");
|
607
|
+
}
|
608
|
+
if (normalizedPath === "/") normalizedPath = "";
|
609
|
+
let totalDeletedCount = 0;
|
610
|
+
let ContinuationToken = void 0;
|
611
|
+
do {
|
612
|
+
const listResp = yield this.execute(
|
613
|
+
new ListObjectsV2Command({
|
614
|
+
Bucket: this.bucket,
|
615
|
+
Prefix: normalizedPath,
|
616
|
+
ContinuationToken
|
617
|
+
})
|
618
|
+
);
|
619
|
+
if (listResp.Contents && listResp.Contents.length > 0) {
|
620
|
+
const deleteResult = yield this.execute(
|
621
|
+
new DeleteObjectsCommand({
|
622
|
+
Bucket: this.bucket,
|
623
|
+
Delete: {
|
624
|
+
Objects: listResp.Contents.map((obj) => ({ Key: obj.Key })),
|
625
|
+
Quiet: false
|
626
|
+
}
|
627
|
+
})
|
628
|
+
);
|
629
|
+
totalDeletedCount += (_b = (_a2 = deleteResult.Deleted) == null ? void 0 : _a2.length) != null ? _b : 0;
|
630
|
+
if (deleteResult.Errors && deleteResult.Errors.length > 0) {
|
631
|
+
(_c = this.logger) == null ? void 0 : _c.warn(this.reqId, `Some objects failed to delete`, {
|
632
|
+
directoryPath: normalizedPath,
|
633
|
+
errors: deleteResult.Errors
|
634
|
+
});
|
635
|
+
}
|
636
|
+
}
|
637
|
+
ContinuationToken = listResp.NextContinuationToken;
|
638
|
+
} while (ContinuationToken);
|
639
|
+
if (totalDeletedCount === 0) {
|
640
|
+
const directoryExists = yield this.fileExists(normalizedPath);
|
641
|
+
if (!directoryExists) {
|
642
|
+
(_d = this.logger) == null ? void 0 : _d.debug(this.reqId, `Directory not found`, { directoryPath: normalizedPath });
|
643
|
+
return null;
|
644
|
+
}
|
645
|
+
}
|
646
|
+
try {
|
647
|
+
yield this.execute(
|
648
|
+
new DeleteObjectCommand({
|
649
|
+
Bucket: this.bucket,
|
650
|
+
Key: normalizedPath
|
651
|
+
})
|
652
|
+
);
|
653
|
+
totalDeletedCount++;
|
654
|
+
} catch (error) {
|
655
|
+
if (error.name !== "NotFound" && ((_e = error.$metadata) == null ? void 0 : _e.httpStatusCode) !== 404) {
|
656
|
+
(_f = this.logger) == null ? void 0 : _f.warn(this.reqId, `Failed to delete directory marker`, {
|
657
|
+
directoryPath: normalizedPath,
|
658
|
+
error
|
659
|
+
});
|
660
|
+
}
|
661
|
+
}
|
662
|
+
(_g = this.logger) == null ? void 0 : _g.info(this.reqId, `Directory deleted successfully`, {
|
663
|
+
directoryPath: normalizedPath,
|
664
|
+
deletedCount: totalDeletedCount
|
665
|
+
});
|
666
|
+
return {
|
667
|
+
Deleted: [{ Key: normalizedPath }],
|
668
|
+
$metadata: {}
|
669
|
+
};
|
670
|
+
});
|
671
|
+
}
|
672
|
+
directoryList(directoryPath) {
|
673
|
+
return __async(this, null, function* () {
|
674
|
+
let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
675
|
+
if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
|
676
|
+
else normalizedPath = "";
|
677
|
+
const result = yield this.execute(
|
678
|
+
new ListObjectsV2Command({
|
679
|
+
Bucket: this.bucket,
|
680
|
+
Prefix: normalizedPath,
|
681
|
+
Delimiter: "/"
|
682
|
+
})
|
683
|
+
);
|
684
|
+
const directories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
|
685
|
+
const relativePath = prefix.replace(normalizedPath, "");
|
686
|
+
const dir = relativePath.replace(/\/$/, "");
|
687
|
+
return dir;
|
688
|
+
}).filter((dir) => dir);
|
689
|
+
const files = (result.Contents || []).filter((content) => {
|
690
|
+
var _a2;
|
691
|
+
return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
|
692
|
+
}).map((content) => __spreadProps(__spreadValues({}, content), {
|
693
|
+
Name: content.Key.replace(normalizedPath, "") || content.Key,
|
694
|
+
LastModified: new Date(content.LastModified)
|
695
|
+
}));
|
696
|
+
return { directories, files };
|
697
|
+
});
|
698
|
+
}
|
699
|
+
/**
|
700
|
+
* Get all files recursively (example for search/indexing)
|
701
|
+
* @param directoryPath
|
702
|
+
*/
|
703
|
+
directoryListRecursive(directoryPath) {
|
704
|
+
return __async(this, null, function* () {
|
705
|
+
let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
706
|
+
if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
|
707
|
+
else normalizedPath = "";
|
708
|
+
const allDirectories = [];
|
709
|
+
const allFiles = [];
|
710
|
+
let ContinuationToken = void 0;
|
711
|
+
do {
|
712
|
+
const command = new ListObjectsV2Command({
|
713
|
+
Bucket: this.bucket,
|
714
|
+
Prefix: normalizedPath,
|
715
|
+
ContinuationToken
|
716
|
+
});
|
717
|
+
const result = yield this.execute(command);
|
718
|
+
if (result.Contents) {
|
719
|
+
for (const content of result.Contents) {
|
720
|
+
const fullPath = content.Key;
|
721
|
+
const relativePath = fullPath.replace(normalizedPath, "");
|
722
|
+
const filename = fullPath.split("/").pop();
|
723
|
+
if (fullPath.endsWith("/")) {
|
724
|
+
allDirectories.push(relativePath.slice(0, -1));
|
725
|
+
} else {
|
726
|
+
allFiles.push(__spreadProps(__spreadValues({}, content), {
|
727
|
+
Name: filename,
|
728
|
+
Path: fullPath,
|
729
|
+
LastModified: new Date(content.LastModified)
|
730
|
+
}));
|
731
|
+
}
|
732
|
+
}
|
733
|
+
}
|
734
|
+
ContinuationToken = result.NextContinuationToken;
|
735
|
+
} while (ContinuationToken);
|
736
|
+
return { directories: allDirectories, files: allFiles };
|
737
|
+
});
|
738
|
+
}
|
739
|
+
/**
|
740
|
+
* Get tree files recursively (example for build file explorer UI)
|
741
|
+
* @param directoryPath - the directory start from
|
742
|
+
* @example
|
743
|
+
* const tree = await s3Util.getDirectoryTree('uploads');
|
744
|
+
* // {
|
745
|
+
* // name: 'uploads',
|
746
|
+
* // path: 'uploads/',
|
747
|
+
* // type: 'directory',
|
748
|
+
* // children: [
|
749
|
+
* // {
|
750
|
+
* // name: 'logo.png',
|
751
|
+
* // path: 'uploads/logo.png',
|
752
|
+
* // type: 'file',
|
753
|
+
* // size: 12345,
|
754
|
+
* // lastModified: Date
|
755
|
+
* // },
|
756
|
+
* // {
|
757
|
+
* // name: 'images',
|
758
|
+
* // path: 'uploads/images/',
|
759
|
+
* // type: 'directory',
|
760
|
+
* // children: [
|
761
|
+
* // { name: 'photo1.jpg', type: 'file', ... },
|
762
|
+
* // { name: 'photo2.jpg', type: 'file', ... }
|
763
|
+
* // ]
|
764
|
+
* // }
|
765
|
+
* // ]
|
766
|
+
* // }
|
767
|
+
*/
|
768
|
+
directoryTree(directoryPath) {
|
769
|
+
return __async(this, null, function* () {
|
770
|
+
let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
771
|
+
const lastDirectory = directoryPath == null ? void 0 : directoryPath.split("/").pop();
|
772
|
+
const { directories, files } = yield this.directoryList(normalizedPath);
|
773
|
+
if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
|
774
|
+
else normalizedPath = "";
|
775
|
+
const treeNode = {
|
776
|
+
path: "/" + normalizedPath,
|
777
|
+
name: lastDirectory || this.bucket,
|
778
|
+
type: "directory",
|
779
|
+
children: []
|
780
|
+
};
|
781
|
+
for (const file of files) {
|
782
|
+
treeNode.children.push({
|
783
|
+
path: "/" + file.Key,
|
784
|
+
name: file.Name,
|
785
|
+
type: "file",
|
786
|
+
size: file.Size,
|
787
|
+
lastModified: file.LastModified
|
788
|
+
});
|
789
|
+
}
|
790
|
+
for (const dir of directories) {
|
791
|
+
const subPath = treeNode.path + dir;
|
792
|
+
const subTree = yield this.directoryTree(subPath);
|
793
|
+
treeNode.children.push(subTree);
|
794
|
+
}
|
795
|
+
return treeNode;
|
796
|
+
});
|
797
|
+
}
|
798
|
+
// ##### FILES BLOCK ##########################
|
799
|
+
fileInfo(filePath) {
|
800
|
+
return __async(this, null, function* () {
|
801
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
802
|
+
if (!normalizedKey || normalizedKey === "/") {
|
803
|
+
throw new Error("No file key provided");
|
804
|
+
}
|
805
|
+
const command = new HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
|
806
|
+
return yield this.execute(command);
|
807
|
+
});
|
808
|
+
}
|
809
|
+
fileListInfo(directoryPath, fileNamePrefix) {
|
810
|
+
return __async(this, null, function* () {
|
811
|
+
var _a2, _b;
|
812
|
+
let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
813
|
+
if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
|
814
|
+
else normalizedPath = "";
|
815
|
+
const prefix = normalizedPath + (fileNamePrefix || "");
|
816
|
+
const command = new ListObjectsCommand({
|
817
|
+
Bucket: this.bucket,
|
818
|
+
Prefix: prefix,
|
819
|
+
Delimiter: "/"
|
820
|
+
});
|
821
|
+
const result = yield this.execute(command);
|
822
|
+
const files = ((_a2 = result.Contents) != null ? _a2 : []).filter((v) => v).map(
|
823
|
+
(content) => {
|
824
|
+
var _a3, _b2;
|
825
|
+
return __spreadProps(__spreadValues({}, content), {
|
826
|
+
Name: (_b2 = (_a3 = content.Key) == null ? void 0 : _a3.replace(prefix, "")) != null ? _b2 : content.Key,
|
827
|
+
LastModified: content.LastModified ? new Date(content.LastModified) : null
|
828
|
+
});
|
829
|
+
}
|
830
|
+
).filter((content) => content.Name);
|
831
|
+
(_b = this.logger) == null ? void 0 : _b.debug(null, "file list info", { prefix, files });
|
832
|
+
return files;
|
833
|
+
});
|
834
|
+
}
|
835
|
+
taggingFile(filePath, tagVersion = "1.0.0") {
|
836
|
+
return __async(this, null, function* () {
|
837
|
+
try {
|
838
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
839
|
+
if (!normalizedKey || normalizedKey === "/") {
|
840
|
+
throw new Error("No file key provided");
|
841
|
+
}
|
842
|
+
const command = new PutObjectTaggingCommand({
|
843
|
+
Bucket: this.bucket,
|
844
|
+
Key: normalizedKey,
|
845
|
+
Tagging: { TagSet: [{ Key: "version", Value: tagVersion }] }
|
846
|
+
});
|
847
|
+
yield this.execute(command);
|
848
|
+
return true;
|
849
|
+
} catch (e) {
|
850
|
+
return false;
|
851
|
+
}
|
852
|
+
});
|
853
|
+
}
|
854
|
+
fileVersion(filePath) {
|
855
|
+
return __async(this, null, function* () {
|
856
|
+
var _a2, _b;
|
857
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
858
|
+
if (!normalizedKey || normalizedKey === "/") {
|
859
|
+
throw new Error("No file key provided");
|
860
|
+
}
|
861
|
+
const command = new GetObjectTaggingCommand({ Bucket: this.bucket, Key: normalizedKey });
|
862
|
+
const result = yield this.execute(command);
|
863
|
+
const tag = (_a2 = result.TagSet) == null ? void 0 : _a2.find((tag2) => tag2.Key === "version");
|
864
|
+
return (_b = tag == null ? void 0 : tag.Value) != null ? _b : "";
|
865
|
+
});
|
866
|
+
}
|
867
|
+
fileUrl(filePath, expiresIn = "15m") {
|
868
|
+
return __async(this, null, function* () {
|
869
|
+
var _a2;
|
870
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
871
|
+
if (!normalizedKey || normalizedKey === "/") {
|
872
|
+
throw new Error("No file key provided");
|
873
|
+
}
|
874
|
+
const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : ms(expiresIn) / 1e3;
|
875
|
+
const command = new GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
|
876
|
+
const url = yield getSignedUrl(this.s3Client, command, {
|
877
|
+
expiresIn: expiresInSeconds
|
878
|
+
// is using 3600 it's will expire in 1 hour (default is 900 seconds = 15 minutes)
|
879
|
+
});
|
880
|
+
(_a2 = this.logger) == null ? void 0 : _a2.info(null, "generate signed file url", { url, filePath: normalizedKey, expiresIn });
|
881
|
+
return url;
|
882
|
+
});
|
883
|
+
}
|
884
|
+
sizeOf(filePath, unit = "bytes") {
|
885
|
+
return __async(this, null, function* () {
|
886
|
+
var _a2, _b, _c;
|
887
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
888
|
+
if (!normalizedKey || normalizedKey === "/") {
|
889
|
+
throw new Error("No file key provided");
|
890
|
+
}
|
891
|
+
try {
|
892
|
+
const command = new HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
|
893
|
+
const headObject = yield this.execute(command);
|
894
|
+
const bytes2 = (_a2 = headObject.ContentLength) != null ? _a2 : 0;
|
895
|
+
switch (unit) {
|
896
|
+
case "KB":
|
897
|
+
return bytes2 / 1024;
|
898
|
+
case "MB":
|
899
|
+
return bytes2 / (1024 * 1024);
|
900
|
+
case "GB":
|
901
|
+
return bytes2 / (1024 * 1024 * 1024);
|
902
|
+
default:
|
903
|
+
return bytes2;
|
904
|
+
}
|
905
|
+
} catch (error) {
|
906
|
+
if (error.name === "NotFound" || ((_b = error.$metadata) == null ? void 0 : _b.httpStatusCode) === 404) {
|
907
|
+
(_c = this.logger) == null ? void 0 : _c.warn(this.reqId, "File not found", { filePath: normalizedKey });
|
908
|
+
return 0;
|
909
|
+
}
|
910
|
+
throw error;
|
911
|
+
}
|
912
|
+
});
|
913
|
+
}
|
914
|
+
fileExists(filePath) {
|
915
|
+
return __async(this, null, function* () {
|
916
|
+
var _a2;
|
917
|
+
try {
|
918
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
919
|
+
if (!normalizedKey || normalizedKey === "/") {
|
920
|
+
throw new Error("No file key provided");
|
921
|
+
}
|
922
|
+
const command = new HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
|
923
|
+
yield this.execute(command);
|
924
|
+
return true;
|
925
|
+
} catch (error) {
|
926
|
+
if (error.name === "NotFound" || ((_a2 = error.$metadata) == null ? void 0 : _a2.httpStatusCode) === 404) {
|
927
|
+
return false;
|
928
|
+
}
|
929
|
+
throw error;
|
930
|
+
}
|
931
|
+
});
|
932
|
+
}
|
933
|
+
fileContent(filePath, format = "buffer") {
|
934
|
+
return __async(this, null, function* () {
|
935
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
936
|
+
if (!normalizedKey || normalizedKey === "/") {
|
937
|
+
throw new Error("No file key provided");
|
938
|
+
}
|
939
|
+
const command = new GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
|
940
|
+
const result = yield this.execute(command);
|
941
|
+
if (!result.Body) {
|
942
|
+
throw new Error("File body is empty");
|
943
|
+
}
|
944
|
+
const stream = result.Body;
|
945
|
+
const chunks = [];
|
946
|
+
try {
|
947
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
|
948
|
+
const chunk = temp.value;
|
949
|
+
chunks.push(chunk);
|
950
|
+
}
|
951
|
+
} catch (temp) {
|
952
|
+
error = [temp];
|
953
|
+
} finally {
|
954
|
+
try {
|
955
|
+
more && (temp = iter.return) && (yield temp.call(iter));
|
956
|
+
} finally {
|
957
|
+
if (error)
|
958
|
+
throw error[0];
|
959
|
+
}
|
960
|
+
}
|
961
|
+
const buffer = Buffer2.concat(chunks);
|
962
|
+
if (format === "base64" || format === "utf8") {
|
963
|
+
return buffer.toString(format);
|
964
|
+
}
|
965
|
+
return buffer;
|
966
|
+
});
|
967
|
+
}
|
968
|
+
uploadFile(_0, _1) {
|
969
|
+
return __async(this, arguments, function* (filePath, fileData, acl = "private" /* private */, version = "1.0.0") {
|
970
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
971
|
+
if (!normalizedKey || normalizedKey === "/") {
|
972
|
+
throw new Error("No file key provided");
|
973
|
+
}
|
974
|
+
const upload = new Upload({
|
975
|
+
client: this.s3Client,
|
976
|
+
params: {
|
977
|
+
Bucket: this.bucket,
|
978
|
+
ACL: acl,
|
979
|
+
Key: normalizedKey,
|
980
|
+
Body: fileData,
|
981
|
+
Tagging: `version=${version}`
|
982
|
+
}
|
983
|
+
});
|
984
|
+
const result = yield upload.done();
|
985
|
+
return {
|
986
|
+
Bucket: this.bucket,
|
987
|
+
Key: normalizedKey,
|
988
|
+
Location: `https://${this.bucket}.s3.amazonaws.com/${normalizedKey}`,
|
989
|
+
test: `${this.link}/${normalizedKey}`,
|
990
|
+
ETag: result.ETag
|
991
|
+
};
|
992
|
+
});
|
993
|
+
}
|
994
|
+
deleteFile(filePath) {
|
995
|
+
return __async(this, null, function* () {
|
996
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
997
|
+
if (!normalizedKey || normalizedKey === "/") {
|
998
|
+
throw new Error("No file key provided");
|
999
|
+
}
|
1000
|
+
const command = new DeleteObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
|
1001
|
+
return yield this.execute(command);
|
1002
|
+
});
|
1003
|
+
}
|
1004
|
+
// ##### STREAMING BLOCK ##########################
|
1005
|
+
streamObjectFile(_0) {
|
1006
|
+
return __async(this, arguments, function* (filePath, {
|
1007
|
+
Range,
|
1008
|
+
checkFileExists = true,
|
1009
|
+
abortSignal
|
1010
|
+
} = {}) {
|
1011
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
1012
|
+
if (!normalizedKey || normalizedKey === "/") {
|
1013
|
+
throw new Error("No file key provided");
|
1014
|
+
}
|
1015
|
+
if (checkFileExists) {
|
1016
|
+
const isExists = yield this.fileExists(normalizedKey);
|
1017
|
+
if (!isExists) return null;
|
1018
|
+
}
|
1019
|
+
const command = new GetObjectCommand(__spreadValues({
|
1020
|
+
Bucket: this.bucket,
|
1021
|
+
Key: normalizedKey
|
1022
|
+
}, Range ? { Range } : {}));
|
1023
|
+
const response = yield this.execute(command, { abortSignal });
|
1024
|
+
if (!response.Body || !(response.Body instanceof Readable)) {
|
1025
|
+
throw new Error("Invalid response body: not a Readable stream");
|
1026
|
+
}
|
1027
|
+
return response.Body;
|
1028
|
+
});
|
1029
|
+
}
|
1030
|
+
streamVideoFile(_0) {
|
1031
|
+
return __async(this, arguments, function* ({
|
1032
|
+
filePath,
|
1033
|
+
Range,
|
1034
|
+
abortSignal
|
1035
|
+
}) {
|
1036
|
+
var _a2;
|
1037
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
1038
|
+
if (!normalizedKey || normalizedKey === "/") {
|
1039
|
+
throw new Error("No file key provided");
|
1040
|
+
}
|
1041
|
+
try {
|
1042
|
+
const cmd = new GetObjectCommand(__spreadValues({
|
1043
|
+
Bucket: this.bucket,
|
1044
|
+
Key: normalizedKey
|
1045
|
+
}, Range ? { Range } : {}));
|
1046
|
+
const data = yield s3Limiter(() => this.execute(cmd, { abortSignal }));
|
1047
|
+
const body = data.Body;
|
1048
|
+
if (!body) return null;
|
1049
|
+
return {
|
1050
|
+
body,
|
1051
|
+
meta: {
|
1052
|
+
contentType: data.ContentType,
|
1053
|
+
contentLength: data.ContentLength,
|
1054
|
+
contentRange: data.ContentRange,
|
1055
|
+
acceptRanges: data.AcceptRanges,
|
1056
|
+
etag: data.ETag,
|
1057
|
+
lastModified: data.LastModified
|
1058
|
+
}
|
1059
|
+
};
|
1060
|
+
} catch (error) {
|
1061
|
+
(_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "getS3VideoStream error", {
|
1062
|
+
Bucket: this.bucket,
|
1063
|
+
filePath: normalizedKey,
|
1064
|
+
Range,
|
1065
|
+
error
|
1066
|
+
});
|
1067
|
+
return null;
|
1068
|
+
}
|
1069
|
+
});
|
1070
|
+
}
|
1071
|
+
getStreamZipFileCtr(_0) {
|
1072
|
+
return __async(this, arguments, function* ({
|
1073
|
+
filePath,
|
1074
|
+
filename: _filename,
|
1075
|
+
compressionLevel = 5
|
1076
|
+
}) {
|
1077
|
+
return (req, res, next) => __async(this, null, function* () {
|
1078
|
+
var _a2, _b, _c, _d, _e;
|
1079
|
+
const filePaths = [].concat(filePath).map((filePath2) => {
|
1080
|
+
return decodeURIComponent((filePath2 == null ? void 0 : filePath2.replace(/^\//, "").replace(/\/$/, "")) || "");
|
1081
|
+
}).filter((v) => v && v !== "/");
|
1082
|
+
if (!filePaths.length) {
|
1083
|
+
throw new Error("No file keys provided");
|
1084
|
+
}
|
1085
|
+
let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
|
1086
|
+
filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
|
1087
|
+
const abort = new AbortController();
|
1088
|
+
const onClose = () => {
|
1089
|
+
abort.abort();
|
1090
|
+
};
|
1091
|
+
req.once("close", onClose);
|
1092
|
+
try {
|
1093
|
+
(_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
|
1094
|
+
const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
|
1095
|
+
var _a3, _b2, _c2;
|
1096
|
+
try {
|
1097
|
+
if (abort.signal.aborted) return null;
|
1098
|
+
const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
|
1099
|
+
if (!stream) {
|
1100
|
+
(_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
|
1101
|
+
return null;
|
1102
|
+
}
|
1103
|
+
const chunks = [];
|
1104
|
+
try {
|
1105
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
|
1106
|
+
const chunk = temp.value;
|
1107
|
+
if (abort.signal.aborted) {
|
1108
|
+
stream.destroy();
|
1109
|
+
return null;
|
1110
|
+
}
|
1111
|
+
chunks.push(Buffer2.from(chunk));
|
1112
|
+
}
|
1113
|
+
} catch (temp) {
|
1114
|
+
error = [temp];
|
1115
|
+
} finally {
|
1116
|
+
try {
|
1117
|
+
more && (temp = iter.return) && (yield temp.call(iter));
|
1118
|
+
} finally {
|
1119
|
+
if (error)
|
1120
|
+
throw error[0];
|
1121
|
+
}
|
1122
|
+
}
|
1123
|
+
const buffer = Buffer2.concat(chunks);
|
1124
|
+
const fileName = filePath2.split("/").pop() || filePath2;
|
1125
|
+
(_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
|
1126
|
+
filePath: filePath2,
|
1127
|
+
sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
|
1128
|
+
});
|
1129
|
+
return { buffer, name: fileName, path: filePath2 };
|
1130
|
+
} catch (error2) {
|
1131
|
+
(_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
|
1132
|
+
return null;
|
1133
|
+
}
|
1134
|
+
}));
|
1135
|
+
const fileBuffers = (yield Promise.all(downloadPromises)).filter(Boolean);
|
1136
|
+
if (abort.signal.aborted || fileBuffers.length === 0) {
|
1137
|
+
req.off("close", onClose);
|
1138
|
+
if (fileBuffers.length === 0) {
|
1139
|
+
next(new Error("No files available to zip"));
|
1140
|
+
}
|
1141
|
+
return;
|
1142
|
+
}
|
1143
|
+
(_b = this.logger) == null ? void 0 : _b.info(this.reqId, "All files downloaded, measuring zip size...", {
|
1144
|
+
fileCount: fileBuffers.length,
|
1145
|
+
totalSizeMB: (fileBuffers.reduce((sum, f) => sum + f.buffer.length, 0) / (1024 * 1024)).toFixed(2)
|
1146
|
+
});
|
1147
|
+
const measureArchive = archiver("zip", { zlib: { level: compressionLevel } });
|
1148
|
+
let actualZipSize = 0;
|
1149
|
+
measureArchive.on("data", (chunk) => {
|
1150
|
+
actualZipSize += chunk.length;
|
1151
|
+
});
|
1152
|
+
for (const file of fileBuffers) {
|
1153
|
+
if (abort.signal.aborted) break;
|
1154
|
+
measureArchive.append(file.buffer, { name: file.name });
|
1155
|
+
}
|
1156
|
+
yield measureArchive.finalize();
|
1157
|
+
if (abort.signal.aborted) {
|
1158
|
+
req.off("close", onClose);
|
1159
|
+
return;
|
1160
|
+
}
|
1161
|
+
(_c = this.logger) == null ? void 0 : _c.info(this.reqId, "Zip size calculated", {
|
1162
|
+
actualZipSize,
|
1163
|
+
sizeMB: (actualZipSize / (1024 * 1024)).toFixed(2)
|
1164
|
+
});
|
1165
|
+
const actualArchive = archiver("zip", { zlib: { level: compressionLevel } });
|
1166
|
+
res.setHeader("Content-Type", "application/zip");
|
1167
|
+
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
1168
|
+
res.setHeader("Content-Length", String(actualZipSize));
|
1169
|
+
res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Disposition, Content-Length");
|
1170
|
+
actualArchive.on("error", (err) => {
|
1171
|
+
var _a3;
|
1172
|
+
(_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Archive error", { error: err });
|
1173
|
+
abort.abort();
|
1174
|
+
if (!res.headersSent) {
|
1175
|
+
next(err);
|
1176
|
+
}
|
1177
|
+
});
|
1178
|
+
actualArchive.pipe(res);
|
1179
|
+
for (const file of fileBuffers) {
|
1180
|
+
if (abort.signal.aborted) break;
|
1181
|
+
actualArchive.append(file.buffer, { name: file.name });
|
1182
|
+
}
|
1183
|
+
yield actualArchive.finalize();
|
1184
|
+
(_d = this.logger) == null ? void 0 : _d.info(this.reqId, "Zip download completed", {
|
1185
|
+
fileCount: fileBuffers.length,
|
1186
|
+
totalSize: actualZipSize
|
1187
|
+
});
|
1188
|
+
req.off("close", onClose);
|
1189
|
+
} catch (error) {
|
1190
|
+
abort.abort();
|
1191
|
+
const isBenignError = (error == null ? void 0 : error.code) === "ERR_STREAM_PREMATURE_CLOSE" || (error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.code) === "ECONNRESET";
|
1192
|
+
if (isBenignError) {
|
1193
|
+
return;
|
1194
|
+
}
|
1195
|
+
if (!res.headersSent) {
|
1196
|
+
(_e = this.logger) == null ? void 0 : _e.error(this.reqId, "Failed to create zip archive", { error });
|
1197
|
+
next(error);
|
1198
|
+
} else if (!res.writableEnded) {
|
1199
|
+
try {
|
1200
|
+
res.end();
|
1201
|
+
} catch (e) {
|
1202
|
+
}
|
1203
|
+
}
|
1204
|
+
} finally {
|
1205
|
+
req.off("close", onClose);
|
1206
|
+
}
|
1207
|
+
});
|
1208
|
+
});
|
1209
|
+
}
|
1210
|
+
getStreamFileCtrl(_0) {
|
1211
|
+
return __async(this, arguments, function* ({ filePath, filename }) {
|
1212
|
+
return (req, res, next) => __async(this, null, function* () {
|
1213
|
+
var _a2, _b;
|
1214
|
+
const abort = new AbortController();
|
1215
|
+
let stream = null;
|
1216
|
+
const onClose = () => {
|
1217
|
+
var _a3;
|
1218
|
+
abort.abort();
|
1219
|
+
(_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
|
1220
|
+
};
|
1221
|
+
req.once("close", onClose);
|
1222
|
+
const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
|
1223
|
+
if (!normalizedKey || normalizedKey === "/") {
|
1224
|
+
throw new Error("No file key provided");
|
1225
|
+
}
|
1226
|
+
try {
|
1227
|
+
const isExists = yield this.fileExists(normalizedKey);
|
1228
|
+
if (!isExists) {
|
1229
|
+
req.off("close", onClose);
|
1230
|
+
next(Error(`File not found: "${normalizedKey}"`));
|
1231
|
+
return;
|
1232
|
+
}
|
1233
|
+
stream = yield this.streamObjectFile(normalizedKey, {
|
1234
|
+
abortSignal: abort.signal,
|
1235
|
+
checkFileExists: false
|
1236
|
+
});
|
1237
|
+
if (!stream) {
|
1238
|
+
req.off("close", onClose);
|
1239
|
+
next(Error(`Failed to get file stream: "${normalizedKey}"`));
|
1240
|
+
return;
|
1241
|
+
}
|
1242
|
+
const fileInfo = yield this.fileInfo(normalizedKey);
|
1243
|
+
const fileName = filename || normalizedKey.split("/").pop() || "download";
|
1244
|
+
res.setHeader("Content-Type", fileInfo.ContentType || "application/octet-stream");
|
1245
|
+
res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
|
1246
|
+
if (fileInfo.ContentLength) {
|
1247
|
+
res.setHeader("Content-Length", String(fileInfo.ContentLength));
|
1248
|
+
}
|
1249
|
+
stream.on("error", (err) => {
|
1250
|
+
var _a3, _b2;
|
1251
|
+
(_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { filePath: normalizedKey, error: err });
|
1252
|
+
abort.abort();
|
1253
|
+
(_b2 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _b2.call(stream);
|
1254
|
+
});
|
1255
|
+
res.once("close", () => {
|
1256
|
+
var _a3;
|
1257
|
+
(_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
|
1258
|
+
req.off("close", onClose);
|
1259
|
+
});
|
1260
|
+
yield pump(stream, res);
|
1261
|
+
req.off("close", onClose);
|
1262
|
+
} catch (error) {
|
1263
|
+
abort.abort();
|
1264
|
+
if (stream) {
|
1265
|
+
(_a2 = stream.destroy) == null ? void 0 : _a2.call(stream);
|
1266
|
+
}
|
1267
|
+
const isBenignStreamError = (error == null ? void 0 : error.code) === "ERR_STREAM_PREMATURE_CLOSE" || (error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.code) === "ECONNRESET";
|
1268
|
+
if (isBenignStreamError) {
|
1269
|
+
return;
|
1270
|
+
}
|
1271
|
+
(_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Failed to stream file", { filePath: normalizedKey, error });
|
1272
|
+
if (!res.headersSent) {
|
1273
|
+
next(error);
|
1274
|
+
} else if (!res.writableEnded) {
|
1275
|
+
try {
|
1276
|
+
res.end();
|
1277
|
+
} catch (e) {
|
1278
|
+
}
|
1279
|
+
}
|
1280
|
+
} finally {
|
1281
|
+
req.off("close", onClose);
|
1282
|
+
}
|
1283
|
+
});
|
1284
|
+
});
|
1285
|
+
}
|
1286
|
+
getStreamVideoFileCtrl(_0) {
|
1287
|
+
return __async(this, arguments, function* ({
|
1288
|
+
fileKey,
|
1289
|
+
allowedWhitelist,
|
1290
|
+
contentType = "video/mp4",
|
1291
|
+
streamTimeoutMS = 3e4,
|
1292
|
+
bufferMB = 5
|
1293
|
+
}) {
|
1294
|
+
return (req, res, next) => __async(this, null, function* () {
|
1295
|
+
var _a2, _b, _c, _d, _e, _f;
|
1296
|
+
const normalizedKey = decodeURIComponent((fileKey == null ? void 0 : fileKey.replace(/^\//, "").replace(/\/$/, "")) || "");
|
1297
|
+
if (!normalizedKey || normalizedKey === "/") {
|
1298
|
+
throw new Error("No file key provided");
|
1299
|
+
}
|
1300
|
+
const isExists = yield this.fileExists(normalizedKey);
|
1301
|
+
const fileSize = yield this.sizeOf(normalizedKey);
|
1302
|
+
let Range;
|
1303
|
+
if (!isExists) {
|
1304
|
+
next(Error(`File does not exist: "${normalizedKey}"`));
|
1305
|
+
return;
|
1306
|
+
}
|
1307
|
+
try {
|
1308
|
+
if (req.method === "HEAD") {
|
1309
|
+
res.setHeader("Content-Type", contentType);
|
1310
|
+
res.setHeader("Accept-Ranges", "bytes");
|
1311
|
+
if (fileSize) res.setHeader("Content-Length", String(fileSize));
|
1312
|
+
return res.status(200).end();
|
1313
|
+
}
|
1314
|
+
const bufferSize = bufferMB;
|
1315
|
+
const CHUNK_SIZE = __pow(10, 6) * bufferSize;
|
1316
|
+
const rangeValues = parseRangeHeader(req.headers.range, fileSize, CHUNK_SIZE);
|
1317
|
+
let [start, end] = rangeValues || [];
|
1318
|
+
if (!rangeValues || start < 0 || start >= fileSize || end < 0 || end >= fileSize || start > end) {
|
1319
|
+
res.status(416).send("Requested Range Not Satisfiable");
|
1320
|
+
return;
|
1321
|
+
}
|
1322
|
+
res.statusCode = 206;
|
1323
|
+
const chunkLength = end - start + 1;
|
1324
|
+
res.setHeader("Content-Length", chunkLength);
|
1325
|
+
res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`);
|
1326
|
+
res.setHeader("Accept-Ranges", "bytes");
|
1327
|
+
res.setHeader("Content-Type", "video/mp4");
|
1328
|
+
Range = `bytes=${start}-${end}`;
|
1329
|
+
} catch (error) {
|
1330
|
+
next(error);
|
1331
|
+
return;
|
1332
|
+
}
|
1333
|
+
const abort = new AbortController();
|
1334
|
+
const onClose = () => abort.abort();
|
1335
|
+
req.once("close", onClose);
|
1336
|
+
try {
|
1337
|
+
const result = yield this.streamVideoFile({
|
1338
|
+
filePath: normalizedKey,
|
1339
|
+
Range,
|
1340
|
+
abortSignal: abort.signal
|
1341
|
+
});
|
1342
|
+
const { body, meta } = result;
|
1343
|
+
const origin = Array.isArray(allowedWhitelist) ? allowedWhitelist.includes((_a2 = req.headers.origin) != null ? _a2 : "") ? req.headers.origin : void 0 : allowedWhitelist;
|
1344
|
+
if (origin) {
|
1345
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
1346
|
+
res.setHeader("Vary", "Origin");
|
1347
|
+
}
|
1348
|
+
const finalContentType = contentType.startsWith("video/") ? contentType : `video/${contentType}`;
|
1349
|
+
res.setHeader("Content-Type", (_b = meta.contentType) != null ? _b : finalContentType);
|
1350
|
+
res.setHeader("Accept-Ranges", (_c = meta.acceptRanges) != null ? _c : "bytes");
|
1351
|
+
if (Range && meta.contentRange) {
|
1352
|
+
res.status(206);
|
1353
|
+
res.setHeader("Content-Range", meta.contentRange);
|
1354
|
+
if (typeof meta.contentLength === "number") {
|
1355
|
+
res.setHeader("Content-Length", String(meta.contentLength));
|
1356
|
+
}
|
1357
|
+
} else if (fileSize) {
|
1358
|
+
res.setHeader("Content-Length", String(fileSize));
|
1359
|
+
}
|
1360
|
+
if (meta.etag) res.setHeader("ETag", meta.etag);
|
1361
|
+
if (meta.lastModified) res.setHeader("Last-Modified", meta.lastModified.toUTCString());
|
1362
|
+
const timeout = setTimeout(() => {
|
1363
|
+
abort.abort();
|
1364
|
+
if (!res.headersSent) res.status(504);
|
1365
|
+
res.end();
|
1366
|
+
}, streamTimeoutMS);
|
1367
|
+
res.once("close", () => {
|
1368
|
+
var _a3;
|
1369
|
+
clearTimeout(timeout);
|
1370
|
+
(_a3 = body.destroy) == null ? void 0 : _a3.call(body);
|
1371
|
+
req.off("close", onClose);
|
1372
|
+
});
|
1373
|
+
yield pump(body, res);
|
1374
|
+
clearTimeout(timeout);
|
1375
|
+
} catch (error) {
|
1376
|
+
const isBenignStreamError = (error == null ? void 0 : error.code) === "ERR_STREAM_PREMATURE_CLOSE" || (error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.code) === "ECONNRESET";
|
1377
|
+
if (isBenignStreamError) {
|
1378
|
+
return;
|
1379
|
+
}
|
1380
|
+
if (!res.headersSent) {
|
1381
|
+
(_f = this.logger) == null ? void 0 : _f.warn(req.id, "caught exception in stream controller", {
|
1382
|
+
error: (_d = error == null ? void 0 : error.message) != null ? _d : String(error),
|
1383
|
+
key: fileKey,
|
1384
|
+
url: req.originalUrl,
|
1385
|
+
userId: (_e = req.user) == null ? void 0 : _e._id
|
1386
|
+
});
|
1387
|
+
next(error);
|
1388
|
+
return;
|
1389
|
+
}
|
1390
|
+
if (!res.writableEnded) {
|
1391
|
+
try {
|
1392
|
+
res.end();
|
1393
|
+
} catch (e) {
|
1394
|
+
}
|
1395
|
+
}
|
1396
|
+
return;
|
1397
|
+
}
|
1398
|
+
});
|
1399
|
+
});
|
1400
|
+
}
|
1401
|
+
static fileFilter(types, fileExt) {
|
1402
|
+
const fileTypesChecker = (fileExt == null ? void 0 : fileExt.length) ? new RegExp(`\\.(${fileExt.join("|")})$`, "i") : void 0;
|
1403
|
+
return function(_req, file, cb) {
|
1404
|
+
const fileExtension = path.extname(file.originalname).substring(1);
|
1405
|
+
const extname = fileTypesChecker ? fileTypesChecker.test(`.${fileExtension}`) : true;
|
1406
|
+
const mimeType = (types == null ? void 0 : types.length) ? types.some((type) => file.mimetype.startsWith(`${type}/`)) : true;
|
1407
|
+
if (mimeType && extname) {
|
1408
|
+
return cb(null, true);
|
1409
|
+
}
|
1410
|
+
const errorMsg = !extname ? `Upload File Ext Error: Allowed extensions: [${fileExt == null ? void 0 : fileExt.join(", ")}]. Got: ${fileExtension}` : `Upload File Type Error: Allowed types: [${types == null ? void 0 : types.join(", ")}]. Got: ${file.mimetype}`;
|
1411
|
+
return cb(new Error(errorMsg));
|
1412
|
+
};
|
1413
|
+
}
|
1414
|
+
getFileSize(maxFileSize) {
|
1415
|
+
var _a2;
|
1416
|
+
const fileSizeUnitValue = maxFileSize != null ? maxFileSize : this.maxUploadFileSizeRestriction;
|
1417
|
+
const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : bytes(fileSizeUnitValue);
|
1418
|
+
if (!fileSize) {
|
1419
|
+
(_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "Failed to convert fileSize restriction, proceeding without limit", {
|
1420
|
+
maxFileSize,
|
1421
|
+
maxUploadFileSizeRestriction: this.maxUploadFileSizeRestriction
|
1422
|
+
});
|
1423
|
+
return void 0;
|
1424
|
+
}
|
1425
|
+
return fileSize;
|
1426
|
+
}
|
1427
|
+
getUploadFileMW(directory, {
|
1428
|
+
acl = "private" /* private */,
|
1429
|
+
maxFileSize,
|
1430
|
+
filename: _filename,
|
1431
|
+
fileType = [],
|
1432
|
+
fileExt = [],
|
1433
|
+
metadata: customMetadata
|
1434
|
+
} = {}) {
|
1435
|
+
let normalizedPath = decodeURIComponent((directory == null ? void 0 : directory.replace(/^\//, "").replace(/\/$/, "")) || "");
|
1436
|
+
if (directory !== "/" && directory !== "" && directory !== void 0) normalizedPath += "/";
|
1437
|
+
else normalizedPath = "";
|
1438
|
+
const fileSize = this.getFileSize(maxFileSize);
|
1439
|
+
const fileTypes = [].concat(fileType);
|
1440
|
+
const fileExts = [].concat(fileExt);
|
1441
|
+
const fileFilter = (fileTypes == null ? void 0 : fileTypes.length) || (fileExts == null ? void 0 : fileExts.length) ? _S3BucketUtil.fileFilter(fileTypes, fileExts) : void 0;
|
1442
|
+
return multer({
|
1443
|
+
fileFilter,
|
1444
|
+
limits: __spreadValues({}, fileSize && { fileSize }),
|
1445
|
+
storage: multerS3({
|
1446
|
+
acl,
|
1447
|
+
s3: this.s3Client,
|
1448
|
+
bucket: this.bucket,
|
1449
|
+
contentType: multerS3.AUTO_CONTENT_TYPE,
|
1450
|
+
metadata: (req, file, cb) => __async(this, null, function* () {
|
1451
|
+
const baseMetadata = __spreadProps(__spreadValues({}, file), { directory: normalizedPath });
|
1452
|
+
if (customMetadata) {
|
1453
|
+
const additionalMetadata = typeof customMetadata === "function" ? yield customMetadata(req, file) : customMetadata;
|
1454
|
+
Object.assign(baseMetadata, additionalMetadata);
|
1455
|
+
}
|
1456
|
+
cb(null, baseMetadata);
|
1457
|
+
}),
|
1458
|
+
key: (req, file, cb) => __async(this, null, function* () {
|
1459
|
+
let filename;
|
1460
|
+
if (typeof _filename === "function") {
|
1461
|
+
filename = yield _filename(req, file);
|
1462
|
+
} else if (_filename) {
|
1463
|
+
filename = _filename;
|
1464
|
+
} else {
|
1465
|
+
filename = file.originalname;
|
1466
|
+
}
|
1467
|
+
filename = decodeURIComponent(filename);
|
1468
|
+
const key = `${normalizedPath}${filename}`;
|
1469
|
+
cb(null, key);
|
1470
|
+
})
|
1471
|
+
})
|
1472
|
+
});
|
1473
|
+
}
|
1474
|
+
/**
|
1475
|
+
* Middleware for uploading a single file
|
1476
|
+
* Adds the uploaded file info to req.s3File
|
1477
|
+
*/
|
1478
|
+
uploadSingleFile(fieldName, directory, options = {}) {
|
1479
|
+
const upload = this.getUploadFileMW(directory, options);
|
1480
|
+
return (req, res, next) => {
|
1481
|
+
const mw = upload.single(fieldName);
|
1482
|
+
mw(req, res, (err) => {
|
1483
|
+
var _a2, _b;
|
1484
|
+
if (err) {
|
1485
|
+
(_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Single file upload error", { fieldName, error: err.message });
|
1486
|
+
return next(err);
|
1487
|
+
}
|
1488
|
+
if (req.file) {
|
1489
|
+
req.s3File = req.file;
|
1490
|
+
(_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Single file uploaded successfully", {
|
1491
|
+
fieldName,
|
1492
|
+
key: req.s3File.key,
|
1493
|
+
location: req.s3File.location,
|
1494
|
+
size: req.s3File.size
|
1495
|
+
});
|
1496
|
+
}
|
1497
|
+
next();
|
1498
|
+
});
|
1499
|
+
};
|
1500
|
+
}
|
1501
|
+
/**
|
1502
|
+
* Middleware for uploading multiple files with the same field name
|
1503
|
+
* Adds the uploaded files info to req.s3Files
|
1504
|
+
*/
|
1505
|
+
uploadMultipleFiles(fieldName, directory, options = {}) {
|
1506
|
+
const upload = this.getUploadFileMW(directory, options);
|
1507
|
+
return (req, res, next) => {
|
1508
|
+
const mw = upload.array(fieldName, options.maxFilesCount || void 0);
|
1509
|
+
mw(req, res, (err) => {
|
1510
|
+
var _a2, _b;
|
1511
|
+
if (err) {
|
1512
|
+
(_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Multiple files upload error", { fieldName, error: err.message });
|
1513
|
+
return next(err);
|
1514
|
+
}
|
1515
|
+
if (Array.isArray(req.files)) {
|
1516
|
+
req.s3Files = req.files;
|
1517
|
+
(_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Multiple files uploaded successfully", {
|
1518
|
+
fieldName,
|
1519
|
+
count: req.s3Files.length,
|
1520
|
+
keys: req.s3Files.map((f) => f.key)
|
1521
|
+
});
|
1522
|
+
}
|
1523
|
+
next();
|
1524
|
+
});
|
1525
|
+
};
|
1526
|
+
}
|
1527
|
+
/**
|
1528
|
+
* Middleware for uploading multiple files with different field names
|
1529
|
+
* Adds the uploaded files info to req.s3FilesByField
|
1530
|
+
*/
|
1531
|
+
uploadFieldsFiles(fields) {
|
1532
|
+
const fieldConfigs = fields.map((field) => {
|
1533
|
+
const upload = this.getUploadFileMW(field.directory, field.options || {});
|
1534
|
+
return {
|
1535
|
+
name: field.name,
|
1536
|
+
maxCount: field.maxCount || 1,
|
1537
|
+
upload,
|
1538
|
+
directory: field.directory
|
1539
|
+
};
|
1540
|
+
});
|
1541
|
+
return (req, res, next) => __async(this, null, function* () {
|
1542
|
+
const multerFields = fieldConfigs.map((f) => ({ name: f.name, maxCount: f.maxCount }));
|
1543
|
+
const upload = this.getUploadFileMW(fieldConfigs[0].directory);
|
1544
|
+
const mw = upload.fields(multerFields);
|
1545
|
+
mw(req, res, (err) => {
|
1546
|
+
var _a2, _b;
|
1547
|
+
if (err) {
|
1548
|
+
(_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Fields upload error", { error: err.message });
|
1549
|
+
return next(err);
|
1550
|
+
}
|
1551
|
+
if (req.files && typeof req.files === "object" && !Array.isArray(req.files)) {
|
1552
|
+
req.s3FilesByField = req.files;
|
1553
|
+
const uploadSummary = Object.entries(req.s3FilesByField).map(([field, files]) => ({
|
1554
|
+
field,
|
1555
|
+
count: files.length,
|
1556
|
+
keys: files.map((f) => f.key)
|
1557
|
+
}));
|
1558
|
+
(_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Fields uploaded successfully", { uploadSummary });
|
1559
|
+
}
|
1560
|
+
next();
|
1561
|
+
});
|
1562
|
+
});
|
1563
|
+
}
|
1564
|
+
/**
|
1565
|
+
* Middleware for uploading any files (mixed field names)
|
1566
|
+
* Adds the uploaded files info to req.s3AllFiles
|
1567
|
+
*/
|
1568
|
+
uploadAnyFiles(directory, maxCount, options = {}) {
|
1569
|
+
const upload = this.getUploadFileMW(directory, options);
|
1570
|
+
return (req, res, next) => {
|
1571
|
+
const anyUpload = maxCount ? upload.any() : upload.any();
|
1572
|
+
anyUpload(req, res, (err) => {
|
1573
|
+
var _a2, _b;
|
1574
|
+
if (err) {
|
1575
|
+
(_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Any files upload error", { error: err.message });
|
1576
|
+
return next(err);
|
1577
|
+
}
|
1578
|
+
if (req.files && Array.isArray(req.files)) {
|
1579
|
+
req.s3AllFiles = req.files;
|
1580
|
+
if (maxCount && req.s3AllFiles.length > maxCount) {
|
1581
|
+
return next(new Error(`Too many files uploaded. Maximum is ${maxCount}`));
|
1582
|
+
}
|
1583
|
+
(_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Any files uploaded successfully", {
|
1584
|
+
count: req.s3AllFiles.length,
|
1585
|
+
keys: req.s3AllFiles.map((f) => f.key)
|
1586
|
+
});
|
1587
|
+
}
|
1588
|
+
next();
|
1589
|
+
});
|
1590
|
+
};
|
1591
|
+
}
|
1592
|
+
};
|
1593
|
+
|
1594
|
+
// src/aws/sns.ts
|
1595
|
+
import { SNS } from "@aws-sdk/client-sns";
|
1596
|
+
var SNSUtil = class {
|
1597
|
+
constructor({
|
1598
|
+
accessKeyId = AWSConfigSharingUtil.accessKeyId,
|
1599
|
+
secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
|
1600
|
+
endpoint = AWSConfigSharingUtil.endpoint,
|
1601
|
+
region = AWSConfigSharingUtil.region,
|
1602
|
+
topicArn,
|
1603
|
+
debug = false
|
1604
|
+
}) {
|
1605
|
+
__publicField(this, "sns");
|
1606
|
+
__publicField(this, "topicArn");
|
1607
|
+
const credentials = { accessKeyId, secretAccessKey };
|
1608
|
+
const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
|
1609
|
+
if (debug) {
|
1610
|
+
console.log("LambdaUtil client options", options);
|
1611
|
+
}
|
1612
|
+
this.topicArn = topicArn;
|
1613
|
+
this.sns = new SNS(__spreadValues(__spreadValues(__spreadValues({}, credentials && { credentials }), endpoint && { endpoint }), region && { region }));
|
1614
|
+
}
|
1615
|
+
publishTopicMessage(message) {
|
1616
|
+
return __async(this, null, function* () {
|
1617
|
+
this.sns.publish({
|
1618
|
+
Message: typeof message === "string" ? message : JSON.stringify(message),
|
1619
|
+
TopicArn: this.topicArn
|
1620
|
+
});
|
1621
|
+
});
|
1622
|
+
}
|
1623
|
+
};
|
1624
|
+
export {
|
1625
|
+
ACLs,
|
1626
|
+
AWSConfigSharingUtil,
|
1627
|
+
IAMUtil,
|
1628
|
+
LambdaUtil,
|
1629
|
+
S3BucketUtil,
|
1630
|
+
SNSUtil
|
1631
|
+
};
|