@aws-amplify/storage 4.4.12 → 4.4.13-custom-pk.86

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.
@@ -58,8 +58,8 @@ export class AWSS3ProviderManagedUpload {
58
58
  private params: PutObjectRequest = null;
59
59
  private opts = null;
60
60
  private completedParts: CompletedPart[] = [];
61
- private cancel = false;
62
61
  private s3client: S3Client;
62
+ private uploadId = null;
63
63
 
64
64
  // Progress reporting
65
65
  private bytesUploaded = 0;
@@ -74,79 +74,87 @@ export class AWSS3ProviderManagedUpload {
74
74
  }
75
75
 
76
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)
77
+ try {
78
+ this.body = await this.validateAndSanitizeBody(this.params.Body);
79
+ this.totalBytesToUpload = this.byteLength(this.body);
80
+ if (this.totalBytesToUpload <= this.minPartSize) {
81
+ // Multipart upload is not required. Upload the sanitized body as is
82
+ this.params.Body = this.body;
83
+ const putObjectCommand = new PutObjectCommand(this.params);
84
+ return this.s3client.send(putObjectCommand);
85
+ } else {
86
+ // Step 1: Initiate the multi part upload
87
+ this.uploadId = await this.createMultiPartUpload();
88
+
89
+ // Step 2: Upload chunks in parallel as requested
90
+ const numberOfPartsToUpload = Math.ceil(
91
+ this.totalBytesToUpload / this.minPartSize
108
92
  );
109
93
 
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
- }
94
+ const parts: Part[] = this.createParts();
95
+ for (
96
+ let start = 0;
97
+ start < numberOfPartsToUpload;
98
+ start += this.queueSize
99
+ ) {
100
+
101
+ // Upload as many as `queueSize` parts simultaneously
102
+ await this.uploadParts(
103
+ this.uploadId,
104
+ parts.slice(start, start + this.queueSize)
105
+ );
106
+ }
115
107
 
116
- parts.map(part => {
117
- this.removeEventListener(part);
118
- });
108
+ parts.map(part => {
109
+ this.removeEventListener(part);
110
+ });
119
111
 
120
- // Step 3: Finalize the upload such that S3 can recreate the file
121
- return await this.finishMultiPartUpload(uploadId);
112
+ // Step 3: Finalize the upload such that S3 can recreate the file
113
+ return await this.finishMultiPartUpload(this.uploadId);
114
+ }
115
+ } catch (error) {
116
+ // if any error is thrown, call cleanup
117
+ await this.cleanup(this.uploadId);
118
+ logger.error('Error. Cancelling the multipart upload.');
119
+ throw error;
122
120
  }
123
121
  }
124
122
 
125
123
  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;
124
+ try {
125
+ const parts: Part[] = [];
126
+ for (let bodyStart = 0; bodyStart < this.totalBytesToUpload; ) {
127
+ const bodyEnd = Math.min(
128
+ bodyStart + this.minPartSize,
129
+ this.totalBytesToUpload
130
+ );
131
+ parts.push({
132
+ bodyPart: this.body.slice(bodyStart, bodyEnd),
133
+ partNumber: parts.length + 1,
134
+ emitter: new events.EventEmitter(),
135
+ _lastUploadedBytes: 0,
136
+ });
137
+ bodyStart += this.minPartSize;
138
+ }
139
+ return parts;
140
+ } catch (error) {
141
+ logger.error(error);
142
+ throw error;
139
143
  }
140
- return parts;
141
144
  }
142
145
 
143
146
  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;
147
+ try {
148
+ const createMultiPartUploadCommand = new CreateMultipartUploadCommand(
149
+ this.params
150
+ );
151
+ const response = await this.s3client.send(createMultiPartUploadCommand);
152
+ logger.debug(response.UploadId);
153
+ return response.UploadId;
154
+ } catch (error) {
155
+ logger.error(error);
156
+ throw error;
157
+ }
150
158
  }
151
159
 
152
160
  /**
@@ -191,11 +199,9 @@ export class AWSS3ProviderManagedUpload {
191
199
  }
192
200
  } catch (error) {
193
201
  logger.error(
194
- 'error happened while uploading a part. Cancelling the multipart upload',
195
- error
202
+ 'Error happened while uploading a part. Cancelling the multipart upload'
196
203
  );
197
- this.cancelUpload();
198
- return;
204
+ throw error;
199
205
  }
200
206
  }
201
207
 
@@ -211,31 +217,11 @@ export class AWSS3ProviderManagedUpload {
211
217
  const data = await this.s3client.send(completeUploadCommand);
212
218
  return data.Key;
213
219
  } 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
+ logger.error('Error happened while finishing the upload.');
221
+ throw error;
220
222
  }
221
223
  }
222
224
 
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
225
  private async cleanup(uploadId: string) {
240
226
  // Reset this's state
241
227
  this.body = null;
@@ -255,7 +241,7 @@ export class AWSS3ProviderManagedUpload {
255
241
  const data = await this.s3client.send(new ListPartsCommand(input));
256
242
 
257
243
  if (data && data.Parts && data.Parts.length > 0) {
258
- throw new Error('Multi Part upload clean up failed');
244
+ throw new Error('Multipart upload clean up failed.');
259
245
  }
260
246
  }
261
247
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ * Copyright 2017-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5
5
  * the License. A copy of the License is located at
@@ -18,23 +18,48 @@ import axios, {
18
18
  AxiosRequestConfig,
19
19
  Method,
20
20
  CancelTokenSource,
21
- AxiosTransformer,
21
+ AxiosRequestHeaders,
22
+ AxiosRequestTransformer,
22
23
  } from 'axios';
23
24
  import { ConsoleLogger as Logger, Platform } from '@aws-amplify/core';
24
25
  import { FetchHttpHandlerOptions } from '@aws-sdk/fetch-http-handler';
25
26
  import * as events from 'events';
26
27
  import { AWSS3ProviderUploadErrorStrings } from '../common/StorageErrorStrings';
27
28
 
29
+ /**
30
+ Extending the axios interface here to make headers required, (previously,
31
+ they were not required on the type we were using, but our implementation
32
+ does not currently account for missing headers. This worked previously,
33
+ because the previous `headers` type was `any`.
34
+ */
35
+ interface AxiosTransformer extends Partial<AxiosRequestTransformer> {
36
+ (data: any, headers: AxiosRequestHeaders): any;
37
+ }
38
+
28
39
  const logger = new Logger('axios-http-handler');
29
40
  export const SEND_UPLOAD_PROGRESS_EVENT = 'sendUploadProgress';
30
41
  export const SEND_DOWNLOAD_PROGRESS_EVENT = 'sendDownloadProgress';
31
42
 
43
+ export type ErrorWithResponse = {
44
+ response: { status: number } & { [key: string]: any };
45
+ };
46
+
32
47
  function isBlob(body: any): body is Blob {
33
48
  return typeof Blob !== 'undefined' && body instanceof Blob;
34
49
  }
35
50
 
51
+ function hasErrorResponse(error: any): error is ErrorWithResponse {
52
+ return (
53
+ typeof error !== 'undefined' &&
54
+ Object.prototype.hasOwnProperty.call(error, 'response') &&
55
+ typeof error.response !== 'undefined' &&
56
+ Object.prototype.hasOwnProperty.call(error.response, 'status') &&
57
+ typeof error.response.status === 'number'
58
+ );
59
+ }
60
+
36
61
  const normalizeHeaders = (
37
- headers: Record<string, string>,
62
+ headers: AxiosRequestHeaders,
38
63
  normalizedName: string
39
64
  ) => {
40
65
  for (const [k, v] of Object.entries(headers)) {
@@ -49,7 +74,7 @@ const normalizeHeaders = (
49
74
  };
50
75
 
51
76
  export const reactNativeRequestTransformer: AxiosTransformer[] = [
52
- function(data, headers) {
77
+ (data: any, headers: AxiosRequestHeaders): any => {
53
78
  if (isBlob(data)) {
54
79
  normalizeHeaders(headers, 'Content-Type');
55
80
  normalizeHeaders(headers, 'Accept');
@@ -137,10 +162,12 @@ export class AxiosHttpHandler implements HttpHandler {
137
162
  }
138
163
  }
139
164
  if (emitter) {
165
+ // TODO: Unify linting rules across JS repo
140
166
  axiosRequest.onUploadProgress = function(event) {
141
167
  emitter.emit(SEND_UPLOAD_PROGRESS_EVENT, event);
142
168
  logger.debug(event);
143
169
  };
170
+ // TODO: Unify linting rules across JS repo
144
171
  axiosRequest.onDownloadProgress = function(event) {
145
172
  emitter.emit(SEND_DOWNLOAD_PROGRESS_EVENT, event);
146
173
  logger.debug(event);
@@ -186,15 +213,19 @@ export class AxiosHttpHandler implements HttpHandler {
186
213
  logger.error(error.message);
187
214
  }
188
215
  // for axios' cancel error, we should re-throw it back so it's not considered an s3client error
189
- // if we return empty, or an abitrary error HttpResponse, it will be hard to debug down the line
190
- if (axios.isCancel(error)) {
216
+ // if we return empty, or an abitrary error HttpResponse, it will be hard to debug down the line.
217
+ //
218
+ // for errors that does not have a 'response' object, it's very likely that it is an unexpected error for
219
+ // example a disconnect. Without it we cannot meaningfully reconstruct a HttpResponse, and the AWS SDK might
220
+ // consider the request successful by mistake. In this case we should also re-throw the error.
221
+ if (axios.isCancel(error) || !hasErrorResponse(error)) {
191
222
  throw error;
192
223
  }
193
224
  // otherwise, we should re-construct an HttpResponse from the error, so that it can be passed down to other
194
225
  // aws sdk middleware (e.g retry, clock skew correction, error message serializing)
195
226
  return {
196
227
  response: new HttpResponse({
197
- statusCode: error.response?.status,
228
+ statusCode: error.response.status,
198
229
  body: error.response?.data,
199
230
  headers: error.response?.headers,
200
231
  }),