@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.
@@ -40,7 +40,11 @@ const DEFAULT_MAX_OPERATION_RETRY_TIME = 2 * 60 * 1000;
40
40
  *
41
41
  * The timeout for upload.
42
42
  */
43
- const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000;
43
+ const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000;
44
+ /**
45
+ * 1 second
46
+ */
47
+ const DEFAULT_MIN_SLEEP_TIME_MILLIS = 1000;
44
48
 
45
49
  /**
46
50
  * @license
@@ -67,9 +71,11 @@ class StorageError extends FirebaseError {
67
71
  * @param code - A StorageErrorCode string to be prefixed with 'storage/' and
68
72
  * added to the end of the message.
69
73
  * @param message - Error message.
74
+ * @param status_ - Corresponding HTTP Status Code
70
75
  */
71
- constructor(code, message) {
76
+ constructor(code, message, status_ = 0) {
72
77
  super(prependCode(code), `Firebase Storage: ${message} (${prependCode(code)})`);
78
+ this.status_ = status_;
73
79
  /**
74
80
  * Stores custom error data unque to StorageError.
75
81
  */
@@ -79,6 +85,12 @@ class StorageError extends FirebaseError {
79
85
  // returns false.
80
86
  Object.setPrototypeOf(this, StorageError.prototype);
81
87
  }
88
+ get status() {
89
+ return this.status_;
90
+ }
91
+ set status(status) {
92
+ this.status_ = status;
93
+ }
82
94
  /**
83
95
  * Compares a StorageErrorCode against this error's code, filtering out the prefix.
84
96
  */
@@ -338,14 +350,20 @@ class FailRequest {
338
350
  * limitations under the License.
339
351
  */
340
352
  /**
341
- * @param f May be invoked
342
- * before the function returns.
343
- * @param callback Get all the arguments passed to the function
344
- * passed to f, including the initial boolean.
353
+ * Accepts a callback for an action to perform (`doRequest`),
354
+ * and then a callback for when the backoff has completed (`backoffCompleteCb`).
355
+ * The callback sent to start requires an argument to call (`onRequestComplete`).
356
+ * When `start` calls `doRequest`, it passes a callback for when the request has
357
+ * completed, `onRequestComplete`. Based on this, the backoff continues, with
358
+ * another call to `doRequest` and the above loop continues until the timeout
359
+ * is hit, or a successful response occurs.
360
+ * @description
361
+ * @param doRequest Callback to perform request
362
+ * @param backoffCompleteCb Callback to call when backoff has been completed
345
363
  */
346
- function start(f,
364
+ function start(doRequest,
347
365
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
- callback, timeout) {
366
+ backoffCompleteCb, timeout) {
349
367
  // TODO(andysoto): make this code cleaner (probably refactor into an actual
350
368
  // type instead of a bunch of functions with state shared in the closure)
351
369
  let waitSeconds = 1;
@@ -364,13 +382,13 @@ callback, timeout) {
364
382
  function triggerCallback(...args) {
365
383
  if (!triggeredCallback) {
366
384
  triggeredCallback = true;
367
- callback.apply(null, args);
385
+ backoffCompleteCb.apply(null, args);
368
386
  }
369
387
  }
370
388
  function callWithDelay(millis) {
371
389
  retryTimeoutId = setTimeout(() => {
372
390
  retryTimeoutId = null;
373
- f(handler, canceled());
391
+ doRequest(responseHandler, canceled());
374
392
  }, millis);
375
393
  }
376
394
  function clearGlobalTimeout() {
@@ -378,7 +396,7 @@ callback, timeout) {
378
396
  clearTimeout(globalTimeoutId);
379
397
  }
380
398
  }
381
- function handler(success, ...args) {
399
+ function responseHandler(success, ...args) {
382
400
  if (triggeredCallback) {
383
401
  clearGlobalTimeout();
384
402
  return;
@@ -556,6 +574,43 @@ var ErrorCode;
556
574
  ErrorCode[ErrorCode["ABORT"] = 2] = "ABORT";
557
575
  })(ErrorCode || (ErrorCode = {}));
558
576
 
577
+ /**
578
+ * @license
579
+ * Copyright 2022 Google LLC
580
+ *
581
+ * Licensed under the Apache License, Version 2.0 (the "License");
582
+ * you may not use this file except in compliance with the License.
583
+ * You may obtain a copy of the License at
584
+ *
585
+ * http://www.apache.org/licenses/LICENSE-2.0
586
+ *
587
+ * Unless required by applicable law or agreed to in writing, software
588
+ * distributed under the License is distributed on an "AS IS" BASIS,
589
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
590
+ * See the License for the specific language governing permissions and
591
+ * limitations under the License.
592
+ */
593
+ /**
594
+ * Checks the status code to see if the action should be retried.
595
+ *
596
+ * @param status Current HTTP status code returned by server.
597
+ * @param additionalRetryCodes additional retry codes to check against
598
+ */
599
+ function isRetryStatusCode(status, additionalRetryCodes) {
600
+ // The codes for which to retry came from this page:
601
+ // https://cloud.google.com/storage/docs/exponential-backoff
602
+ const isFiveHundredCode = status >= 500 && status < 600;
603
+ const extraRetryCodes = [
604
+ // Request Timeout: web server didn't receive full request in time.
605
+ 408,
606
+ // Too Many Requests: you're getting rate-limited, basically.
607
+ 429
608
+ ];
609
+ const isExtraRetryCode = extraRetryCodes.indexOf(status) !== -1;
610
+ const isAdditionalRetryCode = additionalRetryCodes.indexOf(status) !== -1;
611
+ return isFiveHundredCode || isExtraRetryCode || isAdditionalRetryCode;
612
+ }
613
+
559
614
  /**
560
615
  * @license
561
616
  * Copyright 2017 Google LLC
@@ -581,7 +636,7 @@ var ErrorCode;
581
636
  * happens in the specified `callback_`.
582
637
  */
583
638
  class NetworkRequest {
584
- constructor(url_, method_, headers_, body_, successCodes_, additionalRetryCodes_, callback_, errorCallback_, timeout_, progressCallback_, connectionFactory_) {
639
+ constructor(url_, method_, headers_, body_, successCodes_, additionalRetryCodes_, callback_, errorCallback_, timeout_, progressCallback_, connectionFactory_, retry = true) {
585
640
  this.url_ = url_;
586
641
  this.method_ = method_;
587
642
  this.headers_ = headers_;
@@ -593,6 +648,7 @@ class NetworkRequest {
593
648
  this.timeout_ = timeout_;
594
649
  this.progressCallback_ = progressCallback_;
595
650
  this.connectionFactory_ = connectionFactory_;
651
+ this.retry = retry;
596
652
  this.pendingConnection_ = null;
597
653
  this.backoffId_ = null;
598
654
  this.canceled_ = false;
@@ -616,9 +672,7 @@ class NetworkRequest {
616
672
  this.pendingConnection_ = connection;
617
673
  const progressListener = progressEvent => {
618
674
  const loaded = progressEvent.loaded;
619
- const total = progressEvent.lengthComputable
620
- ? progressEvent.total
621
- : -1;
675
+ const total = progressEvent.lengthComputable ? progressEvent.total : -1;
622
676
  if (this.progressCallback_ !== null) {
623
677
  this.progressCallback_(loaded, total);
624
678
  }
@@ -637,7 +691,9 @@ class NetworkRequest {
637
691
  this.pendingConnection_ = null;
638
692
  const hitServer = connection.getErrorCode() === ErrorCode.NO_ERROR;
639
693
  const status = connection.getStatus();
640
- if (!hitServer || this.isRetryStatusCode_(status)) {
694
+ if ((!hitServer ||
695
+ isRetryStatusCode(status, this.additionalRetryCodes_)) &&
696
+ this.retry) {
641
697
  const wasCanceled = connection.getErrorCode() === ErrorCode.ABORT;
642
698
  backoffCallback(false, new RequestEndStatus(false, null, wasCanceled));
643
699
  return;
@@ -713,20 +769,6 @@ class NetworkRequest {
713
769
  this.pendingConnection_.abort();
714
770
  }
715
771
  }
716
- isRetryStatusCode_(status) {
717
- // The codes for which to retry came from this page:
718
- // https://cloud.google.com/storage/docs/exponential-backoff
719
- const isFiveHundredCode = status >= 500 && status < 600;
720
- const extraRetryCodes = [
721
- // Request Timeout: web server didn't receive full request in time.
722
- 408,
723
- // Too Many Requests: you're getting rate-limited, basically.
724
- 429
725
- ];
726
- const isExtraRetryCode = extraRetryCodes.indexOf(status) !== -1;
727
- const isRequestSpecificRetryCode = this.additionalRetryCodes_.indexOf(status) !== -1;
728
- return isFiveHundredCode || isExtraRetryCode || isRequestSpecificRetryCode;
729
- }
730
772
  }
731
773
  /**
732
774
  * A collection of information about the result of a network request.
@@ -758,7 +800,7 @@ function addAppCheckHeader_(headers, appCheckToken) {
758
800
  headers['X-Firebase-AppCheck'] = appCheckToken;
759
801
  }
760
802
  }
761
- function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactory, firebaseVersion) {
803
+ function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactory, firebaseVersion, retry = true) {
762
804
  const queryPart = makeQueryString(requestInfo.urlParams);
763
805
  const url = requestInfo.url + queryPart;
764
806
  const headers = Object.assign({}, requestInfo.headers);
@@ -766,7 +808,7 @@ function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactor
766
808
  addAuthHeader_(headers, authToken);
767
809
  addVersionHeader_(headers, firebaseVersion);
768
810
  addAppCheckHeader_(headers, appCheckToken);
769
- return new NetworkRequest(url, requestInfo.method, headers, requestInfo.body, requestInfo.successCodes, requestInfo.additionalRetryCodes, requestInfo.handler, requestInfo.errorHandler, requestInfo.timeout, requestInfo.progressCallback, requestFactory);
811
+ return new NetworkRequest(url, requestInfo.method, headers, requestInfo.body, requestInfo.successCodes, requestInfo.additionalRetryCodes, requestInfo.handler, requestInfo.errorHandler, requestInfo.timeout, requestInfo.progressCallback, requestFactory, retry);
770
812
  }
771
813
 
772
814
  /**
@@ -1583,6 +1625,7 @@ function sharedErrorHandler(location) {
1583
1625
  }
1584
1626
  }
1585
1627
  }
1628
+ newErr.status = xhr.getStatus();
1586
1629
  newErr.serverResponse = err.serverResponse;
1587
1630
  return newErr;
1588
1631
  }
@@ -1867,7 +1910,16 @@ function continueResumableUpload(location, service, url, blob, chunkSize, mappin
1867
1910
  }
1868
1911
  const startByte = status_.current;
1869
1912
  const endByte = startByte + bytesToUpload;
1870
- const uploadCommand = bytesToUpload === bytesLeft ? 'upload, finalize' : 'upload';
1913
+ let uploadCommand = '';
1914
+ if (bytesToUpload === 0) {
1915
+ uploadCommand = 'finalize';
1916
+ }
1917
+ else if (bytesLeft === bytesToUpload) {
1918
+ uploadCommand = 'upload, finalize';
1919
+ }
1920
+ else {
1921
+ uploadCommand = 'upload';
1922
+ }
1871
1923
  const headers = {
1872
1924
  'X-Goog-Upload-Command': uploadCommand,
1873
1925
  'X-Goog-Upload-Offset': `${status_.current}`
@@ -2227,6 +2279,18 @@ class UploadTask {
2227
2279
  this.completeTransitions_();
2228
2280
  }
2229
2281
  else {
2282
+ const backoffExpired = this.isExponentialBackoffExpired();
2283
+ if (isRetryStatusCode(error.status, [])) {
2284
+ if (backoffExpired) {
2285
+ error = retryLimitExceeded();
2286
+ }
2287
+ else {
2288
+ this.sleepTime = Math.max(this.sleepTime * 2, DEFAULT_MIN_SLEEP_TIME_MILLIS);
2289
+ this._needToFetchStatus = true;
2290
+ this.completeTransitions_();
2291
+ return;
2292
+ }
2293
+ }
2230
2294
  this._error = error;
2231
2295
  this._transition("error" /* ERROR */);
2232
2296
  }
@@ -2241,6 +2305,8 @@ class UploadTask {
2241
2305
  this._transition("error" /* ERROR */);
2242
2306
  }
2243
2307
  };
2308
+ this.sleepTime = 0;
2309
+ this.maxSleepTime = this._ref.storage.maxUploadRetryTime;
2244
2310
  this._promise = new Promise((resolve, reject) => {
2245
2311
  this._resolve = resolve;
2246
2312
  this._reject = reject;
@@ -2250,6 +2316,9 @@ class UploadTask {
2250
2316
  // to the top level with a dummy handler.
2251
2317
  this._promise.then(null, () => { });
2252
2318
  }
2319
+ isExponentialBackoffExpired() {
2320
+ return this.sleepTime > this.maxSleepTime;
2321
+ }
2253
2322
  _makeProgressCallback() {
2254
2323
  const sizeBefore = this._transferred;
2255
2324
  return loaded => this._updateProgress(sizeBefore + loaded);
@@ -2279,7 +2348,9 @@ class UploadTask {
2279
2348
  this._fetchMetadata();
2280
2349
  }
2281
2350
  else {
2282
- this._continueUpload();
2351
+ setTimeout(() => {
2352
+ this._continueUpload();
2353
+ }, this.sleepTime);
2283
2354
  }
2284
2355
  }
2285
2356
  }
@@ -2355,7 +2426,9 @@ class UploadTask {
2355
2426
  this._transition("error" /* ERROR */);
2356
2427
  return;
2357
2428
  }
2358
- const uploadRequest = this._ref.storage._makeRequest(requestInfo, newTextConnection, authToken, appCheckToken);
2429
+ const uploadRequest = this._ref.storage._makeRequest(requestInfo, newTextConnection, authToken, appCheckToken,
2430
+ /*retry=*/ false // Upload requests should not be retried as each retry should be preceded by another query request. Which is handled in this file.
2431
+ );
2359
2432
  this._request = uploadRequest;
2360
2433
  uploadRequest.getPromise().then((newStatus) => {
2361
2434
  this._increaseMultiplier();
@@ -2374,7 +2447,7 @@ class UploadTask {
2374
2447
  _increaseMultiplier() {
2375
2448
  const currentSize = RESUMABLE_UPLOAD_CHUNK_SIZE * this._chunkMultiplier;
2376
2449
  // Max chunk size is 32M.
2377
- if (currentSize < 32 * 1024 * 1024) {
2450
+ if (currentSize * 2 < 32 * 1024 * 1024) {
2378
2451
  this._chunkMultiplier *= 2;
2379
2452
  }
2380
2453
  }
@@ -2523,6 +2596,7 @@ class UploadTask {
2523
2596
  * callbacks.
2524
2597
  */
2525
2598
  on(type, nextOrObserver, error, completed) {
2599
+ // Note: `type` isn't being used. Its type is also incorrect. TaskEvent should not be a string.
2526
2600
  const observer = new Observer(nextOrObserver || undefined, error || undefined, completed || undefined);
2527
2601
  this._addObserver(observer);
2528
2602
  return () => {
@@ -3208,9 +3282,9 @@ class FirebaseStorageImpl {
3208
3282
  * @param requestInfo - HTTP RequestInfo object
3209
3283
  * @param authToken - Firebase auth token
3210
3284
  */
3211
- _makeRequest(requestInfo, requestFactory, authToken, appCheckToken) {
3285
+ _makeRequest(requestInfo, requestFactory, authToken, appCheckToken, retry = true) {
3212
3286
  if (!this._deleted) {
3213
- const request = makeRequest(requestInfo, this._appId, authToken, appCheckToken, requestFactory, this._firebaseVersion);
3287
+ const request = makeRequest(requestInfo, this._appId, authToken, appCheckToken, requestFactory, this._firebaseVersion, retry);
3214
3288
  this._requests.add(request);
3215
3289
  // Request removes itself from set when complete.
3216
3290
  request.getPromise().then(() => this._requests.delete(request), () => this._requests.delete(request));
@@ -3230,7 +3304,7 @@ class FirebaseStorageImpl {
3230
3304
  }
3231
3305
 
3232
3306
  const name = "@firebase/storage";
3233
- const version = "0.9.10";
3307
+ const version = "0.9.11-20221010190246";
3234
3308
 
3235
3309
  /**
3236
3310
  * @license