@webiny/api-file-manager-s3 6.4.12-beta.5 → 6.5.0-beta.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.
Files changed (138) hide show
  1. package/assetDelivery/abstractions.d.ts +2 -0
  2. package/assetDelivery/abstractions.js.map +1 -1
  3. package/assetDelivery/assetDeliveryConfig.d.ts +1 -1
  4. package/assetDelivery/createAssetDelivery.js +1 -1
  5. package/assetDelivery/createAssetDelivery.js.map +1 -1
  6. package/assetDelivery/feature.js +2 -1
  7. package/assetDelivery/feature.js.map +1 -1
  8. package/assetDelivery/index.d.ts +1 -2
  9. package/assetDelivery/index.js +1 -2
  10. package/assetDelivery/s3/S3AssetResolver.d.ts +10 -9
  11. package/assetDelivery/s3/S3AssetResolver.js +10 -8
  12. package/assetDelivery/s3/S3AssetResolver.js.map +1 -1
  13. package/assetDelivery/s3/S3ContentsReader.d.ts +6 -5
  14. package/assetDelivery/s3/S3ContentsReader.js +3 -0
  15. package/assetDelivery/s3/S3ContentsReader.js.map +1 -1
  16. package/assetDelivery/s3/S3ErrorAssetReply.d.ts +3 -2
  17. package/assetDelivery/s3/S3ErrorAssetReply.js +4 -1
  18. package/assetDelivery/s3/S3ErrorAssetReply.js.map +1 -1
  19. package/assetDelivery/s3/S3OutputStrategy.d.ts +8 -6
  20. package/assetDelivery/s3/S3OutputStrategy.js +6 -5
  21. package/assetDelivery/s3/S3OutputStrategy.js.map +1 -1
  22. package/assetDelivery/s3/S3RedirectAssetReply.d.ts +1 -1
  23. package/assetDelivery/s3/S3RedirectAssetReply.js +1 -1
  24. package/assetDelivery/s3/S3RedirectAssetReply.js.map +1 -1
  25. package/assetDelivery/s3/SharpTransform.d.ts +13 -5
  26. package/assetDelivery/s3/SharpTransform.js +98 -74
  27. package/assetDelivery/s3/SharpTransform.js.map +1 -1
  28. package/assetDelivery/threatDetection/createThreatDetectionEventHandler.d.ts +1 -1
  29. package/assetDelivery/threatDetection/createThreatDetectionEventHandler.js +4 -3
  30. package/assetDelivery/threatDetection/createThreatDetectionEventHandler.js.map +1 -1
  31. package/assetDelivery/threatDetection/processThreatScanResult.js +10 -8
  32. package/assetDelivery/threatDetection/processThreatScanResult.js.map +1 -1
  33. package/assetDelivery/types.d.ts +3 -0
  34. package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.d.ts +8 -0
  35. package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.js +51 -0
  36. package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.js.map +1 -0
  37. package/features/CompleteMultiPartUpload/feature.d.ts +4 -0
  38. package/features/CompleteMultiPartUpload/feature.js +11 -0
  39. package/features/CompleteMultiPartUpload/feature.js.map +1 -0
  40. package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.d.ts +9 -0
  41. package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.js +40 -0
  42. package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.js.map +1 -0
  43. package/features/CreateMultiPartUpload/feature.d.ts +4 -0
  44. package/features/CreateMultiPartUpload/feature.js +11 -0
  45. package/features/CreateMultiPartUpload/feature.js.map +1 -0
  46. package/features/DeleteFileFromBucket/DeleteS3FolderTask.d.ts +1 -1
  47. package/features/ExtractMetadata/ExtractMetadataHandler.d.ts +1 -11
  48. package/features/ExtractMetadata/ExtractMetadataHandler.js +1 -25
  49. package/features/ExtractMetadata/ExtractMetadataTask.d.ts +15 -17
  50. package/features/ExtractMetadata/ExtractMetadataTask.js +8 -10
  51. package/features/ExtractMetadata/ExtractMetadataTask.js.map +1 -1
  52. package/features/FlushCache/InvalidateCacheTask.d.ts +2 -2
  53. package/features/FlushCache/InvalidateCacheTask.js +1 -1
  54. package/features/FlushCache/InvalidateCacheTask.js.map +1 -1
  55. package/features/GetFileContentsById/GetFileContentsByIdUseCase.d.ts +3 -3
  56. package/features/GetFileContentsById/GetFileContentsByIdUseCase.js +4 -5
  57. package/features/GetFileContentsById/GetFileContentsByIdUseCase.js.map +1 -1
  58. package/features/GetUploadPayload/GetUploadPayloadUseCase.d.ts +14 -0
  59. package/features/GetUploadPayload/GetUploadPayloadUseCase.js +54 -0
  60. package/features/GetUploadPayload/GetUploadPayloadUseCase.js.map +1 -0
  61. package/features/{WriteFileMetadata → GetUploadPayload}/feature.d.ts +1 -1
  62. package/features/GetUploadPayload/feature.js +11 -0
  63. package/features/GetUploadPayload/feature.js.map +1 -0
  64. package/index.d.ts +2 -2
  65. package/index.js +8 -6
  66. package/index.js.map +1 -1
  67. package/package.json +18 -24
  68. package/types.d.ts +2 -20
  69. package/utils/uploadFileToS3.d.ts +1 -1
  70. package/assetDelivery/s3/S3StreamAssetReply.d.ts +0 -5
  71. package/assetDelivery/s3/S3StreamAssetReply.js +0 -17
  72. package/assetDelivery/s3/S3StreamAssetReply.js.map +0 -1
  73. package/assetDelivery/s3/transformation/AssetKeyGenerator.d.ts +0 -8
  74. package/assetDelivery/s3/transformation/AssetKeyGenerator.js +0 -21
  75. package/assetDelivery/s3/transformation/AssetKeyGenerator.js.map +0 -1
  76. package/assetDelivery/s3/transformation/CallableContentsReader.d.ts +0 -10
  77. package/assetDelivery/s3/transformation/CallableContentsReader.js +0 -11
  78. package/assetDelivery/s3/transformation/CallableContentsReader.js.map +0 -1
  79. package/assetDelivery/s3/transformation/WidthCollection.d.ts +0 -7
  80. package/assetDelivery/s3/transformation/WidthCollection.js +0 -19
  81. package/assetDelivery/s3/transformation/WidthCollection.js.map +0 -1
  82. package/assetDelivery/s3/transformation/utils.d.ts +0 -9
  83. package/assetDelivery/s3/transformation/utils.js +0 -31
  84. package/assetDelivery/s3/transformation/utils.js.map +0 -1
  85. package/assetDelivery/threatDetection/ObjectKey.d.ts +0 -7
  86. package/assetDelivery/threatDetection/ObjectKey.js +0 -18
  87. package/assetDelivery/threatDetection/ObjectKey.js.map +0 -1
  88. package/features/ExtractMetadata/ExtractMetadataHandler.js.map +0 -1
  89. package/features/WriteFileMetadata/MetadataReader.d.ts +0 -14
  90. package/features/WriteFileMetadata/MetadataReader.js +0 -13
  91. package/features/WriteFileMetadata/MetadataReader.js.map +0 -1
  92. package/features/WriteFileMetadata/MetadataWriter.d.ts +0 -10
  93. package/features/WriteFileMetadata/MetadataWriter.js +0 -26
  94. package/features/WriteFileMetadata/MetadataWriter.js.map +0 -1
  95. package/features/WriteFileMetadata/WriteMetadataAfterBatchCreateHandler.d.ts +0 -12
  96. package/features/WriteFileMetadata/WriteMetadataAfterBatchCreateHandler.js +0 -23
  97. package/features/WriteFileMetadata/WriteMetadataAfterBatchCreateHandler.js.map +0 -1
  98. package/features/WriteFileMetadata/WriteMetadataAfterCreateHandler.d.ts +0 -12
  99. package/features/WriteFileMetadata/WriteMetadataAfterCreateHandler.js +0 -25
  100. package/features/WriteFileMetadata/WriteMetadataAfterCreateHandler.js.map +0 -1
  101. package/features/WriteFileMetadata/feature.js +0 -13
  102. package/features/WriteFileMetadata/feature.js.map +0 -1
  103. package/graphql/checkPermissions.d.ts +0 -5
  104. package/graphql/checkPermissions.js +0 -22
  105. package/graphql/checkPermissions.js.map +0 -1
  106. package/graphql/schema.d.ts +0 -1
  107. package/graphql/schema.js +0 -203
  108. package/graphql/schema.js.map +0 -1
  109. package/multiPartUpload/CompleteMultiPartUploadUseCase.d.ts +0 -15
  110. package/multiPartUpload/CompleteMultiPartUploadUseCase.js +0 -55
  111. package/multiPartUpload/CompleteMultiPartUploadUseCase.js.map +0 -1
  112. package/multiPartUpload/CreateMultiPartUploadUseCase.d.ts +0 -20
  113. package/multiPartUpload/CreateMultiPartUploadUseCase.js +0 -34
  114. package/multiPartUpload/CreateMultiPartUploadUseCase.js.map +0 -1
  115. package/utils/FileExtension.d.ts +0 -6
  116. package/utils/FileExtension.js +0 -16
  117. package/utils/FileExtension.js.map +0 -1
  118. package/utils/FileKey.d.ts +0 -11
  119. package/utils/FileKey.js +0 -33
  120. package/utils/FileKey.js.map +0 -1
  121. package/utils/FileKey.test.d.ts +0 -1
  122. package/utils/FileKey.test.js +0 -59
  123. package/utils/FileKey.test.js.map +0 -1
  124. package/utils/FileNormalizer.d.ts +0 -18
  125. package/utils/FileNormalizer.js +0 -41
  126. package/utils/FileNormalizer.js.map +0 -1
  127. package/utils/FileUploadModifier.d.ts +0 -30
  128. package/utils/FileUploadModifier.js +0 -39
  129. package/utils/FileUploadModifier.js.map +0 -1
  130. package/utils/createFileNormalizerFromContext.d.ts +0 -3
  131. package/utils/createFileNormalizerFromContext.js +0 -9
  132. package/utils/createFileNormalizerFromContext.js.map +0 -1
  133. package/utils/getPresignedPostPayload.d.ts +0 -4
  134. package/utils/getPresignedPostPayload.js +0 -43
  135. package/utils/getPresignedPostPayload.js.map +0 -1
  136. package/utils/mimeTypes.d.ts +0 -5
  137. package/utils/mimeTypes.js +0 -9
  138. package/utils/mimeTypes.js.map +0 -1
@@ -1,85 +1,104 @@
1
1
  import sharp from "sharp";
2
- import { AssetTransformationStrategy } from "@webiny/api-file-manager/features/assetDelivery/abstractions.js";
3
- import { WidthCollection } from "./transformation/WidthCollection.js";
4
- import { CallableContentsReader } from "./transformation/CallableContentsReader.js";
5
- import { AssetKeyGenerator } from "./transformation/AssetKeyGenerator.js";
2
+ import { IdentityContext } from "@webiny/api-core/features/security/IdentityContext/index.js";
3
+ import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
4
+ import { CallableContentsReader, DEFAULT_IMAGE_QUALITY, contentTypeForFormat } from "@webiny/api-file-manager/features/assetDelivery/transformation/index.js";
5
+ import { extractFramedRegion, transformImageBuffer } from "@webiny/api-file-manager/features/assetDelivery/assetTypes/image/transformImage.js";
6
+ import { normalizeImageOptions } from "@webiny/api-file-manager/features/assetDelivery/assetTypes/image/normalizeImageOptions.js";
7
+ import { AssetKeyGenerator, ImageAssetTypeHandler } from "@webiny/api-file-manager/features/assetDelivery/assetTypes/image/index.js";
6
8
  import { S3AssetDeliveryConfig, S3Bucket, S3Client } from "../abstractions.js";
7
- import * as __rspack_external__transformation_utils_js_9257ec9a from "./transformation/utils.js";
9
+ const hasTransform = (options)=>void 0 !== options.width || void 0 !== options.format || void 0 !== options.quality;
8
10
  class SharpTransform {
9
- constructor(s3, bucket, config){
11
+ constructor(s3, bucket, config, identityContext, getFile){
10
12
  this.s3 = s3;
11
13
  this.bucket = bucket;
12
14
  this.imageResizeWidths = config.imageResizeWidths;
15
+ this.imageQuality = {
16
+ ...DEFAULT_IMAGE_QUALITY,
17
+ ...config.imageQuality ?? {}
18
+ };
19
+ this.identityContext = identityContext;
20
+ this.getFile = getFile;
13
21
  }
14
- async transform(assetRequest, asset) {
15
- if (!__rspack_external__transformation_utils_js_9257ec9a.SUPPORTED_TRANSFORMABLE_IMAGES.includes(asset.getExtension())) {
16
- console.log(`Transformations/optimizations of ${asset.getContentType()} assets are not supported. Skipping.`);
17
- return asset;
18
- }
19
- const { original, ...options } = assetRequest.getOptions();
22
+ async handle(assetRequest, asset) {
23
+ const rawQuery = assetRequest.getOptions();
24
+ const acceptHeader = assetRequest.getContext().accept;
25
+ const { original, crop, focal, aspectRatio, ...options } = normalizeImageOptions(rawQuery, acceptHeader);
26
+ const assetCrop = await this.loadAssetCrop(asset.getId());
27
+ const framing = {
28
+ crop: crop ?? assetCrop,
29
+ focal,
30
+ aspectRatio
31
+ };
20
32
  const transformedAsset = asset.clone();
21
- if (Object.keys(options).length > 0) return this.transformAsset(transformedAsset, options);
22
- return this.optimizeAsset(transformedAsset);
33
+ if (hasTransform(options)) return this.transformAsset(transformedAsset, options, framing);
34
+ return this.optimizeAsset(transformedAsset, framing);
23
35
  }
24
- async transformAsset(asset, options) {
25
- if (options.width) {
26
- const assetKey = new AssetKeyGenerator(asset);
27
- const transformedAssetKey = assetKey.getTransformedImageKey(options);
28
- try {
29
- const { Body } = await this.s3.getObject({
30
- Bucket: this.bucket,
31
- Key: transformedAssetKey
32
- });
33
- if (!Body) throw new Error("Missing image body!");
34
- const buffer = Buffer.from(await Body.transformToByteArray());
35
- const newAsset = asset.withProps({
36
- size: buffer.length
37
- });
38
- newAsset.setContentsReader(new CallableContentsReader(()=>buffer));
39
- console.log("Return a previously transformed asset", {
40
- key: transformedAssetKey,
41
- size: newAsset.getSize()
42
- });
43
- return newAsset;
44
- } catch {
45
- const optimizedImage = await this.optimizeAsset(asset);
46
- const widths = new WidthCollection(this.imageResizeWidths);
47
- const width = widths.getClosestOrMax(options.width);
48
- console.log(`Resize the asset (width: ${width})`);
49
- const buffer = await optimizedImage.getContents();
50
- const transformedBuffer = await sharp(buffer, {
51
- animated: this.isAssetAnimated(asset)
52
- }).withMetadata().resize({
53
- width,
54
- withoutEnlargement: true
55
- }).toBuffer();
56
- const newAsset = asset.withProps({
57
- size: transformedBuffer.length
58
- });
59
- newAsset.setContentsReader(new CallableContentsReader(()=>transformedBuffer));
60
- await this.s3.putObject({
61
- Bucket: this.bucket,
62
- Key: transformedAssetKey,
63
- ContentType: newAsset.getContentType(),
64
- Body: await newAsset.getContents()
65
- });
66
- console.log("Return the resized asset", {
67
- key: transformedAssetKey,
68
- size: newAsset.getSize()
69
- });
70
- return newAsset;
71
- }
36
+ async loadAssetCrop(fileId) {
37
+ const result = await this.identityContext.withoutAuthorization(()=>this.getFile.execute(fileId));
38
+ if (result.isFail()) return;
39
+ const imageEdit = result.value.metadata?.image;
40
+ return imageEdit?.crop;
41
+ }
42
+ async transformAsset(asset, options, framing) {
43
+ const assetKey = AssetKeyGenerator.create(asset, framing);
44
+ const transformedAssetKey = assetKey.getTransformedImageKey(options);
45
+ const contentType = options.format ? contentTypeForFormat(options.format) : asset.getContentType();
46
+ try {
47
+ const { Body } = await this.s3.getObject({
48
+ Bucket: this.bucket,
49
+ Key: transformedAssetKey
50
+ });
51
+ if (!Body) throw new Error("Missing image body!");
52
+ const buffer = Buffer.from(await Body.transformToByteArray());
53
+ const newAsset = asset.withProps({
54
+ size: buffer.length,
55
+ contentType
56
+ });
57
+ newAsset.setContentsReader(CallableContentsReader.create(()=>buffer));
58
+ console.log("Return a previously transformed asset", {
59
+ key: transformedAssetKey,
60
+ size: newAsset.getSize()
61
+ });
62
+ return newAsset;
63
+ } catch {
64
+ const optimizedImage = await this.optimizeAsset(asset, framing);
65
+ console.log("Transform the asset", options);
66
+ const baseBuffer = await optimizedImage.getContents();
67
+ const { buffer: transformedBuffer, contentType: outputContentType } = await transformImageBuffer({
68
+ buffer: baseBuffer,
69
+ animated: this.isAssetAnimated(asset),
70
+ sourceContentType: asset.getContentType(),
71
+ widths: this.imageResizeWidths,
72
+ options,
73
+ qualityDefaults: this.imageQuality
74
+ });
75
+ const newAsset = asset.withProps({
76
+ size: transformedBuffer.length,
77
+ contentType: outputContentType
78
+ });
79
+ newAsset.setContentsReader(CallableContentsReader.create(()=>transformedBuffer));
80
+ await this.s3.putObject({
81
+ Bucket: this.bucket,
82
+ Key: transformedAssetKey,
83
+ ContentType: newAsset.getContentType(),
84
+ Body: await newAsset.getContents()
85
+ });
86
+ console.log("Return the transformed asset", {
87
+ key: transformedAssetKey,
88
+ size: newAsset.getSize(),
89
+ contentType: newAsset.getContentType()
90
+ });
91
+ return newAsset;
72
92
  }
73
- return asset;
74
93
  }
75
- async optimizeAsset(asset) {
94
+ async optimizeAsset(asset, framing) {
76
95
  console.log("Optimize asset", {
77
96
  id: asset.getId(),
78
97
  key: asset.getKey(),
79
98
  size: asset.getSize(),
80
99
  type: asset.getContentType()
81
100
  });
82
- const assetKey = new AssetKeyGenerator(asset);
101
+ const assetKey = AssetKeyGenerator.create(asset, framing);
83
102
  const optimizedAssetKey = assetKey.getOptimizedImageKey();
84
103
  try {
85
104
  const { Body } = await this.s3.getObject({
@@ -92,27 +111,30 @@ class SharpTransform {
92
111
  const newAsset = asset.withProps({
93
112
  size: buffer.length
94
113
  });
95
- newAsset.setContentsReader(new CallableContentsReader(()=>buffer));
114
+ newAsset.setContentsReader(CallableContentsReader.create(()=>buffer));
96
115
  return newAsset;
97
116
  } catch {
98
117
  console.log("Create an optimized version of the original asset", asset.getKey());
99
- const buffer = await asset.getContents();
118
+ let buffer = await asset.getContents();
119
+ const framed = await extractFramedRegion(buffer, framing);
120
+ const cropped = framed !== buffer;
121
+ buffer = framed;
100
122
  const optimizationMap = {
101
123
  "image/png": (buffer)=>this.optimizePng(buffer),
102
124
  "image/jpeg": (buffer)=>this.optimizeJpeg(buffer),
103
125
  "image/jpg": (buffer)=>this.optimizeJpeg(buffer)
104
126
  };
105
127
  const optimization = optimizationMap[asset.getContentType()];
106
- if (!optimization) {
128
+ if (!optimization && !cropped) {
107
129
  console.log(`No optimizations defined for ${asset.getContentType()}`);
108
130
  return asset;
109
131
  }
110
- const optimizedBuffer = await optimization(buffer).toBuffer();
111
- console.log("Optimized asset size", optimizedBuffer.length);
132
+ const finalBuffer = optimization ? await optimization(buffer).toBuffer() : buffer;
133
+ console.log("Optimized asset size", finalBuffer.length);
112
134
  const newAsset = asset.withProps({
113
- size: optimizedBuffer.length
135
+ size: finalBuffer.length
114
136
  });
115
- newAsset.setContentsReader(new CallableContentsReader(()=>optimizedBuffer));
137
+ newAsset.setContentsReader(CallableContentsReader.create(()=>finalBuffer));
116
138
  await this.s3.putObject({
117
139
  Bucket: this.bucket,
118
140
  Key: optimizedAssetKey,
@@ -149,12 +171,14 @@ class SharpTransform {
149
171
  });
150
172
  }
151
173
  }
152
- const SharpTransformImpl = AssetTransformationStrategy.createImplementation({
174
+ const SharpTransformImpl = ImageAssetTypeHandler.createImplementation({
153
175
  implementation: SharpTransform,
154
176
  dependencies: [
155
177
  S3Client,
156
178
  S3Bucket,
157
- S3AssetDeliveryConfig
179
+ S3AssetDeliveryConfig,
180
+ IdentityContext,
181
+ GetFileUseCase
158
182
  ]
159
183
  });
160
184
  export { SharpTransform, SharpTransformImpl };
@@ -1 +1 @@
1
- {"version":3,"file":"assetDelivery/s3/SharpTransform.js","sources":["../../../src/assetDelivery/s3/SharpTransform.ts"],"sourcesContent":["import sharp, { type Sharp } from \"sharp\";\nimport type { S3 } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport type {\n Asset,\n AssetRequest,\n AssetRequestOptions,\n AssetTransformationStrategy\n} from \"@webiny/api-file-manager\";\nimport { AssetTransformationStrategy as AssetTransformationStrategyAbstraction } from \"@webiny/api-file-manager/features/assetDelivery/abstractions.js\";\nimport { WidthCollection } from \"./transformation/WidthCollection.js\";\nimport * as utils from \"./transformation/utils.js\";\nimport { CallableContentsReader } from \"./transformation/CallableContentsReader.js\";\nimport { AssetKeyGenerator } from \"./transformation/AssetKeyGenerator.js\";\nimport { S3Client, S3Bucket, S3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\nimport type { IS3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\n\nexport class SharpTransform implements AssetTransformationStrategy {\n private readonly s3: S3;\n private readonly bucket: string;\n private readonly imageResizeWidths: number[];\n\n constructor(s3: S3, bucket: string, config: IS3AssetDeliveryConfig) {\n this.s3 = s3;\n this.bucket = bucket;\n this.imageResizeWidths = config.imageResizeWidths;\n }\n\n async transform(assetRequest: AssetRequest, asset: Asset): Promise<Asset> {\n if (!utils.SUPPORTED_TRANSFORMABLE_IMAGES.includes(asset.getExtension())) {\n console.log(\n `Transformations/optimizations of ${asset.getContentType()} assets are not supported. Skipping.`\n );\n return asset;\n }\n\n // oxlint-disable-next-line typescript/no-unused-vars\n const { original, ...options } = assetRequest.getOptions();\n\n const transformedAsset = asset.clone();\n\n if (Object.keys(options).length > 0) {\n return this.transformAsset(transformedAsset, options);\n }\n\n return this.optimizeAsset(transformedAsset);\n }\n\n private async transformAsset(asset: Asset, options: Omit<AssetRequestOptions, \"original\">) {\n if (options.width) {\n const assetKey = new AssetKeyGenerator(asset);\n const transformedAssetKey = assetKey.getTransformedImageKey(options);\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: transformedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => buffer));\n\n console.log(`Return a previously transformed asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize()\n });\n\n return newAsset;\n } catch {\n const optimizedImage = await this.optimizeAsset(asset);\n\n const widths = new WidthCollection(this.imageResizeWidths);\n const width = widths.getClosestOrMax(options.width);\n\n console.log(`Resize the asset (width: ${width})`);\n const buffer = await optimizedImage.getContents();\n const transformedBuffer = await sharp(buffer, {\n animated: this.isAssetAnimated(asset)\n })\n .withMetadata()\n .resize({ width, withoutEnlargement: true })\n .toBuffer();\n\n const newAsset = asset.withProps({ size: transformedBuffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => transformedBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: transformedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n console.log(`Return the resized asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize()\n });\n\n return newAsset;\n }\n }\n\n return asset;\n }\n\n private async optimizeAsset(asset: Asset) {\n console.log(\"Optimize asset\", {\n id: asset.getId(),\n key: asset.getKey(),\n size: asset.getSize(),\n type: asset.getContentType()\n });\n\n const assetKey = new AssetKeyGenerator(asset);\n const optimizedAssetKey = assetKey.getOptimizedImageKey();\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n console.log(\"Return a previously optimized asset\", optimizedAssetKey);\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => buffer));\n\n return newAsset;\n } catch {\n console.log(\"Create an optimized version of the original asset\", asset.getKey());\n const buffer = await asset.getContents();\n\n const optimizationMap: Record<string, ((buffer: Buffer) => Sharp) | undefined> = {\n \"image/png\": (buffer: Buffer) => this.optimizePng(buffer),\n \"image/jpeg\": (buffer: Buffer) => this.optimizeJpeg(buffer),\n \"image/jpg\": (buffer: Buffer) => this.optimizeJpeg(buffer)\n };\n\n const optimization = optimizationMap[asset.getContentType()];\n\n if (!optimization) {\n console.log(`No optimizations defined for ${asset.getContentType()}`);\n return asset;\n }\n\n const optimizedBuffer = await optimization(buffer).toBuffer();\n\n console.log(\"Optimized asset size\", optimizedBuffer.length);\n\n const newAsset = asset.withProps({ size: optimizedBuffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => optimizedBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n return newAsset;\n }\n }\n\n private isAssetAnimated(asset: Asset) {\n return [\"gif\", \"webp\"].includes(asset.getExtension());\n }\n\n private optimizePng(buffer: Buffer) {\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })\n .withMetadata();\n }\n\n private optimizeJpeg(buffer: Buffer) {\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .withMetadata()\n .toFormat(\"jpeg\", { quality: 90 });\n }\n}\n\nexport const SharpTransformImpl = AssetTransformationStrategyAbstraction.createImplementation({\n implementation: SharpTransform,\n dependencies: [S3Client, S3Bucket, S3AssetDeliveryConfig]\n});\n"],"names":["SharpTransform","s3","bucket","config","assetRequest","asset","utils","console","original","options","transformedAsset","Object","assetKey","AssetKeyGenerator","transformedAssetKey","Body","Error","buffer","Buffer","newAsset","CallableContentsReader","optimizedImage","widths","WidthCollection","width","transformedBuffer","sharp","optimizedAssetKey","optimizationMap","optimization","optimizedBuffer","SharpTransformImpl","AssetTransformationStrategyAbstraction","S3Client","S3Bucket","S3AssetDeliveryConfig"],"mappings":";;;;;;;AAgBO,MAAMA;IAKT,YAAYC,EAAM,EAAEC,MAAc,EAAEC,MAA8B,CAAE;QAChE,IAAI,CAAC,EAAE,GAAGF;QACV,IAAI,CAAC,MAAM,GAAGC;QACd,IAAI,CAAC,iBAAiB,GAAGC,OAAO,iBAAiB;IACrD;IAEA,MAAM,UAAUC,YAA0B,EAAEC,KAAY,EAAkB;QACtE,IAAI,CAACC,oDAAAA,8BAAAA,CAAAA,QAA6C,CAACD,MAAM,YAAY,KAAK;YACtEE,QAAQ,GAAG,CACP,CAAC,iCAAiC,EAAEF,MAAM,cAAc,GAAG,oCAAoC,CAAC;YAEpG,OAAOA;QACX;QAGA,MAAM,EAAEG,QAAQ,EAAE,GAAGC,SAAS,GAAGL,aAAa,UAAU;QAExD,MAAMM,mBAAmBL,MAAM,KAAK;QAEpC,IAAIM,OAAO,IAAI,CAACF,SAAS,MAAM,GAAG,GAC9B,OAAO,IAAI,CAAC,cAAc,CAACC,kBAAkBD;QAGjD,OAAO,IAAI,CAAC,aAAa,CAACC;IAC9B;IAEA,MAAc,eAAeL,KAAY,EAAEI,OAA8C,EAAE;QACvF,IAAIA,QAAQ,KAAK,EAAE;YACf,MAAMG,WAAW,IAAIC,kBAAkBR;YACvC,MAAMS,sBAAsBF,SAAS,sBAAsB,CAACH;YAE5D,IAAI;gBACA,MAAM,EAAEM,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC;oBACrC,QAAQ,IAAI,CAAC,MAAM;oBACnB,KAAKD;gBACT;gBAEA,IAAI,CAACC,MACD,MAAM,IAAIC,MAAM;gBAGpB,MAAMC,SAASC,OAAO,IAAI,CAAC,MAAMH,KAAK,oBAAoB;gBAE1D,MAAMI,WAAWd,MAAM,SAAS,CAAC;oBAAE,MAAMY,OAAO,MAAM;gBAAC;gBACvDE,SAAS,iBAAiB,CAAC,IAAIC,uBAAuB,IAAMH;gBAE5DV,QAAQ,GAAG,CAAC,yCAAyC;oBACjD,KAAKO;oBACL,MAAMK,SAAS,OAAO;gBAC1B;gBAEA,OAAOA;YACX,EAAE,OAAM;gBACJ,MAAME,iBAAiB,MAAM,IAAI,CAAC,aAAa,CAAChB;gBAEhD,MAAMiB,SAAS,IAAIC,gBAAgB,IAAI,CAAC,iBAAiB;gBACzD,MAAMC,QAAQF,OAAO,eAAe,CAACb,QAAQ,KAAK;gBAElDF,QAAQ,GAAG,CAAC,CAAC,yBAAyB,EAAEiB,MAAM,CAAC,CAAC;gBAChD,MAAMP,SAAS,MAAMI,eAAe,WAAW;gBAC/C,MAAMI,oBAAoB,MAAMC,MAAMT,QAAQ;oBAC1C,UAAU,IAAI,CAAC,eAAe,CAACZ;gBACnC,GACK,YAAY,GACZ,MAAM,CAAC;oBAAEmB;oBAAO,oBAAoB;gBAAK,GACzC,QAAQ;gBAEb,MAAML,WAAWd,MAAM,SAAS,CAAC;oBAAE,MAAMoB,kBAAkB,MAAM;gBAAC;gBAClEN,SAAS,iBAAiB,CAAC,IAAIC,uBAAuB,IAAMK;gBAE5D,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC;oBACpB,QAAQ,IAAI,CAAC,MAAM;oBACnB,KAAKX;oBACL,aAAaK,SAAS,cAAc;oBACpC,MAAM,MAAMA,SAAS,WAAW;gBACpC;gBAEAZ,QAAQ,GAAG,CAAC,4BAA4B;oBACpC,KAAKO;oBACL,MAAMK,SAAS,OAAO;gBAC1B;gBAEA,OAAOA;YACX;QACJ;QAEA,OAAOd;IACX;IAEA,MAAc,cAAcA,KAAY,EAAE;QACtCE,QAAQ,GAAG,CAAC,kBAAkB;YAC1B,IAAIF,MAAM,KAAK;YACf,KAAKA,MAAM,MAAM;YACjB,MAAMA,MAAM,OAAO;YACnB,MAAMA,MAAM,cAAc;QAC9B;QAEA,MAAMO,WAAW,IAAIC,kBAAkBR;QACvC,MAAMsB,oBAAoBf,SAAS,oBAAoB;QAEvD,IAAI;YACA,MAAM,EAAEG,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC;gBACrC,QAAQ,IAAI,CAAC,MAAM;gBACnB,KAAKY;YACT;YAEA,IAAI,CAACZ,MACD,MAAM,IAAIC,MAAM;YAGpBT,QAAQ,GAAG,CAAC,uCAAuCoB;YAEnD,MAAMV,SAASC,OAAO,IAAI,CAAC,MAAMH,KAAK,oBAAoB;YAE1D,MAAMI,WAAWd,MAAM,SAAS,CAAC;gBAAE,MAAMY,OAAO,MAAM;YAAC;YACvDE,SAAS,iBAAiB,CAAC,IAAIC,uBAAuB,IAAMH;YAE5D,OAAOE;QACX,EAAE,OAAM;YACJZ,QAAQ,GAAG,CAAC,qDAAqDF,MAAM,MAAM;YAC7E,MAAMY,SAAS,MAAMZ,MAAM,WAAW;YAEtC,MAAMuB,kBAA2E;gBAC7E,aAAa,CAACX,SAAmB,IAAI,CAAC,WAAW,CAACA;gBAClD,cAAc,CAACA,SAAmB,IAAI,CAAC,YAAY,CAACA;gBACpD,aAAa,CAACA,SAAmB,IAAI,CAAC,YAAY,CAACA;YACvD;YAEA,MAAMY,eAAeD,eAAe,CAACvB,MAAM,cAAc,GAAG;YAE5D,IAAI,CAACwB,cAAc;gBACftB,QAAQ,GAAG,CAAC,CAAC,6BAA6B,EAAEF,MAAM,cAAc,IAAI;gBACpE,OAAOA;YACX;YAEA,MAAMyB,kBAAkB,MAAMD,aAAaZ,QAAQ,QAAQ;YAE3DV,QAAQ,GAAG,CAAC,wBAAwBuB,gBAAgB,MAAM;YAE1D,MAAMX,WAAWd,MAAM,SAAS,CAAC;gBAAE,MAAMyB,gBAAgB,MAAM;YAAC;YAChEX,SAAS,iBAAiB,CAAC,IAAIC,uBAAuB,IAAMU;YAE5D,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC;gBACpB,QAAQ,IAAI,CAAC,MAAM;gBACnB,KAAKH;gBACL,aAAaR,SAAS,cAAc;gBACpC,MAAM,MAAMA,SAAS,WAAW;YACpC;YAEA,OAAOA;QACX;IACJ;IAEQ,gBAAgBd,KAAY,EAAE;QAClC,OAAO;YAAC;YAAO;SAAO,CAAC,QAAQ,CAACA,MAAM,YAAY;IACtD;IAEQ,YAAYY,MAAc,EAAE;QAChC,OAAOS,MAAMT,QACR,MAAM,CAAC;YAAE,OAAO;YAAM,oBAAoB;YAAM,KAAK;QAAS,GAC9D,GAAG,CAAC;YAAE,kBAAkB;YAAG,mBAAmB;YAAM,OAAO;QAAK,GAChE,YAAY;IACrB;IAEQ,aAAaA,MAAc,EAAE;QACjC,OAAOS,MAAMT,QACR,MAAM,CAAC;YAAE,OAAO;YAAM,oBAAoB;YAAM,KAAK;QAAS,GAC9D,YAAY,GACZ,QAAQ,CAAC,QAAQ;YAAE,SAAS;QAAG;IACxC;AACJ;AAEO,MAAMc,qBAAqBC,4BAAAA,oBAA2D,CAAC;IAC1F,gBAAgBhC;IAChB,cAAc;QAACiC;QAAUC;QAAUC;KAAsB;AAC7D"}
1
+ {"version":3,"file":"assetDelivery/s3/SharpTransform.js","sources":["../../../src/assetDelivery/s3/SharpTransform.ts"],"sourcesContent":["import sharp from \"sharp\";\nimport type { Sharp } from \"sharp\";\nimport type { S3 } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { IdentityContext } from \"@webiny/api-core/features/security/IdentityContext/index.js\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\nimport {\n contentTypeForFormat,\n DEFAULT_IMAGE_QUALITY,\n type ImageFormat\n} from \"@webiny/api-file-manager/features/assetDelivery/transformation/index.js\";\nimport {\n extractFramedRegion,\n transformImageBuffer\n} from \"@webiny/api-file-manager/features/assetDelivery/assetTypes/image/transformImage.js\";\nimport type {\n AssetImageEdit,\n Framing,\n ImageRequestOptions\n} from \"@webiny/api-file-manager/features/assetDelivery/assetTypes/image/imageTypes.js\";\nimport { normalizeImageOptions } from \"@webiny/api-file-manager/features/assetDelivery/assetTypes/image/normalizeImageOptions.js\";\nimport { CallableContentsReader } from \"@webiny/api-file-manager/features/assetDelivery/transformation/index.js\";\nimport { AssetKeyGenerator } from \"@webiny/api-file-manager/features/assetDelivery/assetTypes/image/index.js\";\nimport { ImageAssetTypeHandler } from \"@webiny/api-file-manager/features/assetDelivery/assetTypes/image/index.js\";\nimport type { Asset } from \"@webiny/api-file-manager/delivery/AssetDelivery/Asset.js\";\nimport type { AssetRequest } from \"@webiny/api-file-manager/delivery/AssetDelivery/AssetRequest.js\";\nimport { S3Client, S3Bucket, S3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\nimport type { IS3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\n\ntype TransformOptions = Omit<ImageRequestOptions, \"original\" | \"crop\" | \"focal\" | \"aspectRatio\">;\n\nconst hasTransform = (options: TransformOptions): boolean => {\n return (\n options.width !== undefined || options.format !== undefined || options.quality !== undefined\n );\n};\n\nexport class SharpTransform implements ImageAssetTypeHandler.Interface {\n private readonly s3: S3;\n private readonly bucket: string;\n private readonly imageResizeWidths: number[];\n private readonly imageQuality: Record<ImageFormat, number>;\n private readonly identityContext: IdentityContext.Interface;\n private readonly getFile: GetFileUseCase.Interface;\n\n constructor(\n s3: S3,\n bucket: string,\n config: IS3AssetDeliveryConfig,\n identityContext: IdentityContext.Interface,\n getFile: GetFileUseCase.Interface\n ) {\n this.s3 = s3;\n this.bucket = bucket;\n this.imageResizeWidths = config.imageResizeWidths;\n this.imageQuality = { ...DEFAULT_IMAGE_QUALITY, ...(config.imageQuality ?? {}) };\n this.identityContext = identityContext;\n this.getFile = getFile;\n }\n\n async handle(assetRequest: AssetRequest, asset: Asset): Promise<Asset> {\n const rawQuery = assetRequest.getOptions() as Record<string, any>;\n const acceptHeader = assetRequest.getContext<{ accept?: string }>().accept;\n // oxlint-disable-next-line typescript/no-unused-vars\n const { original, crop, focal, aspectRatio, ...options } = normalizeImageOptions(\n rawQuery,\n acceptHeader\n );\n\n const assetCrop = await this.loadAssetCrop(asset.getId());\n const framing: Framing = {\n crop: crop ?? assetCrop,\n focal,\n aspectRatio\n };\n\n const transformedAsset = asset.clone();\n\n if (hasTransform(options)) {\n return this.transformAsset(transformedAsset, options, framing);\n }\n\n return this.optimizeAsset(transformedAsset, framing);\n }\n\n private async loadAssetCrop(fileId: string) {\n const result = await this.identityContext.withoutAuthorization(() =>\n this.getFile.execute(fileId)\n );\n\n if (result.isFail()) {\n return undefined;\n }\n\n const imageEdit = result.value.metadata?.image as AssetImageEdit | undefined;\n return imageEdit?.crop;\n }\n\n private async transformAsset(asset: Asset, options: TransformOptions, framing: Framing) {\n const assetKey = AssetKeyGenerator.create(asset, framing);\n const transformedAssetKey = assetKey.getTransformedImageKey(options);\n\n const contentType = options.format\n ? contentTypeForFormat(options.format)\n : asset.getContentType();\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: transformedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length, contentType });\n newAsset.setContentsReader(CallableContentsReader.create(() => buffer));\n\n console.log(`Return a previously transformed asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize()\n });\n\n return newAsset;\n } catch {\n const optimizedImage = await this.optimizeAsset(asset, framing);\n\n console.log(`Transform the asset`, options);\n const baseBuffer = await optimizedImage.getContents();\n const { buffer: transformedBuffer, contentType: outputContentType } =\n await transformImageBuffer({\n buffer: baseBuffer,\n animated: this.isAssetAnimated(asset),\n sourceContentType: asset.getContentType(),\n widths: this.imageResizeWidths,\n options,\n qualityDefaults: this.imageQuality\n });\n\n const newAsset = asset.withProps({\n size: transformedBuffer.length,\n contentType: outputContentType\n });\n newAsset.setContentsReader(CallableContentsReader.create(() => transformedBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: transformedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n console.log(`Return the transformed asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize(),\n contentType: newAsset.getContentType()\n });\n\n return newAsset;\n }\n }\n\n private async optimizeAsset(asset: Asset, framing: Framing) {\n console.log(\"Optimize asset\", {\n id: asset.getId(),\n key: asset.getKey(),\n size: asset.getSize(),\n type: asset.getContentType()\n });\n\n const assetKey = AssetKeyGenerator.create(asset, framing);\n const optimizedAssetKey = assetKey.getOptimizedImageKey();\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n console.log(\"Return a previously optimized asset\", optimizedAssetKey);\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length });\n newAsset.setContentsReader(CallableContentsReader.create(() => buffer));\n\n return newAsset;\n } catch {\n console.log(\"Create an optimized version of the original asset\", asset.getKey());\n let buffer = await asset.getContents();\n\n const framed = await extractFramedRegion(buffer, framing);\n const cropped = framed !== buffer;\n buffer = framed;\n\n const optimizationMap: Record<string, ((buffer: Buffer) => Sharp) | undefined> = {\n \"image/png\": (buffer: Buffer) => this.optimizePng(buffer),\n \"image/jpeg\": (buffer: Buffer) => this.optimizeJpeg(buffer),\n \"image/jpg\": (buffer: Buffer) => this.optimizeJpeg(buffer)\n };\n\n const optimization = optimizationMap[asset.getContentType()];\n\n if (!optimization && !cropped) {\n console.log(`No optimizations defined for ${asset.getContentType()}`);\n return asset;\n }\n\n const finalBuffer = optimization ? await optimization(buffer).toBuffer() : buffer;\n\n console.log(\"Optimized asset size\", finalBuffer.length);\n\n const newAsset = asset.withProps({ size: finalBuffer.length });\n newAsset.setContentsReader(CallableContentsReader.create(() => finalBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n return newAsset;\n }\n }\n\n private isAssetAnimated(asset: Asset) {\n return [\"gif\", \"webp\"].includes(asset.getExtension());\n }\n\n private optimizePng(buffer: Buffer) {\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })\n .withMetadata();\n }\n\n private optimizeJpeg(buffer: Buffer) {\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .withMetadata()\n .toFormat(\"jpeg\", { quality: 90 });\n }\n}\n\nexport const SharpTransformImpl = ImageAssetTypeHandler.createImplementation({\n implementation: SharpTransform,\n dependencies: [S3Client, S3Bucket, S3AssetDeliveryConfig, IdentityContext, GetFileUseCase]\n});\n"],"names":["hasTransform","options","undefined","SharpTransform","s3","bucket","config","identityContext","getFile","DEFAULT_IMAGE_QUALITY","assetRequest","asset","rawQuery","acceptHeader","original","crop","focal","aspectRatio","normalizeImageOptions","assetCrop","framing","transformedAsset","fileId","result","imageEdit","assetKey","AssetKeyGenerator","transformedAssetKey","contentType","contentTypeForFormat","Body","Error","buffer","Buffer","newAsset","CallableContentsReader","console","optimizedImage","baseBuffer","transformedBuffer","outputContentType","transformImageBuffer","optimizedAssetKey","framed","extractFramedRegion","cropped","optimizationMap","optimization","finalBuffer","sharp","SharpTransformImpl","ImageAssetTypeHandler","S3Client","S3Bucket","S3AssetDeliveryConfig","IdentityContext","GetFileUseCase"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
1
  import type { GuardDutyEvent } from "./types.js";
2
- export declare const createThreatDetectionEventHandler: () => (import("@webiny/handler").HandlerOnRequestPlugin<import("@webiny/handler/types.js").Context> | import("@webiny/handler-aws").EventBridgeEventHandler<"GuardDuty Malware Protection Object Scan Result", GuardDutyEvent, any>)[];
2
+ export declare const createThreatDetectionEventHandler: () => (import("@webiny/handler-aws").EventBridgeEventHandler<"GuardDuty Malware Protection Object Scan Result", GuardDutyEvent, any> | import("@webiny/handler").HandlerOnRequestPlugin<import("@webiny/handler/types.js").Context>)[];
@@ -1,17 +1,18 @@
1
1
  import { createEventBridgeEventHandler } from "@webiny/handler-aws";
2
2
  import { createHandlerOnRequest } from "@webiny/handler";
3
3
  import { GlobalKeyValueStore } from "@webiny/api-core/features/keyValueStore/index.js";
4
+ import { ObjectKey } from "@webiny/api-file-manager/exports/api/file-manager/assetDelivery.js";
4
5
  import { processThreatScanResult } from "./processThreatScanResult.js";
5
- import { ObjectKey } from "./ObjectKey.js";
6
6
  const detailType = "GuardDuty Malware Protection Object Scan Result";
7
7
  const createThreatDetectionEventHandler = ()=>{
8
8
  const handlerOnRequest = createHandlerOnRequest(async (request, _, context)=>{
9
9
  const payload = request.body;
10
10
  if (payload["detail-type"] !== detailType) return;
11
- const objectKey = payload.detail.s3ObjectDetails.objectKey;
11
+ const s3ObjectKey = payload.detail.s3ObjectDetails.objectKey;
12
+ const objectKey = context.container.resolve(ObjectKey);
12
13
  const keyValueStore = context.container.resolve(GlobalKeyValueStore);
13
14
  try {
14
- const fileId = ObjectKey.from(objectKey).id();
15
+ const fileId = objectKey.from(s3ObjectKey).id();
15
16
  const result = await keyValueStore.get(`FileManager/File/${fileId}/Metadata`);
16
17
  if (result.isFail()) return;
17
18
  request.headers = {
@@ -1 +1 @@
1
- {"version":3,"file":"assetDelivery/threatDetection/createThreatDetectionEventHandler.js","sources":["../../../src/assetDelivery/threatDetection/createThreatDetectionEventHandler.ts"],"sourcesContent":["import { createEventBridgeEventHandler } from \"@webiny/handler-aws\";\nimport { createHandlerOnRequest } from \"@webiny/handler\";\nimport type { EventBridgeEvent } from \"@webiny/aws-sdk/types/index.js\";\nimport type { ApiCoreContext } from \"@webiny/api-core/types/core.js\";\nimport { GlobalKeyValueStore } from \"@webiny/api-core/features/keyValueStore/index.js\";\nimport type { GuardDutyEvent } from \"./types.js\";\nimport { processThreatScanResult } from \"./processThreatScanResult.js\";\nimport { ObjectKey } from \"./ObjectKey.js\";\n\nconst detailType = \"GuardDuty Malware Protection Object Scan Result\";\n\nexport const createThreatDetectionEventHandler = () => {\n const handlerOnRequest = createHandlerOnRequest(async (request, _, context) => {\n const payload = request.body as EventBridgeEvent<string, GuardDutyEvent>;\n\n if (payload[\"detail-type\"] !== detailType) {\n return;\n }\n\n const objectKey = payload.detail.s3ObjectDetails.objectKey;\n const keyValueStore = context.container.resolve(GlobalKeyValueStore);\n\n try {\n // Extract file id from the absolute S3 object key\n const fileId = ObjectKey.from(objectKey).id();\n const result = await keyValueStore.get<{ tenant: string }>(\n `FileManager/File/${fileId}/Metadata`\n );\n\n if (result.isFail()) {\n return;\n }\n\n request.headers = {\n ...request.headers,\n \"x-tenant\": result.value.tenant\n };\n } catch {\n // If metadata can't be loaded, we ignore the file.\n // Most likely it's because the file is a rendition of the original file,\n // so we don't need to do anything with it.\n }\n });\n // Guard Duty event handler.\n const threatScanEventHandler = createEventBridgeEventHandler<typeof detailType, GuardDutyEvent>(\n async ({ payload, next, ...rest }) => {\n const context = rest.context as ApiCoreContext;\n\n const threatDetectionEnabled = context.wcp.canUseFileManagerThreatDetection();\n\n if (!threatDetectionEnabled || payload[\"detail-type\"] !== detailType) {\n return next();\n }\n\n await processThreatScanResult(context, payload.detail);\n }\n );\n\n // Assign a human-readable name for easier debugging.\n threatScanEventHandler.name = threatScanEventHandler.type + \".threatDetectionEventHandler\";\n\n return [handlerOnRequest, threatScanEventHandler];\n};\n"],"names":["detailType","createThreatDetectionEventHandler","handlerOnRequest","createHandlerOnRequest","request","_","context","payload","objectKey","keyValueStore","GlobalKeyValueStore","fileId","ObjectKey","result","threatScanEventHandler","createEventBridgeEventHandler","next","rest","threatDetectionEnabled","processThreatScanResult"],"mappings":";;;;;AASA,MAAMA,aAAa;AAEZ,MAAMC,oCAAoC;IAC7C,MAAMC,mBAAmBC,uBAAuB,OAAOC,SAASC,GAAGC;QAC/D,MAAMC,UAAUH,QAAQ,IAAI;QAE5B,IAAIG,OAAO,CAAC,cAAc,KAAKP,YAC3B;QAGJ,MAAMQ,YAAYD,QAAQ,MAAM,CAAC,eAAe,CAAC,SAAS;QAC1D,MAAME,gBAAgBH,QAAQ,SAAS,CAAC,OAAO,CAACI;QAEhD,IAAI;YAEA,MAAMC,SAASC,UAAU,IAAI,CAACJ,WAAW,EAAE;YAC3C,MAAMK,SAAS,MAAMJ,cAAc,GAAG,CAClC,CAAC,iBAAiB,EAAEE,OAAO,SAAS,CAAC;YAGzC,IAAIE,OAAO,MAAM,IACb;YAGJT,QAAQ,OAAO,GAAG;gBACd,GAAGA,QAAQ,OAAO;gBAClB,YAAYS,OAAO,KAAK,CAAC,MAAM;YACnC;QACJ,EAAE,OAAM,CAIR;IACJ;IAEA,MAAMC,yBAAyBC,8BAC3B,OAAO,EAAER,OAAO,EAAES,IAAI,EAAE,GAAGC,MAAM;QAC7B,MAAMX,UAAUW,KAAK,OAAO;QAE5B,MAAMC,yBAAyBZ,QAAQ,GAAG,CAAC,gCAAgC;QAE3E,IAAI,CAACY,0BAA0BX,OAAO,CAAC,cAAc,KAAKP,YACtD,OAAOgB;QAGX,MAAMG,wBAAwBb,SAASC,QAAQ,MAAM;IACzD;IAIJO,uBAAuB,IAAI,GAAGA,uBAAuB,IAAI,GAAG;IAE5D,OAAO;QAACZ;QAAkBY;KAAuB;AACrD"}
1
+ {"version":3,"file":"assetDelivery/threatDetection/createThreatDetectionEventHandler.js","sources":["../../../src/assetDelivery/threatDetection/createThreatDetectionEventHandler.ts"],"sourcesContent":["import { createEventBridgeEventHandler } from \"@webiny/handler-aws\";\nimport { createHandlerOnRequest } from \"@webiny/handler\";\nimport type { EventBridgeEvent } from \"@webiny/aws-sdk/types/index.js\";\nimport type { ApiCoreContext } from \"@webiny/api-core/types/core.js\";\nimport { GlobalKeyValueStore } from \"@webiny/api-core/features/keyValueStore/index.js\";\nimport { ObjectKey } from \"@webiny/api-file-manager/exports/api/file-manager/assetDelivery.js\";\nimport type { GuardDutyEvent } from \"./types.js\";\nimport { processThreatScanResult } from \"./processThreatScanResult.js\";\n\nconst detailType = \"GuardDuty Malware Protection Object Scan Result\";\n\nexport const createThreatDetectionEventHandler = () => {\n const handlerOnRequest = createHandlerOnRequest(async (request, _, context) => {\n const payload = request.body as EventBridgeEvent<string, GuardDutyEvent>;\n\n if (payload[\"detail-type\"] !== detailType) {\n return;\n }\n\n const s3ObjectKey = payload.detail.s3ObjectDetails.objectKey;\n const objectKey = context.container.resolve(ObjectKey);\n const keyValueStore = context.container.resolve(GlobalKeyValueStore);\n\n try {\n const fileId = objectKey.from(s3ObjectKey).id();\n const result = await keyValueStore.get<{ tenant: string }>(\n `FileManager/File/${fileId}/Metadata`\n );\n\n if (result.isFail()) {\n return;\n }\n\n request.headers = {\n ...request.headers,\n \"x-tenant\": result.value.tenant\n };\n } catch {\n // If metadata can't be loaded, we ignore the file.\n // Most likely it's because the file is a rendition of the original file,\n // so we don't need to do anything with it.\n }\n });\n // Guard Duty event handler.\n const threatScanEventHandler = createEventBridgeEventHandler<typeof detailType, GuardDutyEvent>(\n async ({ payload, next, ...rest }) => {\n const context = rest.context as ApiCoreContext;\n\n const threatDetectionEnabled = context.wcp.canUseFileManagerThreatDetection();\n\n if (!threatDetectionEnabled || payload[\"detail-type\"] !== detailType) {\n return next();\n }\n\n await processThreatScanResult(context, payload.detail);\n }\n );\n\n // Assign a human-readable name for easier debugging.\n threatScanEventHandler.name = threatScanEventHandler.type + \".threatDetectionEventHandler\";\n\n return [handlerOnRequest, threatScanEventHandler];\n};\n"],"names":["detailType","createThreatDetectionEventHandler","handlerOnRequest","createHandlerOnRequest","request","_","context","payload","s3ObjectKey","objectKey","ObjectKey","keyValueStore","GlobalKeyValueStore","fileId","result","threatScanEventHandler","createEventBridgeEventHandler","next","rest","threatDetectionEnabled","processThreatScanResult"],"mappings":";;;;;AASA,MAAMA,aAAa;AAEZ,MAAMC,oCAAoC;IAC7C,MAAMC,mBAAmBC,uBAAuB,OAAOC,SAASC,GAAGC;QAC/D,MAAMC,UAAUH,QAAQ,IAAI;QAE5B,IAAIG,OAAO,CAAC,cAAc,KAAKP,YAC3B;QAGJ,MAAMQ,cAAcD,QAAQ,MAAM,CAAC,eAAe,CAAC,SAAS;QAC5D,MAAME,YAAYH,QAAQ,SAAS,CAAC,OAAO,CAACI;QAC5C,MAAMC,gBAAgBL,QAAQ,SAAS,CAAC,OAAO,CAACM;QAEhD,IAAI;YACA,MAAMC,SAASJ,UAAU,IAAI,CAACD,aAAa,EAAE;YAC7C,MAAMM,SAAS,MAAMH,cAAc,GAAG,CAClC,CAAC,iBAAiB,EAAEE,OAAO,SAAS,CAAC;YAGzC,IAAIC,OAAO,MAAM,IACb;YAGJV,QAAQ,OAAO,GAAG;gBACd,GAAGA,QAAQ,OAAO;gBAClB,YAAYU,OAAO,KAAK,CAAC,MAAM;YACnC;QACJ,EAAE,OAAM,CAIR;IACJ;IAEA,MAAMC,yBAAyBC,8BAC3B,OAAO,EAAET,OAAO,EAAEU,IAAI,EAAE,GAAGC,MAAM;QAC7B,MAAMZ,UAAUY,KAAK,OAAO;QAE5B,MAAMC,yBAAyBb,QAAQ,GAAG,CAAC,gCAAgC;QAE3E,IAAI,CAACa,0BAA0BZ,OAAO,CAAC,cAAc,KAAKP,YACtD,OAAOiB;QAGX,MAAMG,wBAAwBd,SAASC,QAAQ,MAAM;IACzD;IAIJQ,uBAAuB,IAAI,GAAGA,uBAAuB,IAAI,GAAG;IAE5D,OAAO;QAACb;QAAkBa;KAAuB;AACrD"}
@@ -1,22 +1,24 @@
1
1
  import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
2
2
  import { DeleteFileUseCase } from "@webiny/api-file-manager/features/file/DeleteFile/index.js";
3
- import { WebsocketService } from "@webiny/api-websockets/features/WebsocketService/index.js";
4
- import { ObjectKey } from "./ObjectKey.js";
3
+ import { WebsocketsListConnectionsUseCase, WebsocketsSendToConnectionsUseCase } from "@webiny/api-websockets/exports/api.js";
4
+ import { ObjectKey } from "@webiny/api-file-manager/exports/api/file-manager/assetDelivery.js";
5
5
  import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
6
6
  const processThreatScanResult = async (context, eventDetail)=>{
7
- const websocketService = context.container.resolve(WebsocketService);
7
+ const objectKey = context.container.resolve(ObjectKey);
8
+ const listConnections = context.container.resolve(WebsocketsListConnectionsUseCase);
9
+ const sendToConnections = context.container.resolve(WebsocketsSendToConnectionsUseCase);
8
10
  const getFile = context.container.resolve(GetFileUseCase);
9
11
  const updateFile = context.container.resolve(UpdateFileUseCase);
10
12
  const deleteFile = context.container.resolve(DeleteFileUseCase);
11
13
  await context.security.withoutAuthorization(async ()=>{
12
14
  const scanStatus = eventDetail.scanResultDetails.scanResultStatus;
13
15
  const s3Object = eventDetail.s3ObjectDetails;
14
- const fileId = ObjectKey.from(s3Object.objectKey).id();
16
+ const fileId = objectKey.from(s3Object.objectKey).id();
15
17
  const fileResult = await getFile.execute(fileId);
16
18
  if (fileResult.isFail()) return;
17
19
  const file = fileResult.value;
18
20
  let allConnections = [];
19
- const connectionsResult = await websocketService.listConnections();
21
+ const connectionsResult = await listConnections.execute();
20
22
  if (connectionsResult.isOk()) allConnections = connectionsResult.value;
21
23
  if ("NO_THREATS_FOUND" === scanStatus) {
22
24
  const newTags = file.tags.filter((tag)=>"threatScanInProgress" !== tag);
@@ -24,7 +26,7 @@ const processThreatScanResult = async (context, eventDetail)=>{
24
26
  id: file.id,
25
27
  tags: newTags
26
28
  });
27
- await websocketService.sendToConnections(allConnections, {
29
+ await sendToConnections.execute(allConnections, {
28
30
  action: "fm.threatScan.noThreatFound",
29
31
  data: {
30
32
  id: file.id,
@@ -35,7 +37,7 @@ const processThreatScanResult = async (context, eventDetail)=>{
35
37
  }
36
38
  if ("THREATS_FOUND" === scanStatus) {
37
39
  await deleteFile.execute(file.id);
38
- await websocketService.sendToConnections(allConnections, {
40
+ await sendToConnections.execute(allConnections, {
39
41
  action: "fm.threatScan.threatDetected",
40
42
  data: {
41
43
  id: file.id,
@@ -45,7 +47,7 @@ const processThreatScanResult = async (context, eventDetail)=>{
45
47
  return;
46
48
  }
47
49
  await deleteFile.execute(file.id);
48
- await websocketService.sendToConnections(allConnections, {
50
+ await sendToConnections.execute(allConnections, {
49
51
  action: "fm.threatScan.unsupported",
50
52
  data: {
51
53
  id: file.id,
@@ -1 +1 @@
1
- {"version":3,"file":"assetDelivery/threatDetection/processThreatScanResult.js","sources":["../../../src/assetDelivery/threatDetection/processThreatScanResult.ts"],"sourcesContent":["import type { ApiCoreContext } from \"@webiny/api-core/types/core.js\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { DeleteFileUseCase } from \"@webiny/api-file-manager/features/file/DeleteFile/index.js\";\nimport { WebsocketService } from \"@webiny/api-websockets/features/WebsocketService/index.js\";\nimport type { GuardDutyEvent } from \"./types.js\";\nimport { ObjectKey } from \"./ObjectKey.js\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\n\nexport const processThreatScanResult = async (\n context: ApiCoreContext,\n eventDetail: GuardDutyEvent\n) => {\n const websocketService = context.container.resolve(WebsocketService);\n const getFile = context.container.resolve(GetFileUseCase);\n const updateFile = context.container.resolve(UpdateFileUseCase);\n const deleteFile = context.container.resolve(DeleteFileUseCase);\n\n await context.security.withoutAuthorization(async () => {\n const scanStatus = eventDetail.scanResultDetails.scanResultStatus;\n const s3Object = eventDetail.s3ObjectDetails;\n\n const fileId = ObjectKey.from(s3Object.objectKey).id();\n const fileResult = await getFile.execute(fileId);\n\n if (fileResult.isFail()) {\n return;\n }\n\n const file = fileResult.value;\n\n let allConnections: WebsocketService.Connection[] = [];\n const connectionsResult = await websocketService.listConnections();\n if (connectionsResult.isOk()) {\n allConnections = connectionsResult.value;\n }\n\n if (scanStatus === \"NO_THREATS_FOUND\") {\n const newTags = file.tags.filter(tag => tag !== \"threatScanInProgress\");\n await updateFile.execute({\n id: file.id,\n tags: newTags\n });\n\n await websocketService.sendToConnections(allConnections, {\n action: \"fm.threatScan.noThreatFound\",\n data: {\n id: file.id,\n tags: newTags\n }\n });\n\n return;\n }\n\n if (scanStatus === \"THREATS_FOUND\") {\n // Delete the infected file.\n await deleteFile.execute(file.id);\n\n await websocketService.sendToConnections(allConnections, {\n action: \"fm.threatScan.threatDetected\",\n data: {\n id: file.id,\n name: file.name\n }\n });\n\n return;\n }\n\n // For all other outcomes, we delete the file, until better logic is implemented.\n await deleteFile.execute(file.id);\n\n await websocketService.sendToConnections(allConnections, {\n action: \"fm.threatScan.unsupported\",\n data: {\n id: file.id,\n name: file.name\n }\n });\n });\n};\n"],"names":["processThreatScanResult","context","eventDetail","websocketService","WebsocketService","getFile","GetFileUseCase","updateFile","UpdateFileUseCase","deleteFile","DeleteFileUseCase","scanStatus","s3Object","fileId","ObjectKey","fileResult","file","allConnections","connectionsResult","newTags","tag"],"mappings":";;;;;AAQO,MAAMA,0BAA0B,OACnCC,SACAC;IAEA,MAAMC,mBAAmBF,QAAQ,SAAS,CAAC,OAAO,CAACG;IACnD,MAAMC,UAAUJ,QAAQ,SAAS,CAAC,OAAO,CAACK;IAC1C,MAAMC,aAAaN,QAAQ,SAAS,CAAC,OAAO,CAACO;IAC7C,MAAMC,aAAaR,QAAQ,SAAS,CAAC,OAAO,CAACS;IAE7C,MAAMT,QAAQ,QAAQ,CAAC,oBAAoB,CAAC;QACxC,MAAMU,aAAaT,YAAY,iBAAiB,CAAC,gBAAgB;QACjE,MAAMU,WAAWV,YAAY,eAAe;QAE5C,MAAMW,SAASC,UAAU,IAAI,CAACF,SAAS,SAAS,EAAE,EAAE;QACpD,MAAMG,aAAa,MAAMV,QAAQ,OAAO,CAACQ;QAEzC,IAAIE,WAAW,MAAM,IACjB;QAGJ,MAAMC,OAAOD,WAAW,KAAK;QAE7B,IAAIE,iBAAgD,EAAE;QACtD,MAAMC,oBAAoB,MAAMf,iBAAiB,eAAe;QAChE,IAAIe,kBAAkB,IAAI,IACtBD,iBAAiBC,kBAAkB,KAAK;QAG5C,IAAIP,AAAe,uBAAfA,YAAmC;YACnC,MAAMQ,UAAUH,KAAK,IAAI,CAAC,MAAM,CAACI,CAAAA,MAAOA,AAAQ,2BAARA;YACxC,MAAMb,WAAW,OAAO,CAAC;gBACrB,IAAIS,KAAK,EAAE;gBACX,MAAMG;YACV;YAEA,MAAMhB,iBAAiB,iBAAiB,CAACc,gBAAgB;gBACrD,QAAQ;gBACR,MAAM;oBACF,IAAID,KAAK,EAAE;oBACX,MAAMG;gBACV;YACJ;YAEA;QACJ;QAEA,IAAIR,AAAe,oBAAfA,YAAgC;YAEhC,MAAMF,WAAW,OAAO,CAACO,KAAK,EAAE;YAEhC,MAAMb,iBAAiB,iBAAiB,CAACc,gBAAgB;gBACrD,QAAQ;gBACR,MAAM;oBACF,IAAID,KAAK,EAAE;oBACX,MAAMA,KAAK,IAAI;gBACnB;YACJ;YAEA;QACJ;QAGA,MAAMP,WAAW,OAAO,CAACO,KAAK,EAAE;QAEhC,MAAMb,iBAAiB,iBAAiB,CAACc,gBAAgB;YACrD,QAAQ;YACR,MAAM;gBACF,IAAID,KAAK,EAAE;gBACX,MAAMA,KAAK,IAAI;YACnB;QACJ;IACJ;AACJ"}
1
+ {"version":3,"file":"assetDelivery/threatDetection/processThreatScanResult.js","sources":["../../../src/assetDelivery/threatDetection/processThreatScanResult.ts"],"sourcesContent":["import type { ApiCoreContext } from \"@webiny/api-core/types/core.js\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { DeleteFileUseCase } from \"@webiny/api-file-manager/features/file/DeleteFile/index.js\";\nimport {\n WebsocketsListConnectionsUseCase,\n WebsocketsSendToConnectionsUseCase,\n ConnectionRegistry\n} from \"@webiny/api-websockets/exports/api.js\";\nimport { ObjectKey } from \"@webiny/api-file-manager/exports/api/file-manager/assetDelivery.js\";\nimport type { GuardDutyEvent } from \"./types.js\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\n\nexport const processThreatScanResult = async (\n context: ApiCoreContext,\n eventDetail: GuardDutyEvent\n) => {\n const objectKey = context.container.resolve(ObjectKey);\n const listConnections = context.container.resolve(WebsocketsListConnectionsUseCase);\n const sendToConnections = context.container.resolve(WebsocketsSendToConnectionsUseCase);\n const getFile = context.container.resolve(GetFileUseCase);\n const updateFile = context.container.resolve(UpdateFileUseCase);\n const deleteFile = context.container.resolve(DeleteFileUseCase);\n\n await context.security.withoutAuthorization(async () => {\n const scanStatus = eventDetail.scanResultDetails.scanResultStatus;\n const s3Object = eventDetail.s3ObjectDetails;\n\n const fileId = objectKey.from(s3Object.objectKey).id();\n const fileResult = await getFile.execute(fileId);\n\n if (fileResult.isFail()) {\n return;\n }\n\n const file = fileResult.value;\n\n let allConnections: ConnectionRegistry.Data[] = [];\n const connectionsResult = await listConnections.execute();\n if (connectionsResult.isOk()) {\n allConnections = connectionsResult.value;\n }\n\n if (scanStatus === \"NO_THREATS_FOUND\") {\n const newTags = file.tags.filter(tag => tag !== \"threatScanInProgress\");\n await updateFile.execute({\n id: file.id,\n tags: newTags\n });\n\n await sendToConnections.execute(allConnections, {\n action: \"fm.threatScan.noThreatFound\",\n data: {\n id: file.id,\n tags: newTags\n }\n });\n\n return;\n }\n\n if (scanStatus === \"THREATS_FOUND\") {\n // Delete the infected file.\n await deleteFile.execute(file.id);\n\n await sendToConnections.execute(allConnections, {\n action: \"fm.threatScan.threatDetected\",\n data: {\n id: file.id,\n name: file.name\n }\n });\n\n return;\n }\n\n // For all other outcomes, we delete the file, until better logic is implemented.\n await deleteFile.execute(file.id);\n\n await sendToConnections.execute(allConnections, {\n action: \"fm.threatScan.unsupported\",\n data: {\n id: file.id,\n name: file.name\n }\n });\n });\n};\n"],"names":["processThreatScanResult","context","eventDetail","objectKey","ObjectKey","listConnections","WebsocketsListConnectionsUseCase","sendToConnections","WebsocketsSendToConnectionsUseCase","getFile","GetFileUseCase","updateFile","UpdateFileUseCase","deleteFile","DeleteFileUseCase","scanStatus","s3Object","fileId","fileResult","file","allConnections","connectionsResult","newTags","tag"],"mappings":";;;;;AAYO,MAAMA,0BAA0B,OACnCC,SACAC;IAEA,MAAMC,YAAYF,QAAQ,SAAS,CAAC,OAAO,CAACG;IAC5C,MAAMC,kBAAkBJ,QAAQ,SAAS,CAAC,OAAO,CAACK;IAClD,MAAMC,oBAAoBN,QAAQ,SAAS,CAAC,OAAO,CAACO;IACpD,MAAMC,UAAUR,QAAQ,SAAS,CAAC,OAAO,CAACS;IAC1C,MAAMC,aAAaV,QAAQ,SAAS,CAAC,OAAO,CAACW;IAC7C,MAAMC,aAAaZ,QAAQ,SAAS,CAAC,OAAO,CAACa;IAE7C,MAAMb,QAAQ,QAAQ,CAAC,oBAAoB,CAAC;QACxC,MAAMc,aAAab,YAAY,iBAAiB,CAAC,gBAAgB;QACjE,MAAMc,WAAWd,YAAY,eAAe;QAE5C,MAAMe,SAASd,UAAU,IAAI,CAACa,SAAS,SAAS,EAAE,EAAE;QACpD,MAAME,aAAa,MAAMT,QAAQ,OAAO,CAACQ;QAEzC,IAAIC,WAAW,MAAM,IACjB;QAGJ,MAAMC,OAAOD,WAAW,KAAK;QAE7B,IAAIE,iBAA4C,EAAE;QAClD,MAAMC,oBAAoB,MAAMhB,gBAAgB,OAAO;QACvD,IAAIgB,kBAAkB,IAAI,IACtBD,iBAAiBC,kBAAkB,KAAK;QAG5C,IAAIN,AAAe,uBAAfA,YAAmC;YACnC,MAAMO,UAAUH,KAAK,IAAI,CAAC,MAAM,CAACI,CAAAA,MAAOA,AAAQ,2BAARA;YACxC,MAAMZ,WAAW,OAAO,CAAC;gBACrB,IAAIQ,KAAK,EAAE;gBACX,MAAMG;YACV;YAEA,MAAMf,kBAAkB,OAAO,CAACa,gBAAgB;gBAC5C,QAAQ;gBACR,MAAM;oBACF,IAAID,KAAK,EAAE;oBACX,MAAMG;gBACV;YACJ;YAEA;QACJ;QAEA,IAAIP,AAAe,oBAAfA,YAAgC;YAEhC,MAAMF,WAAW,OAAO,CAACM,KAAK,EAAE;YAEhC,MAAMZ,kBAAkB,OAAO,CAACa,gBAAgB;gBAC5C,QAAQ;gBACR,MAAM;oBACF,IAAID,KAAK,EAAE;oBACX,MAAMA,KAAK,IAAI;gBACnB;YACJ;YAEA;QACJ;QAGA,MAAMN,WAAW,OAAO,CAACM,KAAK,EAAE;QAEhC,MAAMZ,kBAAkB,OAAO,CAACa,gBAAgB;YAC5C,QAAQ;YACR,MAAM;gBACF,IAAID,KAAK,EAAE;gBACX,MAAMA,KAAK,IAAI;YACnB;QACJ;IACJ;AACJ"}
@@ -1,5 +1,8 @@
1
+ import type { ImageFormat } from "@webiny/api-file-manager/features/assetDelivery/transformation/index.js";
1
2
  export type AssetDeliveryParams = {
2
3
  imageResizeWidths?: number[];
4
+ /** Per-format encoder quality (1-100). Merged over the built-in defaults. */
5
+ imageQuality?: Partial<Record<ImageFormat, number>>;
3
6
  /**
4
7
  * BE CAREFUL!
5
8
  * Setting this to more than 1 hour may cause your URLs to still expire before the desired expiration time.
@@ -0,0 +1,8 @@
1
+ import { CompleteMultiPartUploadUseCase } from "@webiny/api-file-manager/features/upload/CompleteMultiPartUpload/index.js";
2
+ declare class CompleteMultiPartUploadUseCaseImpl implements CompleteMultiPartUploadUseCase.Interface {
3
+ execute(params: CompleteMultiPartUploadUseCase.Params): Promise<void>;
4
+ }
5
+ export declare const CompleteMultiPartUploadUseCaseImplementation: typeof CompleteMultiPartUploadUseCaseImpl & {
6
+ __abstraction: import("@webiny/di").Abstraction<import("@webiny/api-file-manager/features/upload/CompleteMultiPartUpload/abstractions").ICompleteMultiPartUploadUseCase>;
7
+ };
8
+ export {};
@@ -0,0 +1,51 @@
1
+ import { CompleteMultipartUploadCommand, ListPartsCommand, S3 } from "@webiny/aws-sdk/client-s3/index.js";
2
+ import { CompleteMultiPartUploadUseCase } from "@webiny/api-file-manager/features/upload/CompleteMultiPartUpload/index.js";
3
+ const EMPTY_MARKER_VALUES = [
4
+ void 0,
5
+ "0"
6
+ ];
7
+ class CompleteMultiPartUploadUseCaseImpl {
8
+ async execute(params) {
9
+ const bucket = String(process.env.S3_BUCKET);
10
+ const s3Client = new S3({
11
+ region: process.env.AWS_REGION
12
+ });
13
+ const uploadParams = {
14
+ Bucket: bucket,
15
+ Key: params.fileKey,
16
+ UploadId: params.uploadId
17
+ };
18
+ const allParts = await getAllUploadParts(s3Client, uploadParams);
19
+ const command = new CompleteMultipartUploadCommand({
20
+ ...uploadParams,
21
+ MultipartUpload: {
22
+ Parts: allParts
23
+ }
24
+ });
25
+ await s3Client.send(command);
26
+ }
27
+ }
28
+ async function getAllUploadParts(s3Client, params) {
29
+ const parts = [];
30
+ let marker;
31
+ while(true){
32
+ const { Parts, PartNumberMarker } = await s3Client.send(new ListPartsCommand({
33
+ ...params,
34
+ PartNumberMarker: marker
35
+ }));
36
+ if (Parts) Parts.forEach((part)=>parts.push(part));
37
+ marker = PartNumberMarker || void 0;
38
+ if (EMPTY_MARKER_VALUES.includes(marker)) break;
39
+ }
40
+ return parts.map((part)=>({
41
+ ETag: part.ETag,
42
+ PartNumber: part.PartNumber
43
+ }));
44
+ }
45
+ const CompleteMultiPartUploadUseCaseImplementation = CompleteMultiPartUploadUseCase.createImplementation({
46
+ implementation: CompleteMultiPartUploadUseCaseImpl,
47
+ dependencies: []
48
+ });
49
+ export { CompleteMultiPartUploadUseCaseImplementation };
50
+
51
+ //# sourceMappingURL=CompleteMultiPartUploadUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.js","sources":["../../../src/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.ts"],"sourcesContent":["import type { Part } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport type { ListPartsOutput } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { S3 } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { ListPartsCommand } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { CompleteMultipartUploadCommand } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { CompleteMultiPartUploadUseCase } from \"@webiny/api-file-manager/features/upload/CompleteMultiPartUpload/index.js\";\n\nconst EMPTY_MARKER_VALUES = [undefined, \"0\"];\n\nclass CompleteMultiPartUploadUseCaseImpl implements CompleteMultiPartUploadUseCase.Interface {\n public async execute(params: CompleteMultiPartUploadUseCase.Params): Promise<void> {\n const bucket = String(process.env.S3_BUCKET);\n const s3Client = new S3({ region: process.env.AWS_REGION });\n\n const uploadParams = {\n Bucket: bucket,\n Key: params.fileKey,\n UploadId: params.uploadId\n };\n\n const allParts = await getAllUploadParts(s3Client, uploadParams);\n\n const command = new CompleteMultipartUploadCommand({\n ...uploadParams,\n MultipartUpload: { Parts: allParts }\n });\n\n await s3Client.send(command);\n }\n}\n\ninterface GetAllUploadPartsParams {\n Bucket: string;\n Key: string;\n UploadId: string;\n}\n\nasync function getAllUploadParts(s3Client: S3, params: GetAllUploadPartsParams) {\n const parts: Part[] = [];\n\n let marker: string | undefined = undefined;\n while (true) {\n const { Parts, PartNumberMarker }: ListPartsOutput = await s3Client.send(\n new ListPartsCommand({\n ...params,\n PartNumberMarker: marker\n })\n );\n\n if (Parts) {\n Parts.forEach(part => parts.push(part));\n }\n\n marker = PartNumberMarker || undefined;\n if (EMPTY_MARKER_VALUES.includes(marker)) {\n break;\n }\n }\n\n return parts.map(part => ({\n ETag: part.ETag as string,\n PartNumber: part.PartNumber as number\n }));\n}\n\nexport const CompleteMultiPartUploadUseCaseImplementation =\n CompleteMultiPartUploadUseCase.createImplementation({\n implementation: CompleteMultiPartUploadUseCaseImpl,\n dependencies: []\n });\n"],"names":["EMPTY_MARKER_VALUES","undefined","CompleteMultiPartUploadUseCaseImpl","params","bucket","String","process","s3Client","S3","uploadParams","allParts","getAllUploadParts","command","CompleteMultipartUploadCommand","parts","marker","Parts","PartNumberMarker","ListPartsCommand","part","CompleteMultiPartUploadUseCaseImplementation","CompleteMultiPartUploadUseCase"],"mappings":";;AAOA,MAAMA,sBAAsB;IAACC;IAAW;CAAI;AAE5C,MAAMC;IACF,MAAa,QAAQC,MAA6C,EAAiB;QAC/E,MAAMC,SAASC,OAAOC,QAAQ,GAAG,CAAC,SAAS;QAC3C,MAAMC,WAAW,IAAIC,GAAG;YAAE,QAAQF,QAAQ,GAAG,CAAC,UAAU;QAAC;QAEzD,MAAMG,eAAe;YACjB,QAAQL;YACR,KAAKD,OAAO,OAAO;YACnB,UAAUA,OAAO,QAAQ;QAC7B;QAEA,MAAMO,WAAW,MAAMC,kBAAkBJ,UAAUE;QAEnD,MAAMG,UAAU,IAAIC,+BAA+B;YAC/C,GAAGJ,YAAY;YACf,iBAAiB;gBAAE,OAAOC;YAAS;QACvC;QAEA,MAAMH,SAAS,IAAI,CAACK;IACxB;AACJ;AAQA,eAAeD,kBAAkBJ,QAAY,EAAEJ,MAA+B;IAC1E,MAAMW,QAAgB,EAAE;IAExB,IAAIC;IACJ,MAAO,KAAM;QACT,MAAM,EAAEC,KAAK,EAAEC,gBAAgB,EAAE,GAAoB,MAAMV,SAAS,IAAI,CACpE,IAAIW,iBAAiB;YACjB,GAAGf,MAAM;YACT,kBAAkBY;QACtB;QAGJ,IAAIC,OACAA,MAAM,OAAO,CAACG,CAAAA,OAAQL,MAAM,IAAI,CAACK;QAGrCJ,SAASE,oBAAoBhB;QAC7B,IAAID,oBAAoB,QAAQ,CAACe,SAC7B;IAER;IAEA,OAAOD,MAAM,GAAG,CAACK,CAAAA,OAAS;YACtB,MAAMA,KAAK,IAAI;YACf,YAAYA,KAAK,UAAU;QAC/B;AACJ;AAEO,MAAMC,+CACTC,+BAA+B,oBAAoB,CAAC;IAChD,gBAAgBnB;IAChB,cAAc,EAAE;AACpB"}
@@ -0,0 +1,4 @@
1
+ export declare const CompleteMultiPartUploadFeature: {
2
+ name: string;
3
+ register(container: import("@webiny/di").Container): void;
4
+ };
@@ -0,0 +1,11 @@
1
+ import { createFeature } from "@webiny/feature/api";
2
+ import { CompleteMultiPartUploadUseCaseImplementation } from "./CompleteMultiPartUploadUseCase.js";
3
+ const CompleteMultiPartUploadFeature = createFeature({
4
+ name: "FileManagerS3/CompleteMultiPartUpload",
5
+ register (container) {
6
+ container.register(CompleteMultiPartUploadUseCaseImplementation);
7
+ }
8
+ });
9
+ export { CompleteMultiPartUploadFeature };
10
+
11
+ //# sourceMappingURL=feature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/CompleteMultiPartUpload/feature.js","sources":["../../../src/features/CompleteMultiPartUpload/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { CompleteMultiPartUploadUseCaseImplementation } from \"./CompleteMultiPartUploadUseCase.js\";\n\nexport const CompleteMultiPartUploadFeature = createFeature({\n name: \"FileManagerS3/CompleteMultiPartUpload\",\n register(container) {\n container.register(CompleteMultiPartUploadUseCaseImplementation);\n }\n});\n"],"names":["CompleteMultiPartUploadFeature","createFeature","container","CompleteMultiPartUploadUseCaseImplementation"],"mappings":";;AAGO,MAAMA,iCAAiCC,cAAc;IACxD,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC;IACvB;AACJ"}
@@ -0,0 +1,9 @@
1
+ import { CreateMultiPartUploadUseCase } from "@webiny/api-file-manager/features/upload/CreateMultiPartUpload/index.js";
2
+ import type { CreateMultiPartUploadResult } from "@webiny/api-file-manager/features/upload/types.js";
3
+ declare class CreateMultiPartUploadUseCaseImpl implements CreateMultiPartUploadUseCase.Interface {
4
+ execute(params: CreateMultiPartUploadUseCase.Params): Promise<CreateMultiPartUploadResult>;
5
+ }
6
+ export declare const CreateMultiPartUploadUseCaseImplementation: typeof CreateMultiPartUploadUseCaseImpl & {
7
+ __abstraction: import("@webiny/di").Abstraction<import("@webiny/api-file-manager/features/upload/CreateMultiPartUpload/abstractions").ICreateMultiPartUploadUseCase>;
8
+ };
9
+ export {};
@@ -0,0 +1,40 @@
1
+ import { S3, UploadPartCommand, getSignedUrl } from "@webiny/aws-sdk/client-s3/index.js";
2
+ import { CreateMultiPartUploadUseCase } from "@webiny/api-file-manager/features/upload/CreateMultiPartUpload/index.js";
3
+ class CreateMultiPartUploadUseCaseImpl {
4
+ async execute(params) {
5
+ const { file, numberOfParts } = params;
6
+ const bucket = String(process.env.S3_BUCKET);
7
+ const s3Client = new S3({
8
+ region: process.env.AWS_REGION
9
+ });
10
+ const s3Params = {
11
+ Bucket: bucket,
12
+ Key: file.key
13
+ };
14
+ const { UploadId } = await s3Client.createMultipartUpload(s3Params);
15
+ const parts = await Promise.all(Array.from({
16
+ length: numberOfParts
17
+ }).map((_, index)=>getSignedUrl(s3Client, new UploadPartCommand({
18
+ ...s3Params,
19
+ UploadId,
20
+ PartNumber: index + 1
21
+ }), {
22
+ expiresIn: 86400
23
+ }).then((url)=>({
24
+ url,
25
+ partNumber: index + 1
26
+ }))));
27
+ return {
28
+ file,
29
+ uploadId: UploadId,
30
+ parts
31
+ };
32
+ }
33
+ }
34
+ const CreateMultiPartUploadUseCaseImplementation = CreateMultiPartUploadUseCase.createImplementation({
35
+ implementation: CreateMultiPartUploadUseCaseImpl,
36
+ dependencies: []
37
+ });
38
+ export { CreateMultiPartUploadUseCaseImplementation };
39
+
40
+ //# sourceMappingURL=CreateMultiPartUploadUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.js","sources":["../../../src/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.ts"],"sourcesContent":["import { S3 } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { getSignedUrl } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { UploadPartCommand } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport { CreateMultiPartUploadUseCase } from \"@webiny/api-file-manager/features/upload/CreateMultiPartUpload/index.js\";\nimport type { CreateMultiPartUploadResult } from \"@webiny/api-file-manager/features/upload/types.js\";\n\nclass CreateMultiPartUploadUseCaseImpl implements CreateMultiPartUploadUseCase.Interface {\n public async execute(\n params: CreateMultiPartUploadUseCase.Params\n ): Promise<CreateMultiPartUploadResult> {\n const { file, numberOfParts } = params;\n\n const bucket = String(process.env.S3_BUCKET);\n const s3Client = new S3({ region: process.env.AWS_REGION });\n const s3Params = { Bucket: bucket, Key: file.key };\n\n const { UploadId } = await s3Client.createMultipartUpload(s3Params);\n\n const parts = await Promise.all(\n Array.from({ length: numberOfParts }).map((_, index) => {\n return getSignedUrl(\n s3Client,\n new UploadPartCommand({ ...s3Params, UploadId, PartNumber: index + 1 }),\n { expiresIn: 86400 }\n ).then(url => ({\n url,\n partNumber: index + 1\n }));\n })\n );\n\n return {\n file,\n uploadId: UploadId as string,\n parts\n };\n }\n}\n\nexport const CreateMultiPartUploadUseCaseImplementation =\n CreateMultiPartUploadUseCase.createImplementation({\n implementation: CreateMultiPartUploadUseCaseImpl,\n dependencies: []\n });\n"],"names":["CreateMultiPartUploadUseCaseImpl","params","file","numberOfParts","bucket","String","process","s3Client","S3","s3Params","UploadId","parts","Promise","Array","_","index","getSignedUrl","UploadPartCommand","url","CreateMultiPartUploadUseCaseImplementation","CreateMultiPartUploadUseCase"],"mappings":";;AAMA,MAAMA;IACF,MAAa,QACTC,MAA2C,EACP;QACpC,MAAM,EAAEC,IAAI,EAAEC,aAAa,EAAE,GAAGF;QAEhC,MAAMG,SAASC,OAAOC,QAAQ,GAAG,CAAC,SAAS;QAC3C,MAAMC,WAAW,IAAIC,GAAG;YAAE,QAAQF,QAAQ,GAAG,CAAC,UAAU;QAAC;QACzD,MAAMG,WAAW;YAAE,QAAQL;YAAQ,KAAKF,KAAK,GAAG;QAAC;QAEjD,MAAM,EAAEQ,QAAQ,EAAE,GAAG,MAAMH,SAAS,qBAAqB,CAACE;QAE1D,MAAME,QAAQ,MAAMC,QAAQ,GAAG,CAC3BC,MAAM,IAAI,CAAC;YAAE,QAAQV;QAAc,GAAG,GAAG,CAAC,CAACW,GAAGC,QACnCC,aACHT,UACA,IAAIU,kBAAkB;gBAAE,GAAGR,QAAQ;gBAAEC;gBAAU,YAAYK,QAAQ;YAAE,IACrE;gBAAE,WAAW;YAAM,GACrB,IAAI,CAACG,CAAAA,MAAQ;oBACXA;oBACA,YAAYH,QAAQ;gBACxB;QAIR,OAAO;YACHb;YACA,UAAUQ;YACVC;QACJ;IACJ;AACJ;AAEO,MAAMQ,6CACTC,6BAA6B,oBAAoB,CAAC;IAC9C,gBAAgBpB;IAChB,cAAc,EAAE;AACpB"}
@@ -0,0 +1,4 @@
1
+ export declare const CreateMultiPartUploadFeature: {
2
+ name: string;
3
+ register(container: import("@webiny/di").Container): void;
4
+ };