@@ -29,6 +29,16 @@ interface IIsoDateString {
29
29
dateTime : string ;
30
30
}
31
31
32
+ type RetriableRequestResponse < T > =
33
+ | {
34
+ hasNetworkError : true ;
35
+ error : unknown ;
36
+ }
37
+ | {
38
+ hasNetworkError : false ;
39
+ response : T ;
40
+ } ;
41
+
32
42
const protocolRegex : RegExp = / ^ h t t p s ? : \/ \/ / ;
33
43
const portRegex : RegExp = / : ( \d { 1 , 5 } ) $ / ;
34
44
@@ -124,35 +134,60 @@ export class AmazonS3Client {
124
134
125
135
public async getObjectAsync ( objectName : string ) : Promise < Buffer | undefined > {
126
136
this . _writeDebugLine ( 'Reading object from S3' ) ;
127
- const sendRequest : ( ) => Promise < Buffer | undefined > = async ( ) : Promise < Buffer | undefined > => {
137
+ type Response = RetriableRequestResponse < Buffer | undefined > ;
138
+ const sendRequest : ( ) => Promise < Response > = async ( ) : Promise < Response > => {
128
139
const response : fetch . Response = await this . _makeRequestAsync ( 'GET' , objectName ) ;
129
140
if ( response . ok ) {
130
- return await response . buffer ( ) ;
141
+ return {
142
+ hasNetworkError : false ,
143
+ response : await response . buffer ( )
144
+ } ;
131
145
} else if ( response . status === 404 ) {
132
- return undefined ;
146
+ return {
147
+ hasNetworkError : false ,
148
+ response : undefined
149
+ } ;
133
150
} else if ( response . status === 403 && ! this . _credentials ) {
134
- return undefined ;
135
- } else {
151
+ return {
152
+ hasNetworkError : false ,
153
+ response : undefined
154
+ } ;
155
+ } else if ( response . status === 400 || response . status === 401 || response . status === 403 ) {
136
156
this . _throwS3Error ( response , await this . _safeReadResponseText ( response ) ) ;
157
+ } else {
158
+ const error : Error = this . _getS3Error ( response , await this . _safeReadResponseText ( response ) ) ;
159
+ return {
160
+ hasNetworkError : true ,
161
+ error
162
+ } ;
137
163
}
138
164
} ;
139
165
140
- return this . _sendCacheRequestWithRetries ( sendRequest ) ;
166
+ return await this . _sendCacheRequestWithRetries ( sendRequest ) ;
141
167
}
142
168
143
169
public async uploadObjectAsync ( objectName : string , objectBuffer : Buffer ) : Promise < void > {
144
170
if ( ! this . _credentials ) {
145
171
throw new Error ( 'Credentials are required to upload objects to S3.' ) ;
146
172
}
147
173
148
- const sendRequest : ( ) => Promise < void > = async ( ) : Promise < void > => {
174
+ type Response = RetriableRequestResponse < void > ;
175
+
176
+ const sendRequest : ( ) => Promise < Response > = async ( ) : Promise < Response > => {
149
177
const response : fetch . Response = await this . _makeRequestAsync ( 'PUT' , objectName , objectBuffer ) ;
150
178
if ( ! response . ok ) {
151
- this . _throwS3Error ( response , await this . _safeReadResponseText ( response ) ) ;
179
+ return {
180
+ hasNetworkError : true ,
181
+ error : this . _getS3Error ( response , await this . _safeReadResponseText ( response ) )
182
+ } ;
152
183
}
184
+ return {
185
+ hasNetworkError : false ,
186
+ response : undefined
187
+ } ;
153
188
} ;
154
189
155
- return this . _sendCacheRequestWithRetries ( sendRequest ) ;
190
+ await this . _sendCacheRequestWithRetries ( sendRequest ) ;
156
191
}
157
192
158
193
private _writeDebugLine ( ...messageParts : ( string | IColorableSequence ) [ ] ) : void {
@@ -334,14 +369,18 @@ export class AmazonS3Client {
334
369
return undefined ;
335
370
}
336
371
337
- private _throwS3Error ( response : fetch . Response , text : string | undefined ) : never {
338
- throw new Error (
372
+ private _getS3Error ( response : fetch . Response , text : string | undefined ) : Error {
373
+ return new Error (
339
374
`Amazon S3 responded with status code ${ response . status } (${ response . statusText } )${
340
375
text ? `\n${ text } ` : ''
341
376
} `
342
377
) ;
343
378
}
344
379
380
+ private _throwS3Error ( response : fetch . Response , text : string | undefined ) : never {
381
+ throw this . _getS3Error ( response , text ) ;
382
+ }
383
+
345
384
/**
346
385
* Validates a S3 endpoint which is http(s):// + hostname + port. Hostname validated according to RFC 1123
347
386
* {@link https://docs.aws.amazon.com/general/latest/gr/s3.html}
@@ -401,25 +440,10 @@ export class AmazonS3Client {
401
440
}
402
441
}
403
442
404
- private async _sendCacheRequestWithRetries < T > ( sendRequest : ( ) => Promise < T > ) : Promise < T > {
405
- type TryResponse = { hasNetworkError : false ; response : T } | { hasNetworkError : true ; error : unknown } ;
406
-
407
- const trySendRequest : ( ) => Promise < TryResponse > = async ( ) : Promise < TryResponse > => {
408
- try {
409
- const response : T = await sendRequest ( ) ;
410
- return {
411
- response,
412
- hasNetworkError : false
413
- } ;
414
- } catch ( err ) {
415
- return {
416
- hasNetworkError : true ,
417
- error : err
418
- } ;
419
- }
420
- } ;
421
-
422
- const response : TryResponse = await trySendRequest ( ) ;
443
+ private async _sendCacheRequestWithRetries < T > (
444
+ sendRequest : ( ) => Promise < RetriableRequestResponse < T > >
445
+ ) : Promise < T > {
446
+ const response : RetriableRequestResponse < T > = await sendRequest ( ) ;
423
447
424
448
const log : ( ...messageParts : ( string | IColorableSequence ) [ ] ) => void = this . _writeDebugLine . bind ( this ) ;
425
449
@@ -441,7 +465,7 @@ export class AmazonS3Client {
441
465
} , delay ) ;
442
466
} ) ;
443
467
444
- const response : TryResponse = await trySendRequest ( ) ;
468
+ const response : RetriableRequestResponse < T > = await sendRequest ( ) ;
445
469
446
470
if ( response . hasNetworkError ) {
447
471
if ( retryAttempt < maxTries - 1 ) {
0 commit comments