@firebase/storage 0.9.10 → 0.9.11-20221010190246

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.
@@ -42,7 +42,11 @@ const DEFAULT_MAX_OPERATION_RETRY_TIME = 2 * 60 * 1000;
42
42
  *
43
43
  * The timeout for upload.
44
44
  */
45
- const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000;
45
+ const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000;
46
+ /**
47
+ * 1 second
48
+ */
49
+ const DEFAULT_MIN_SLEEP_TIME_MILLIS = 1000;
46
50
 
47
51
  /**
48
52
  * @license
@@ -69,9 +73,11 @@ class StorageError extends FirebaseError {
69
73
  * @param code - A StorageErrorCode string to be prefixed with 'storage/' and
70
74
  * added to the end of the message.
71
75
  * @param message - Error message.
76
+ * @param status_ - Corresponding HTTP Status Code
72
77
  */
73
- constructor(code, message) {
78
+ constructor(code, message, status_ = 0) {
74
79
  super(prependCode(code), `Firebase Storage: ${message} (${prependCode(code)})`);
80
+ this.status_ = status_;
75
81
  /**
76
82
  * Stores custom error data unque to StorageError.
77
83
  */
@@ -81,6 +87,12 @@ class StorageError extends FirebaseError {
81
87
  // returns false.
82
88
  Object.setPrototypeOf(this, StorageError.prototype);
83
89
  }
90
+ get status() {
91
+ return this.status_;
92
+ }
93
+ set status(status) {
94
+ this.status_ = status;
95
+ }
84
96
  /**
85
97
  * Compares a StorageErrorCode against this error's code, filtering out the prefix.
86
98
  */
@@ -340,14 +352,20 @@ class FailRequest {
340
352
  * limitations under the License.
341
353
  */
342
354
  /**
343
- * @param f May be invoked
344
- * before the function returns.
345
- * @param callback Get all the arguments passed to the function
346
- * passed to f, including the initial boolean.
355
+ * Accepts a callback for an action to perform (`doRequest`),
356
+ * and then a callback for when the backoff has completed (`backoffCompleteCb`).
357
+ * The callback sent to start requires an argument to call (`onRequestComplete`).
358
+ * When `start` calls `doRequest`, it passes a callback for when the request has
359
+ * completed, `onRequestComplete`. Based on this, the backoff continues, with
360
+ * another call to `doRequest` and the above loop continues until the timeout
361
+ * is hit, or a successful response occurs.
362
+ * @description
363
+ * @param doRequest Callback to perform request
364
+ * @param backoffCompleteCb Callback to call when backoff has been completed
347
365
  */
348
- function start(f,
366
+ function start(doRequest,
349
367
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
350
- callback, timeout) {
368
+ backoffCompleteCb, timeout) {
351
369
  // TODO(andysoto): make this code cleaner (probably refactor into an actual
352
370
  // type instead of a bunch of functions with state shared in the closure)
353
371
  let waitSeconds = 1;
@@ -366,13 +384,13 @@ callback, timeout) {
366
384
  function triggerCallback(...args) {
367
385
  if (!triggeredCallback) {
368
386
  triggeredCallback = true;
369
- callback.apply(null, args);
387
+ backoffCompleteCb.apply(null, args);
370
388
  }
371
389
  }
372
390
  function callWithDelay(millis) {
373
391
  retryTimeoutId = setTimeout(() => {
374
392
  retryTimeoutId = null;
375
- f(handler, canceled());
393
+ doRequest(responseHandler, canceled());
376
394
  }, millis);
377
395
  }
378
396
  function clearGlobalTimeout() {
@@ -380,7 +398,7 @@ callback, timeout) {
380
398
  clearTimeout(globalTimeoutId);
381
399
  }
382
400
  }
383
- function handler(success, ...args) {
401
+ function responseHandler(success, ...args) {
384
402
  if (triggeredCallback) {
385
403
  clearGlobalTimeout();
386
404
  return;
@@ -558,6 +576,43 @@ var ErrorCode;
558
576
  ErrorCode[ErrorCode["ABORT"] = 2] = "ABORT";
559
577
  })(ErrorCode || (ErrorCode = {}));
560
578
 
579
+ /**
580
+ * @license
581
+ * Copyright 2022 Google LLC
582
+ *
583
+ * Licensed under the Apache License, Version 2.0 (the "License");
584
+ * you may not use this file except in compliance with the License.
585
+ * You may obtain a copy of the License at
586
+ *
587
+ * http://www.apache.org/licenses/LICENSE-2.0
588
+ *
589
+ * Unless required by applicable law or agreed to in writing, software
590
+ * distributed under the License is distributed on an "AS IS" BASIS,
591
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
592
+ * See the License for the specific language governing permissions and
593
+ * limitations under the License.
594
+ */
595
+ /**
596
+ * Checks the status code to see if the action should be retried.
597
+ *
598
+ * @param status Current HTTP status code returned by server.
599
+ * @param additionalRetryCodes additional retry codes to check against
600
+ */
601
+ function isRetryStatusCode(status, additionalRetryCodes) {
602
+ // The codes for which to retry came from this page:
603
+ // https://cloud.google.com/storage/docs/exponential-backoff
604
+ const isFiveHundredCode = status >= 500 && status < 600;
605
+ const extraRetryCodes = [
606
+ // Request Timeout: web server didn't receive full request in time.
607
+ 408,
608
+ // Too Many Requests: you're getting rate-limited, basically.
609
+ 429
610
+ ];
611
+ const isExtraRetryCode = extraRetryCodes.indexOf(status) !== -1;
612
+ const isAdditionalRetryCode = additionalRetryCodes.indexOf(status) !== -1;
613
+ return isFiveHundredCode || isExtraRetryCode || isAdditionalRetryCode;
614
+ }
615
+
561
616
  /**
562
617
  * @license
563
618
  * Copyright 2017 Google LLC
@@ -583,7 +638,7 @@ var ErrorCode;
583
638
  * happens in the specified `callback_`.
584
639
  */
585
640
  class NetworkRequest {
586
- constructor(url_, method_, headers_, body_, successCodes_, additionalRetryCodes_, callback_, errorCallback_, timeout_, progressCallback_, connectionFactory_) {
641
+ constructor(url_, method_, headers_, body_, successCodes_, additionalRetryCodes_, callback_, errorCallback_, timeout_, progressCallback_, connectionFactory_, retry = true) {
587
642
  this.url_ = url_;
588
643
  this.method_ = method_;
589
644
  this.headers_ = headers_;
@@ -595,6 +650,7 @@ class NetworkRequest {
595
650
  this.timeout_ = timeout_;
596
651
  this.progressCallback_ = progressCallback_;
597
652
  this.connectionFactory_ = connectionFactory_;
653
+ this.retry = retry;
598
654
  this.pendingConnection_ = null;
599
655
  this.backoffId_ = null;
600
656
  this.canceled_ = false;
@@ -618,9 +674,7 @@ class NetworkRequest {
618
674
  this.pendingConnection_ = connection;
619
675
  const progressListener = progressEvent => {
620
676
  const loaded = progressEvent.loaded;
621
- const total = progressEvent.lengthComputable
622
- ? progressEvent.total
623
- : -1;
677
+ const total = progressEvent.lengthComputable ? progressEvent.total : -1;
624
678
  if (this.progressCallback_ !== null) {
625
679
  this.progressCallback_(loaded, total);
626
680
  }
@@ -639,7 +693,9 @@ class NetworkRequest {
639
693
  this.pendingConnection_ = null;
640
694
  const hitServer = connection.getErrorCode() === ErrorCode.NO_ERROR;
641
695
  const status = connection.getStatus();
642
- if (!hitServer || this.isRetryStatusCode_(status)) {
696
+ if ((!hitServer ||
697
+ isRetryStatusCode(status, this.additionalRetryCodes_)) &&
698
+ this.retry) {
643
699
  const wasCanceled = connection.getErrorCode() === ErrorCode.ABORT;
644
700
  backoffCallback(false, new RequestEndStatus(false, null, wasCanceled));
645
701
  return;
@@ -715,20 +771,6 @@ class NetworkRequest {
715
771
  this.pendingConnection_.abort();
716
772
  }
717
773
  }
718
- isRetryStatusCode_(status) {
719
- // The codes for which to retry came from this page:
720
- // https://cloud.google.com/storage/docs/exponential-backoff
721
- const isFiveHundredCode = status >= 500 && status < 600;
722
- const extraRetryCodes = [
723
- // Request Timeout: web server didn't receive full request in time.
724
- 408,
725
- // Too Many Requests: you're getting rate-limited, basically.
726
- 429
727
- ];
728
- const isExtraRetryCode = extraRetryCodes.indexOf(status) !== -1;
729
- const isRequestSpecificRetryCode = this.additionalRetryCodes_.indexOf(status) !== -1;
730
- return isFiveHundredCode || isExtraRetryCode || isRequestSpecificRetryCode;
731
- }
732
774
  }
733
775
  /**
734
776
  * A collection of information about the result of a network request.
@@ -760,7 +802,7 @@ function addAppCheckHeader_(headers, appCheckToken) {
760
802
  headers['X-Firebase-AppCheck'] = appCheckToken;
761
803
  }
762
804
  }
763
- function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactory, firebaseVersion) {
805
+ function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactory, firebaseVersion, retry = true) {
764
806
  const queryPart = makeQueryString(requestInfo.urlParams);
765
807
  const url = requestInfo.url + queryPart;
766
808
  const headers = Object.assign({}, requestInfo.headers);
@@ -768,7 +810,7 @@ function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactor
768
810
  addAuthHeader_(headers, authToken);
769
811
  addVersionHeader_(headers, firebaseVersion);
770
812
  addAppCheckHeader_(headers, appCheckToken);
771
- return new NetworkRequest(url, requestInfo.method, headers, requestInfo.body, requestInfo.successCodes, requestInfo.additionalRetryCodes, requestInfo.handler, requestInfo.errorHandler, requestInfo.timeout, requestInfo.progressCallback, requestFactory);
813
+ return new NetworkRequest(url, requestInfo.method, headers, requestInfo.body, requestInfo.successCodes, requestInfo.additionalRetryCodes, requestInfo.handler, requestInfo.errorHandler, requestInfo.timeout, requestInfo.progressCallback, requestFactory, retry);
772
814
  }
773
815
 
774
816
  /**
@@ -1590,6 +1632,7 @@ function sharedErrorHandler(location) {
1590
1632
  }
1591
1633
  }
1592
1634
  }
1635
+ newErr.status = xhr.getStatus();
1593
1636
  newErr.serverResponse = err.serverResponse;
1594
1637
  return newErr;
1595
1638
  }
@@ -1874,7 +1917,16 @@ function continueResumableUpload(location, service, url, blob, chunkSize, mappin
1874
1917
  }
1875
1918
  const startByte = status_.current;
1876
1919
  const endByte = startByte + bytesToUpload;
1877
- const uploadCommand = bytesToUpload === bytesLeft ? 'upload, finalize' : 'upload';
1920
+ let uploadCommand = '';
1921
+ if (bytesToUpload === 0) {
1922
+ uploadCommand = 'finalize';
1923
+ }
1924
+ else if (bytesLeft === bytesToUpload) {
1925
+ uploadCommand = 'upload, finalize';
1926
+ }
1927
+ else {
1928
+ uploadCommand = 'upload';
1929
+ }
1878
1930
  const headers = {
1879
1931
  'X-Goog-Upload-Command': uploadCommand,
1880
1932
  'X-Goog-Upload-Offset': `${status_.current}`
@@ -2249,6 +2301,18 @@ class UploadTask {
2249
2301
  this.completeTransitions_();
2250
2302
  }
2251
2303
  else {
2304
+ const backoffExpired = this.isExponentialBackoffExpired();
2305
+ if (isRetryStatusCode(error.status, [])) {
2306
+ if (backoffExpired) {
2307
+ error = retryLimitExceeded();
2308
+ }
2309
+ else {
2310
+ this.sleepTime = Math.max(this.sleepTime * 2, DEFAULT_MIN_SLEEP_TIME_MILLIS);
2311
+ this._needToFetchStatus = true;
2312
+ this.completeTransitions_();
2313
+ return;
2314
+ }
2315
+ }
2252
2316
  this._error = error;
2253
2317
  this._transition("error" /* ERROR */);
2254
2318
  }
@@ -2263,6 +2327,8 @@ class UploadTask {
2263
2327
  this._transition("error" /* ERROR */);
2264
2328
  }
2265
2329
  };
2330
+ this.sleepTime = 0;
2331
+ this.maxSleepTime = this._ref.storage.maxUploadRetryTime;
2266
2332
  this._promise = new Promise((resolve, reject) => {
2267
2333
  this._resolve = resolve;
2268
2334
  this._reject = reject;
@@ -2272,6 +2338,9 @@ class UploadTask {
2272
2338
  // to the top level with a dummy handler.
2273
2339
  this._promise.then(null, () => { });
2274
2340
  }
2341
+ isExponentialBackoffExpired() {
2342
+ return this.sleepTime > this.maxSleepTime;
2343
+ }
2275
2344
  _makeProgressCallback() {
2276
2345
  const sizeBefore = this._transferred;
2277
2346
  return loaded => this._updateProgress(sizeBefore + loaded);
@@ -2301,7 +2370,9 @@ class UploadTask {
2301
2370
  this._fetchMetadata();
2302
2371
  }
2303
2372
  else {
2304
- this._continueUpload();
2373
+ setTimeout(() => {
2374
+ this._continueUpload();
2375
+ }, this.sleepTime);
2305
2376
  }
2306
2377
  }
2307
2378
  }
@@ -2377,7 +2448,9 @@ class UploadTask {
2377
2448
  this._transition("error" /* ERROR */);
2378
2449
  return;
2379
2450
  }
2380
- const uploadRequest = this._ref.storage._makeRequest(requestInfo, newTextConnection, authToken, appCheckToken);
2451
+ const uploadRequest = this._ref.storage._makeRequest(requestInfo, newTextConnection, authToken, appCheckToken,
2452
+ /*retry=*/ false // Upload requests should not be retried as each retry should be preceded by another query request. Which is handled in this file.
2453
+ );
2381
2454
  this._request = uploadRequest;
2382
2455
  uploadRequest.getPromise().then((newStatus) => {
2383
2456
  this._increaseMultiplier();
@@ -2396,7 +2469,7 @@ class UploadTask {
2396
2469
  _increaseMultiplier() {
2397
2470
  const currentSize = RESUMABLE_UPLOAD_CHUNK_SIZE * this._chunkMultiplier;
2398
2471
  // Max chunk size is 32M.
2399
- if (currentSize < 32 * 1024 * 1024) {
2472
+ if (currentSize * 2 < 32 * 1024 * 1024) {
2400
2473
  this._chunkMultiplier *= 2;
2401
2474
  }
2402
2475
  }
@@ -2545,6 +2618,7 @@ class UploadTask {
2545
2618
  * callbacks.
2546
2619
  */
2547
2620
  on(type, nextOrObserver, error, completed) {
2621
+ // Note: `type` isn't being used. Its type is also incorrect. TaskEvent should not be a string.
2548
2622
  const observer = new Observer(nextOrObserver || undefined, error || undefined, completed || undefined);
2549
2623
  this._addObserver(observer);
2550
2624
  return () => {
@@ -3247,9 +3321,9 @@ class FirebaseStorageImpl {
3247
3321
  * @param requestInfo - HTTP RequestInfo object
3248
3322
  * @param authToken - Firebase auth token
3249
3323
  */
3250
- _makeRequest(requestInfo, requestFactory, authToken, appCheckToken) {
3324
+ _makeRequest(requestInfo, requestFactory, authToken, appCheckToken, retry = true) {
3251
3325
  if (!this._deleted) {
3252
- const request = makeRequest(requestInfo, this._appId, authToken, appCheckToken, requestFactory, this._firebaseVersion);
3326
+ const request = makeRequest(requestInfo, this._appId, authToken, appCheckToken, requestFactory, this._firebaseVersion, retry);
3253
3327
  this._requests.add(request);
3254
3328
  // Request removes itself from set when complete.
3255
3329
  request.getPromise().then(() => this._requests.delete(request), () => this._requests.delete(request));
@@ -3269,7 +3343,7 @@ class FirebaseStorageImpl {
3269
3343
  }
3270
3344
 
3271
3345
  const name = "@firebase/storage";
3272
- const version = "0.9.10";
3346
+ const version = "0.9.11-20221010190246";
3273
3347
 
3274
3348
  /**
3275
3349
  * @license