@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.
- package/CHANGELOG.md +146 -0
- package/dist/aws-amplify-storage.js +316 -248
- package/dist/aws-amplify-storage.js.map +1 -1
- package/dist/aws-amplify-storage.min.js +4 -18
- package/dist/aws-amplify-storage.min.js.map +1 -1
- package/lib/providers/AWSS3ProviderManagedUpload.d.ts +1 -3
- package/lib/providers/AWSS3ProviderManagedUpload.js +66 -86
- package/lib/providers/AWSS3ProviderManagedUpload.js.map +1 -1
- package/lib/providers/axios-http-handler.d.ts +18 -1
- package/lib/providers/axios-http-handler.js +20 -7
- package/lib/providers/axios-http-handler.js.map +1 -1
- package/lib-esm/providers/AWSS3ProviderManagedUpload.d.ts +1 -3
- package/lib-esm/providers/AWSS3ProviderManagedUpload.js +66 -86
- package/lib-esm/providers/AWSS3ProviderManagedUpload.js.map +1 -1
- package/lib-esm/providers/axios-http-handler.d.ts +18 -1
- package/lib-esm/providers/axios-http-handler.js +20 -7
- package/lib-esm/providers/axios-http-handler.js.map +1 -1
- package/package.json +5 -6
- package/src/providers/AWSS3ProviderManagedUpload.ts +75 -89
- package/src/providers/axios-http-handler.ts +38 -7
|
@@ -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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
108
|
+
parts.map(part => {
|
|
109
|
+
this.removeEventListener(part);
|
|
110
|
+
});
|
|
119
111
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
'
|
|
195
|
-
error
|
|
202
|
+
'Error happened while uploading a part. Cancelling the multipart upload'
|
|
196
203
|
);
|
|
197
|
-
|
|
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
|
-
|
|
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('
|
|
244
|
+
throw new Error('Multipart upload clean up failed.');
|
|
259
245
|
}
|
|
260
246
|
}
|
|
261
247
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright 2017-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
228
|
+
statusCode: error.response.status,
|
|
198
229
|
body: error.response?.data,
|
|
199
230
|
headers: error.response?.headers,
|
|
200
231
|
}),
|