@aws-amplify/storage 4.4.3 → 4.4.4-in-app-messaging.35

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 (78) hide show
  1. package/CHANGELOG.md +28 -352
  2. package/dist/aws-amplify-storage.js +6523 -5062
  3. package/dist/aws-amplify-storage.js.map +1 -1
  4. package/dist/aws-amplify-storage.min.js +5 -5
  5. package/dist/aws-amplify-storage.min.js.map +1 -1
  6. package/lib/Storage.d.ts +3 -1
  7. package/lib/Storage.js +25 -21
  8. package/lib/Storage.js.map +1 -1
  9. package/lib/common/S3ClientUtils.d.ts +33 -0
  10. package/lib/common/S3ClientUtils.js +179 -0
  11. package/lib/common/S3ClientUtils.js.map +1 -0
  12. package/lib/common/StorageConstants.d.ts +4 -0
  13. package/lib/common/StorageConstants.js +10 -0
  14. package/lib/common/StorageConstants.js.map +1 -0
  15. package/lib/common/StorageErrorStrings.d.ts +5 -1
  16. package/lib/common/StorageErrorStrings.js +5 -0
  17. package/lib/common/StorageErrorStrings.js.map +1 -1
  18. package/lib/common/StorageUtils.d.ts +4 -0
  19. package/lib/common/StorageUtils.js +41 -0
  20. package/lib/common/StorageUtils.js.map +1 -0
  21. package/lib/providers/AWSS3Provider.d.ts +9 -6
  22. package/lib/providers/AWSS3Provider.js +197 -149
  23. package/lib/providers/AWSS3Provider.js.map +1 -1
  24. package/lib/providers/AWSS3ProviderManagedUpload.d.ts +3 -10
  25. package/lib/providers/AWSS3ProviderManagedUpload.js +41 -109
  26. package/lib/providers/AWSS3ProviderManagedUpload.js.map +1 -1
  27. package/lib/providers/AWSS3UploadTask.d.ts +107 -0
  28. package/lib/providers/AWSS3UploadTask.js +610 -0
  29. package/lib/providers/AWSS3UploadTask.js.map +1 -0
  30. package/lib/providers/axios-http-handler.d.ts +5 -1
  31. package/lib/providers/axios-http-handler.js +28 -5
  32. package/lib/providers/axios-http-handler.js.map +1 -1
  33. package/lib/types/AWSS3Provider.d.ts +24 -4
  34. package/lib/types/Provider.d.ts +7 -1
  35. package/lib/types/Storage.d.ts +8 -8
  36. package/lib-esm/Storage.d.ts +3 -1
  37. package/lib-esm/Storage.js +25 -21
  38. package/lib-esm/Storage.js.map +1 -1
  39. package/lib-esm/common/S3ClientUtils.d.ts +33 -0
  40. package/lib-esm/common/S3ClientUtils.js +177 -0
  41. package/lib-esm/common/S3ClientUtils.js.map +1 -0
  42. package/lib-esm/common/StorageConstants.d.ts +4 -0
  43. package/lib-esm/common/StorageConstants.js +8 -0
  44. package/lib-esm/common/StorageConstants.js.map +1 -0
  45. package/lib-esm/common/StorageErrorStrings.d.ts +5 -1
  46. package/lib-esm/common/StorageErrorStrings.js +5 -0
  47. package/lib-esm/common/StorageErrorStrings.js.map +1 -1
  48. package/lib-esm/common/StorageUtils.d.ts +4 -0
  49. package/lib-esm/common/StorageUtils.js +39 -0
  50. package/lib-esm/common/StorageUtils.js.map +1 -0
  51. package/lib-esm/providers/AWSS3Provider.d.ts +9 -6
  52. package/lib-esm/providers/AWSS3Provider.js +192 -144
  53. package/lib-esm/providers/AWSS3Provider.js.map +1 -1
  54. package/lib-esm/providers/AWSS3ProviderManagedUpload.d.ts +3 -10
  55. package/lib-esm/providers/AWSS3ProviderManagedUpload.js +44 -112
  56. package/lib-esm/providers/AWSS3ProviderManagedUpload.js.map +1 -1
  57. package/lib-esm/providers/AWSS3UploadTask.d.ts +107 -0
  58. package/lib-esm/providers/AWSS3UploadTask.js +605 -0
  59. package/lib-esm/providers/AWSS3UploadTask.js.map +1 -0
  60. package/lib-esm/providers/axios-http-handler.d.ts +5 -1
  61. package/lib-esm/providers/axios-http-handler.js +28 -5
  62. package/lib-esm/providers/axios-http-handler.js.map +1 -1
  63. package/lib-esm/types/AWSS3Provider.d.ts +24 -4
  64. package/lib-esm/types/Provider.d.ts +7 -1
  65. package/lib-esm/types/Storage.d.ts +8 -8
  66. package/package.json +3 -3
  67. package/src/Storage.ts +85 -27
  68. package/src/common/S3ClientUtils.ts +168 -0
  69. package/src/common/StorageConstants.ts +10 -0
  70. package/src/common/StorageErrorStrings.ts +5 -0
  71. package/src/common/StorageUtils.ts +51 -0
  72. package/src/providers/AWSS3Provider.ts +251 -88
  73. package/src/providers/AWSS3ProviderManagedUpload.ts +346 -397
  74. package/src/providers/AWSS3UploadTask.ts +543 -0
  75. package/src/providers/axios-http-handler.ts +221 -186
  76. package/src/types/AWSS3Provider.ts +48 -7
  77. package/src/types/Provider.ts +18 -3
  78. package/src/types/Storage.ts +9 -9
@@ -1,397 +1,346 @@
1
- /*
2
- * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5
- * the License. A copy of the License is located at
6
- *
7
- * http://aws.amazon.com/apache2.0/
8
- *
9
- * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10
- * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
11
- * and limitations under the License.
12
- */
13
-
14
- import {
15
- ConsoleLogger as Logger,
16
- getAmplifyUserAgent,
17
- Credentials,
18
- } from '@aws-amplify/core';
19
- import {
20
- S3Client,
21
- PutObjectCommand,
22
- PutObjectRequest,
23
- CreateMultipartUploadCommand,
24
- UploadPartCommand,
25
- CompleteMultipartUploadCommand,
26
- CompleteMultipartUploadCommandInput,
27
- ListPartsCommand,
28
- AbortMultipartUploadCommand,
29
- CompletedPart,
30
- } from '@aws-sdk/client-s3';
31
- import { AxiosHttpHandler, SEND_UPLOAD_PROGRESS_EVENT, SEND_DOWNLOAD_PROGRESS_EVENT } from './axios-http-handler';
32
- import * as events from 'events';
33
-
34
- const logger = new Logger('AWSS3ProviderManagedUpload');
35
-
36
- const localTestingStorageEndpoint = 'http://localhost:20005';
37
-
38
- const SET_CONTENT_LENGTH_HEADER = 'contentLengthMiddleware';
39
- export declare interface Part {
40
- bodyPart: any;
41
- partNumber: number;
42
- emitter: events.EventEmitter;
43
- etag?: string;
44
- _lastUploadedBytes: number;
45
- }
46
-
47
- export class AWSS3ProviderManagedUpload {
48
- // Defaults
49
- protected minPartSize = 5 * 1024 * 1024; // in MB
50
- private queueSize = 4;
51
-
52
- // Data for current upload
53
- private body = null;
54
- private params = null;
55
- private opts = null;
56
- private completedParts: CompletedPart[] = [];
57
- private cancel = false;
58
-
59
- // Progress reporting
60
- private bytesUploaded = 0;
61
- private totalBytesToUpload = 0;
62
- private emitter: events.EventEmitter = null;
63
-
64
- constructor(params: PutObjectRequest, opts, emitter: events.EventEmitter) {
65
- this.params = params;
66
- this.opts = opts;
67
- this.emitter = emitter;
68
- }
69
-
70
- public async upload() {
71
- this.body = await this.validateAndSanitizeBody(this.params.Body);
72
- this.totalBytesToUpload = this.byteLength(this.body);
73
- if (this.totalBytesToUpload <= this.minPartSize) {
74
- // Multipart upload is not required. Upload the sanitized body as is
75
- this.params.Body = this.body;
76
- const putObjectCommand = new PutObjectCommand(this.params);
77
- const s3 = await this._createNewS3Client(this.opts, this.emitter);
78
- return s3.send(putObjectCommand);
79
- } else {
80
- // Step 1: Initiate the multi part upload
81
- const uploadId = await this.createMultiPartUpload();
82
-
83
- // Step 2: Upload chunks in parallel as requested
84
- const numberOfPartsToUpload = Math.ceil(
85
- this.totalBytesToUpload / this.minPartSize
86
- );
87
-
88
- const parts: Part[] = this.createParts();
89
- for (
90
- let start = 0;
91
- start < numberOfPartsToUpload;
92
- start += this.queueSize
93
- ) {
94
- /** This first block will try to cancel the upload if the cancel
95
- * request came before any parts uploads have started.
96
- **/
97
- await this.checkIfUploadCancelled(uploadId);
98
-
99
- // Upload as many as `queueSize` parts simultaneously
100
- await this.uploadParts(
101
- uploadId,
102
- parts.slice(start, start + this.queueSize)
103
- );
104
-
105
- /** Call cleanup a second time in case there were part upload requests
106
- * in flight. This is to ensure that all parts are cleaned up.
107
- */
108
- await this.checkIfUploadCancelled(uploadId);
109
- }
110
-
111
- parts.map(part => {
112
- this.removeEventListener(part);
113
- });
114
-
115
- // Step 3: Finalize the upload such that S3 can recreate the file
116
- return await this.finishMultiPartUpload(uploadId);
117
- }
118
- }
119
-
120
- private createParts(): Part[] {
121
- const parts: Part[] = [];
122
- for (let bodyStart = 0; bodyStart < this.totalBytesToUpload; ) {
123
- const bodyEnd = Math.min(
124
- bodyStart + this.minPartSize,
125
- this.totalBytesToUpload
126
- );
127
- parts.push({
128
- bodyPart: this.body.slice(bodyStart, bodyEnd),
129
- partNumber: parts.length + 1,
130
- emitter: new events.EventEmitter(),
131
- _lastUploadedBytes: 0,
132
- });
133
- bodyStart += this.minPartSize;
134
- }
135
- return parts;
136
- }
137
-
138
- private async createMultiPartUpload() {
139
- const createMultiPartUploadCommand = new CreateMultipartUploadCommand(
140
- this.params
141
- );
142
- const s3 = await this._createNewS3Client(this.opts);
143
-
144
- // @aws-sdk/client-s3 seems to be ignoring the `ContentType` parameter, so we
145
- // are explicitly adding it via middleware.
146
- // https://github.com/aws/aws-sdk-js-v3/issues/2000
147
- s3.middlewareStack.add(
148
- next => (args: any) => {
149
- if (
150
- this.params.ContentType &&
151
- args &&
152
- args.request &&
153
- args.request.headers
154
- ) {
155
- args.request.headers['Content-Type'] = this.params.ContentType;
156
- }
157
- return next(args);
158
- },
159
- {
160
- step: 'build',
161
- }
162
- );
163
-
164
- const response = await s3.send(createMultiPartUploadCommand);
165
- logger.debug(response.UploadId);
166
- return response.UploadId;
167
- }
168
-
169
- /**
170
- * @private Not to be extended outside of tests
171
- * @VisibleFotTesting
172
- */
173
- protected async uploadParts(uploadId: string, parts: Part[]) {
174
- try {
175
- const allResults = await Promise.all(
176
- parts.map(async part => {
177
- this.setupEventListener(part);
178
- const s3 = await this._createNewS3Client(this.opts, part.emitter);
179
- const { Key, Bucket, SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } = this.params;
180
- return s3.send(
181
- new UploadPartCommand({
182
- PartNumber: part.partNumber,
183
- Body: part.bodyPart,
184
- UploadId: uploadId,
185
- Key,
186
- Bucket,
187
- ...(SSECustomerAlgorithm && { SSECustomerAlgorithm }),
188
- ...(SSECustomerKey && { SSECustomerKey }),
189
- ...(SSECustomerKeyMD5 && { SSECustomerKeyMD5 })
190
-
191
- })
192
- );
193
- })
194
- );
195
- // The order of resolved promises is the same as input promise order.
196
- for (let i = 0; i < allResults.length; i++) {
197
- this.completedParts.push({
198
- PartNumber: parts[i].partNumber,
199
- ETag: allResults[i].ETag,
200
- });
201
- }
202
- } catch (error) {
203
- logger.error(
204
- 'error happened while uploading a part. Cancelling the multipart upload',
205
- error
206
- );
207
- this.cancelUpload();
208
- return;
209
- }
210
- }
211
-
212
- private async finishMultiPartUpload(uploadId: string) {
213
- const input: CompleteMultipartUploadCommandInput = {
214
- Bucket: this.params.Bucket,
215
- Key: this.params.Key,
216
- UploadId: uploadId,
217
- MultipartUpload: { Parts: this.completedParts },
218
- };
219
- const completeUploadCommand = new CompleteMultipartUploadCommand(input);
220
- const s3 = await this._createNewS3Client(this.opts);
221
- try {
222
- const data = await s3.send(completeUploadCommand);
223
- return data.Key;
224
- } catch (error) {
225
- logger.error(
226
- 'error happened while finishing the upload. Cancelling the multipart upload',
227
- error
228
- );
229
- this.cancelUpload();
230
- return;
231
- }
232
- }
233
-
234
- private async checkIfUploadCancelled(uploadId: string) {
235
- if (this.cancel) {
236
- let errorMessage = 'Upload was cancelled.';
237
- try {
238
- await this.cleanup(uploadId);
239
- } catch (error) {
240
- errorMessage += ` ${error.message}`;
241
- }
242
- throw new Error(errorMessage);
243
- }
244
- }
245
-
246
- public cancelUpload() {
247
- this.cancel = true;
248
- }
249
-
250
- private async cleanup(uploadId: string) {
251
- // Reset this's state
252
- this.body = null;
253
- this.completedParts = [];
254
- this.bytesUploaded = 0;
255
- this.totalBytesToUpload = 0;
256
-
257
- const input = {
258
- Bucket: this.params.Bucket,
259
- Key: this.params.Key,
260
- UploadId: uploadId,
261
- };
262
-
263
- const s3 = await this._createNewS3Client(this.opts);
264
- await s3.send(new AbortMultipartUploadCommand(input));
265
-
266
- // verify that all parts are removed.
267
- const data = await s3.send(new ListPartsCommand(input));
268
-
269
- if (data && data.Parts && data.Parts.length > 0) {
270
- throw new Error('Multi Part upload clean up failed');
271
- }
272
- }
273
-
274
- private removeEventListener(part: Part) {
275
- part.emitter.removeAllListeners(SEND_UPLOAD_PROGRESS_EVENT);
276
- part.emitter.removeAllListeners(SEND_DOWNLOAD_PROGRESS_EVENT);
277
- }
278
-
279
- private setupEventListener(part: Part) {
280
- part.emitter.on(SEND_UPLOAD_PROGRESS_EVENT, progress => {
281
- this.progressChanged(
282
- part.partNumber,
283
- progress.loaded - part._lastUploadedBytes
284
- );
285
- part._lastUploadedBytes = progress.loaded;
286
- });
287
- }
288
-
289
- private progressChanged(partNumber: number, incrementalUpdate: number) {
290
- this.bytesUploaded += incrementalUpdate;
291
- this.emitter.emit(SEND_UPLOAD_PROGRESS_EVENT, {
292
- loaded: this.bytesUploaded,
293
- total: this.totalBytesToUpload,
294
- part: partNumber,
295
- key: this.params.Key,
296
- });
297
- }
298
-
299
- private byteLength(input: any) {
300
- if (input === null || input === undefined) return 0;
301
- if (typeof input.byteLength === 'number') {
302
- return input.byteLength;
303
- } else if (typeof input.length === 'number') {
304
- return input.length;
305
- } else if (typeof input.size === 'number') {
306
- return input.size;
307
- } else if (typeof input.path === 'string') {
308
- /* NodeJs Support
309
- return require('fs').lstatSync(input.path).size;
310
- */
311
- } else {
312
- throw new Error('Cannot determine length of ' + input);
313
- }
314
- }
315
-
316
- private async validateAndSanitizeBody(body: any): Promise<any> {
317
- if (this.isGenericObject(body)) {
318
- // Any javascript object
319
- return JSON.stringify(body);
320
- } else {
321
- // Files, arrayBuffer etc
322
- return body;
323
- }
324
- /* TODO: streams and files for nodejs
325
- if (
326
- typeof body.path === 'string' &&
327
- require('fs').lstatSync(body.path).size > 0
328
- ) {
329
- return body;
330
- } */
331
- }
332
-
333
- private isGenericObject(body: any): body is Object {
334
- if (body !== null && typeof body === 'object') {
335
- try {
336
- return !(this.byteLength(body) >= 0);
337
- } catch (error) {
338
- // If we cannot determine the length of the body, consider it
339
- // as a generic object and upload a stringified version of it
340
- return true;
341
- }
342
- }
343
- return false;
344
- }
345
-
346
- /**
347
- * @private
348
- * creates an S3 client with new V3 aws sdk
349
- */
350
- protected async _createNewS3Client(config, emitter?: events.EventEmitter) {
351
- const credentials = await this._getCredentials();
352
- const {
353
- region,
354
- dangerouslyConnectToHttpEndpointForTesting,
355
- cancelTokenSource,
356
- useAccelerateEndpoint
357
- } = config;
358
- let localTestingConfig = {};
359
-
360
- if (dangerouslyConnectToHttpEndpointForTesting) {
361
- localTestingConfig = {
362
- endpoint: localTestingStorageEndpoint,
363
- tls: false,
364
- bucketEndpoint: false,
365
- forcePathStyle: true,
366
- };
367
- }
368
-
369
- const client = new S3Client({
370
- region,
371
- credentials,
372
- useAccelerateEndpoint,
373
- ...localTestingConfig,
374
- requestHandler: new AxiosHttpHandler({}, emitter, cancelTokenSource),
375
- customUserAgent: getAmplifyUserAgent(),
376
- });
377
- client.middlewareStack.remove(SET_CONTENT_LENGTH_HEADER);
378
- return client;
379
- }
380
-
381
- /**
382
- * @private
383
- */
384
- _getCredentials() {
385
- return Credentials.get()
386
- .then(credentials => {
387
- if (!credentials) return false;
388
- const cred = Credentials.shear(credentials);
389
- logger.debug('set credentials for storage', cred);
390
- return cred;
391
- })
392
- .catch(error => {
393
- logger.warn('ensure credentials error', error);
394
- return false;
395
- });
396
- }
397
- }
1
+ /*
2
+ * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5
+ * the License. A copy of the License is located at
6
+ *
7
+ * http://aws.amazon.com/apache2.0/
8
+ *
9
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
11
+ * and limitations under the License.
12
+ */
13
+
14
+ import { ConsoleLogger as Logger } from '@aws-amplify/core';
15
+ import {
16
+ PutObjectCommand,
17
+ PutObjectRequest,
18
+ CreateMultipartUploadCommand,
19
+ UploadPartCommand,
20
+ CompleteMultipartUploadCommand,
21
+ CompleteMultipartUploadCommandInput,
22
+ ListPartsCommand,
23
+ AbortMultipartUploadCommand,
24
+ CompletedPart,
25
+ S3Client,
26
+ } from '@aws-sdk/client-s3';
27
+ import {
28
+ SEND_UPLOAD_PROGRESS_EVENT,
29
+ SEND_DOWNLOAD_PROGRESS_EVENT,
30
+ AxiosHttpHandlerOptions,
31
+ } from './axios-http-handler';
32
+ import * as events from 'events';
33
+ import {
34
+ createPrefixMiddleware,
35
+ prefixMiddlewareOptions,
36
+ autoAdjustClockskewMiddleware,
37
+ autoAdjustClockskewMiddlewareOptions,
38
+ createS3Client,
39
+ } from '../common/S3ClientUtils';
40
+
41
+ const logger = new Logger('AWSS3ProviderManagedUpload');
42
+
43
+ export declare interface Part {
44
+ bodyPart: any;
45
+ partNumber: number;
46
+ emitter: events.EventEmitter;
47
+ etag?: string;
48
+ _lastUploadedBytes: number;
49
+ }
50
+
51
+ export class AWSS3ProviderManagedUpload {
52
+ // Defaults
53
+ protected minPartSize = 5 * 1024 * 1024; // in MB
54
+ private queueSize = 4;
55
+
56
+ // Data for current upload
57
+ private body = null;
58
+ private params: PutObjectRequest = null;
59
+ private opts = null;
60
+ private completedParts: CompletedPart[] = [];
61
+ private cancel = false;
62
+ private s3client: S3Client;
63
+
64
+ // Progress reporting
65
+ private bytesUploaded = 0;
66
+ private totalBytesToUpload = 0;
67
+ private emitter: events.EventEmitter = null;
68
+
69
+ constructor(params: PutObjectRequest, opts, emitter: events.EventEmitter) {
70
+ this.params = params;
71
+ this.opts = opts;
72
+ this.emitter = emitter;
73
+ this.s3client = this._createNewS3Client(opts, emitter);
74
+ }
75
+
76
+ public async upload() {
77
+ this.body = await this.validateAndSanitizeBody(this.params.Body);
78
+ this.totalBytesToUpload = this.byteLength(this.body);
79
+ if (this.totalBytesToUpload <= this.minPartSize) {
80
+ // Multipart upload is not required. Upload the sanitized body as is
81
+ this.params.Body = this.body;
82
+ const putObjectCommand = new PutObjectCommand(this.params);
83
+ return this.s3client.send(putObjectCommand);
84
+ } else {
85
+ // Step 1: Initiate the multi part upload
86
+ const uploadId = await this.createMultiPartUpload();
87
+
88
+ // Step 2: Upload chunks in parallel as requested
89
+ const numberOfPartsToUpload = Math.ceil(
90
+ this.totalBytesToUpload / this.minPartSize
91
+ );
92
+
93
+ const parts: Part[] = this.createParts();
94
+ for (
95
+ let start = 0;
96
+ start < numberOfPartsToUpload;
97
+ start += this.queueSize
98
+ ) {
99
+ /** This first block will try to cancel the upload if the cancel
100
+ * request came before any parts uploads have started.
101
+ **/
102
+ await this.checkIfUploadCancelled(uploadId);
103
+
104
+ // Upload as many as `queueSize` parts simultaneously
105
+ await this.uploadParts(
106
+ uploadId,
107
+ parts.slice(start, start + this.queueSize)
108
+ );
109
+
110
+ /** Call cleanup a second time in case there were part upload requests
111
+ * in flight. This is to ensure that all parts are cleaned up.
112
+ */
113
+ await this.checkIfUploadCancelled(uploadId);
114
+ }
115
+
116
+ parts.map(part => {
117
+ this.removeEventListener(part);
118
+ });
119
+
120
+ // Step 3: Finalize the upload such that S3 can recreate the file
121
+ return await this.finishMultiPartUpload(uploadId);
122
+ }
123
+ }
124
+
125
+ private createParts(): Part[] {
126
+ const parts: Part[] = [];
127
+ for (let bodyStart = 0; bodyStart < this.totalBytesToUpload; ) {
128
+ const bodyEnd = Math.min(
129
+ bodyStart + this.minPartSize,
130
+ this.totalBytesToUpload
131
+ );
132
+ parts.push({
133
+ bodyPart: this.body.slice(bodyStart, bodyEnd),
134
+ partNumber: parts.length + 1,
135
+ emitter: new events.EventEmitter(),
136
+ _lastUploadedBytes: 0,
137
+ });
138
+ bodyStart += this.minPartSize;
139
+ }
140
+ return parts;
141
+ }
142
+
143
+ private async createMultiPartUpload() {
144
+ const createMultiPartUploadCommand = new CreateMultipartUploadCommand(
145
+ this.params
146
+ );
147
+ const response = await this.s3client.send(createMultiPartUploadCommand);
148
+ logger.debug(response.UploadId);
149
+ return response.UploadId;
150
+ }
151
+
152
+ /**
153
+ * @private Not to be extended outside of tests
154
+ * @VisibleFotTesting
155
+ */
156
+ protected async uploadParts(uploadId: string, parts: Part[]) {
157
+ try {
158
+ const allResults = await Promise.all(
159
+ parts.map(async part => {
160
+ this.setupEventListener(part);
161
+ const options: AxiosHttpHandlerOptions = { emitter: part.emitter };
162
+ const {
163
+ Key,
164
+ Bucket,
165
+ SSECustomerAlgorithm,
166
+ SSECustomerKey,
167
+ SSECustomerKeyMD5,
168
+ } = this.params;
169
+ const res = await this.s3client.send(
170
+ new UploadPartCommand({
171
+ PartNumber: part.partNumber,
172
+ Body: part.bodyPart,
173
+ UploadId: uploadId,
174
+ Key,
175
+ Bucket,
176
+ ...(SSECustomerAlgorithm && { SSECustomerAlgorithm }),
177
+ ...(SSECustomerKey && { SSECustomerKey }),
178
+ ...(SSECustomerKeyMD5 && { SSECustomerKeyMD5 }),
179
+ }),
180
+ options
181
+ );
182
+ return res;
183
+ })
184
+ );
185
+ // The order of resolved promises is the same as input promise order.
186
+ for (let i = 0; i < allResults.length; i++) {
187
+ this.completedParts.push({
188
+ PartNumber: parts[i].partNumber,
189
+ ETag: allResults[i].ETag,
190
+ });
191
+ }
192
+ } catch (error) {
193
+ logger.error(
194
+ 'error happened while uploading a part. Cancelling the multipart upload',
195
+ error
196
+ );
197
+ this.cancelUpload();
198
+ return;
199
+ }
200
+ }
201
+
202
+ private async finishMultiPartUpload(uploadId: string) {
203
+ const input: CompleteMultipartUploadCommandInput = {
204
+ Bucket: this.params.Bucket,
205
+ Key: this.params.Key,
206
+ UploadId: uploadId,
207
+ MultipartUpload: { Parts: this.completedParts },
208
+ };
209
+ const completeUploadCommand = new CompleteMultipartUploadCommand(input);
210
+ try {
211
+ const data = await this.s3client.send(completeUploadCommand);
212
+ return data.Key;
213
+ } catch (error) {
214
+ logger.error(
215
+ 'error happened while finishing the upload. Cancelling the multipart upload',
216
+ error
217
+ );
218
+ this.cancelUpload();
219
+ return;
220
+ }
221
+ }
222
+
223
+ private async checkIfUploadCancelled(uploadId: string) {
224
+ if (this.cancel) {
225
+ let errorMessage = 'Upload was cancelled.';
226
+ try {
227
+ await this.cleanup(uploadId);
228
+ } catch (error) {
229
+ errorMessage += ` ${error.message}`;
230
+ }
231
+ throw new Error(errorMessage);
232
+ }
233
+ }
234
+
235
+ public cancelUpload() {
236
+ this.cancel = true;
237
+ }
238
+
239
+ private async cleanup(uploadId: string) {
240
+ // Reset this's state
241
+ this.body = null;
242
+ this.completedParts = [];
243
+ this.bytesUploaded = 0;
244
+ this.totalBytesToUpload = 0;
245
+
246
+ const input = {
247
+ Bucket: this.params.Bucket,
248
+ Key: this.params.Key,
249
+ UploadId: uploadId,
250
+ };
251
+
252
+ await this.s3client.send(new AbortMultipartUploadCommand(input));
253
+
254
+ // verify that all parts are removed.
255
+ const data = await this.s3client.send(new ListPartsCommand(input));
256
+
257
+ if (data && data.Parts && data.Parts.length > 0) {
258
+ throw new Error('Multi Part upload clean up failed');
259
+ }
260
+ }
261
+
262
+ private removeEventListener(part: Part) {
263
+ part.emitter.removeAllListeners(SEND_UPLOAD_PROGRESS_EVENT);
264
+ part.emitter.removeAllListeners(SEND_DOWNLOAD_PROGRESS_EVENT);
265
+ }
266
+
267
+ private setupEventListener(part: Part) {
268
+ part.emitter.on(SEND_UPLOAD_PROGRESS_EVENT, progress => {
269
+ this.progressChanged(
270
+ part.partNumber,
271
+ progress.loaded - part._lastUploadedBytes
272
+ );
273
+ part._lastUploadedBytes = progress.loaded;
274
+ });
275
+ }
276
+
277
+ private progressChanged(partNumber: number, incrementalUpdate: number) {
278
+ this.bytesUploaded += incrementalUpdate;
279
+ this.emitter.emit(SEND_UPLOAD_PROGRESS_EVENT, {
280
+ loaded: this.bytesUploaded,
281
+ total: this.totalBytesToUpload,
282
+ part: partNumber,
283
+ key: this.params.Key,
284
+ });
285
+ }
286
+
287
+ private byteLength(input: any) {
288
+ if (input === null || input === undefined) return 0;
289
+ if (typeof input.byteLength === 'number') {
290
+ return input.byteLength;
291
+ } else if (typeof input.length === 'number') {
292
+ return input.length;
293
+ } else if (typeof input.size === 'number') {
294
+ return input.size;
295
+ } else if (typeof input.path === 'string') {
296
+ /* NodeJs Support
297
+ return require('fs').lstatSync(input.path).size;
298
+ */
299
+ } else {
300
+ throw new Error('Cannot determine length of ' + input);
301
+ }
302
+ }
303
+
304
+ private async validateAndSanitizeBody(body: any): Promise<any> {
305
+ if (this.isGenericObject(body)) {
306
+ // Any javascript object
307
+ return JSON.stringify(body);
308
+ } else {
309
+ // Files, arrayBuffer etc
310
+ return body;
311
+ }
312
+ /* TODO: streams and files for nodejs
313
+ if (
314
+ typeof body.path === 'string' &&
315
+ require('fs').lstatSync(body.path).size > 0
316
+ ) {
317
+ return body;
318
+ } */
319
+ }
320
+
321
+ private isGenericObject(body: any): body is Object {
322
+ if (body !== null && typeof body === 'object') {
323
+ try {
324
+ return !(this.byteLength(body) >= 0);
325
+ } catch (error) {
326
+ // If we cannot determine the length of the body, consider it
327
+ // as a generic object and upload a stringified version of it
328
+ return true;
329
+ }
330
+ }
331
+ return false;
332
+ }
333
+
334
+ protected _createNewS3Client(config, emitter?: events.EventEmitter) {
335
+ const s3client = createS3Client(config, emitter);
336
+ s3client.middlewareStack.add(
337
+ createPrefixMiddleware(this.opts, this.params.Key),
338
+ prefixMiddlewareOptions
339
+ );
340
+ s3client.middlewareStack.add(
341
+ autoAdjustClockskewMiddleware(s3client.config),
342
+ autoAdjustClockskewMiddlewareOptions
343
+ );
344
+ return s3client;
345
+ }
346
+ }