@firebase/storage 0.9.10 → 0.9.11-20221010203322

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.
@@ -50,7 +50,11 @@ const DEFAULT_MAX_OPERATION_RETRY_TIME = 2 * 60 * 1000;
50
50
  *
51
51
  * The timeout for upload.
52
52
  */
53
- const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000;
53
+ const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000;
54
+ /**
55
+ * 1 second
56
+ */
57
+ const DEFAULT_MIN_SLEEP_TIME_MILLIS = 1000;
54
58
 
55
59
  /**
56
60
  * @license
@@ -77,9 +81,11 @@ class StorageError extends util.FirebaseError {
77
81
  * @param code - A StorageErrorCode string to be prefixed with 'storage/' and
78
82
  * added to the end of the message.
79
83
  * @param message - Error message.
84
+ * @param status_ - Corresponding HTTP Status Code
80
85
  */
81
- constructor(code, message) {
86
+ constructor(code, message, status_ = 0) {
82
87
  super(prependCode(code), `Firebase Storage: ${message} (${prependCode(code)})`);
88
+ this.status_ = status_;
83
89
  /**
84
90
  * Stores custom error data unque to StorageError.
85
91
  */
@@ -89,6 +95,12 @@ class StorageError extends util.FirebaseError {
89
95
  // returns false.
90
96
  Object.setPrototypeOf(this, StorageError.prototype);
91
97
  }
98
+ get status() {
99
+ return this.status_;
100
+ }
101
+ set status(status) {
102
+ this.status_ = status;
103
+ }
92
104
  /**
93
105
  * Compares a StorageErrorCode against this error's code, filtering out the prefix.
94
106
  */
@@ -348,14 +360,20 @@ class FailRequest {
348
360
  * limitations under the License.
349
361
  */
350
362
  /**
351
- * @param f May be invoked
352
- * before the function returns.
353
- * @param callback Get all the arguments passed to the function
354
- * passed to f, including the initial boolean.
363
+ * Accepts a callback for an action to perform (`doRequest`),
364
+ * and then a callback for when the backoff has completed (`backoffCompleteCb`).
365
+ * The callback sent to start requires an argument to call (`onRequestComplete`).
366
+ * When `start` calls `doRequest`, it passes a callback for when the request has
367
+ * completed, `onRequestComplete`. Based on this, the backoff continues, with
368
+ * another call to `doRequest` and the above loop continues until the timeout
369
+ * is hit, or a successful response occurs.
370
+ * @description
371
+ * @param doRequest Callback to perform request
372
+ * @param backoffCompleteCb Callback to call when backoff has been completed
355
373
  */
356
- function start(f,
374
+ function start(doRequest,
357
375
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
358
- callback, timeout) {
376
+ backoffCompleteCb, timeout) {
359
377
  // TODO(andysoto): make this code cleaner (probably refactor into an actual
360
378
  // type instead of a bunch of functions with state shared in the closure)
361
379
  let waitSeconds = 1;
@@ -374,13 +392,13 @@ callback, timeout) {
374
392
  function triggerCallback(...args) {
375
393
  if (!triggeredCallback) {
376
394
  triggeredCallback = true;
377
- callback.apply(null, args);
395
+ backoffCompleteCb.apply(null, args);
378
396
  }
379
397
  }
380
398
  function callWithDelay(millis) {
381
399
  retryTimeoutId = setTimeout(() => {
382
400
  retryTimeoutId = null;
383
- f(handler, canceled());
401
+ doRequest(responseHandler, canceled());
384
402
  }, millis);
385
403
  }
386
404
  function clearGlobalTimeout() {
@@ -388,7 +406,7 @@ callback, timeout) {
388
406
  clearTimeout(globalTimeoutId);
389
407
  }
390
408
  }
391
- function handler(success, ...args) {
409
+ function responseHandler(success, ...args) {
392
410
  if (triggeredCallback) {
393
411
  clearGlobalTimeout();
394
412
  return;
@@ -566,6 +584,43 @@ var ErrorCode;
566
584
  ErrorCode[ErrorCode["ABORT"] = 2] = "ABORT";
567
585
  })(ErrorCode || (ErrorCode = {}));
568
586
 
587
+ /**
588
+ * @license
589
+ * Copyright 2022 Google LLC
590
+ *
591
+ * Licensed under the Apache License, Version 2.0 (the "License");
592
+ * you may not use this file except in compliance with the License.
593
+ * You may obtain a copy of the License at
594
+ *
595
+ * http://www.apache.org/licenses/LICENSE-2.0
596
+ *
597
+ * Unless required by applicable law or agreed to in writing, software
598
+ * distributed under the License is distributed on an "AS IS" BASIS,
599
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
600
+ * See the License for the specific language governing permissions and
601
+ * limitations under the License.
602
+ */
603
+ /**
604
+ * Checks the status code to see if the action should be retried.
605
+ *
606
+ * @param status Current HTTP status code returned by server.
607
+ * @param additionalRetryCodes additional retry codes to check against
608
+ */
609
+ function isRetryStatusCode(status, additionalRetryCodes) {
610
+ // The codes for which to retry came from this page:
611
+ // https://cloud.google.com/storage/docs/exponential-backoff
612
+ const isFiveHundredCode = status >= 500 && status < 600;
613
+ const extraRetryCodes = [
614
+ // Request Timeout: web server didn't receive full request in time.
615
+ 408,
616
+ // Too Many Requests: you're getting rate-limited, basically.
617
+ 429
618
+ ];
619
+ const isExtraRetryCode = extraRetryCodes.indexOf(status) !== -1;
620
+ const isAdditionalRetryCode = additionalRetryCodes.indexOf(status) !== -1;
621
+ return isFiveHundredCode || isExtraRetryCode || isAdditionalRetryCode;
622
+ }
623
+
569
624
  /**
570
625
  * @license
571
626
  * Copyright 2017 Google LLC
@@ -591,7 +646,7 @@ var ErrorCode;
591
646
  * happens in the specified `callback_`.
592
647
  */
593
648
  class NetworkRequest {
594
- constructor(url_, method_, headers_, body_, successCodes_, additionalRetryCodes_, callback_, errorCallback_, timeout_, progressCallback_, connectionFactory_) {
649
+ constructor(url_, method_, headers_, body_, successCodes_, additionalRetryCodes_, callback_, errorCallback_, timeout_, progressCallback_, connectionFactory_, retry = true) {
595
650
  this.url_ = url_;
596
651
  this.method_ = method_;
597
652
  this.headers_ = headers_;
@@ -603,6 +658,7 @@ class NetworkRequest {
603
658
  this.timeout_ = timeout_;
604
659
  this.progressCallback_ = progressCallback_;
605
660
  this.connectionFactory_ = connectionFactory_;
661
+ this.retry = retry;
606
662
  this.pendingConnection_ = null;
607
663
  this.backoffId_ = null;
608
664
  this.canceled_ = false;
@@ -626,9 +682,7 @@ class NetworkRequest {
626
682
  this.pendingConnection_ = connection;
627
683
  const progressListener = progressEvent => {
628
684
  const loaded = progressEvent.loaded;
629
- const total = progressEvent.lengthComputable
630
- ? progressEvent.total
631
- : -1;
685
+ const total = progressEvent.lengthComputable ? progressEvent.total : -1;
632
686
  if (this.progressCallback_ !== null) {
633
687
  this.progressCallback_(loaded, total);
634
688
  }
@@ -647,7 +701,9 @@ class NetworkRequest {
647
701
  this.pendingConnection_ = null;
648
702
  const hitServer = connection.getErrorCode() === ErrorCode.NO_ERROR;
649
703
  const status = connection.getStatus();
650
- if (!hitServer || this.isRetryStatusCode_(status)) {
704
+ if ((!hitServer ||
705
+ isRetryStatusCode(status, this.additionalRetryCodes_)) &&
706
+ this.retry) {
651
707
  const wasCanceled = connection.getErrorCode() === ErrorCode.ABORT;
652
708
  backoffCallback(false, new RequestEndStatus(false, null, wasCanceled));
653
709
  return;
@@ -723,20 +779,6 @@ class NetworkRequest {
723
779
  this.pendingConnection_.abort();
724
780
  }
725
781
  }
726
- isRetryStatusCode_(status) {
727
- // The codes for which to retry came from this page:
728
- // https://cloud.google.com/storage/docs/exponential-backoff
729
- const isFiveHundredCode = status >= 500 && status < 600;
730
- const extraRetryCodes = [
731
- // Request Timeout: web server didn't receive full request in time.
732
- 408,
733
- // Too Many Requests: you're getting rate-limited, basically.
734
- 429
735
- ];
736
- const isExtraRetryCode = extraRetryCodes.indexOf(status) !== -1;
737
- const isRequestSpecificRetryCode = this.additionalRetryCodes_.indexOf(status) !== -1;
738
- return isFiveHundredCode || isExtraRetryCode || isRequestSpecificRetryCode;
739
- }
740
782
  }
741
783
  /**
742
784
  * A collection of information about the result of a network request.
@@ -768,7 +810,7 @@ function addAppCheckHeader_(headers, appCheckToken) {
768
810
  headers['X-Firebase-AppCheck'] = appCheckToken;
769
811
  }
770
812
  }
771
- function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactory, firebaseVersion) {
813
+ function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactory, firebaseVersion, retry = true) {
772
814
  const queryPart = makeQueryString(requestInfo.urlParams);
773
815
  const url = requestInfo.url + queryPart;
774
816
  const headers = Object.assign({}, requestInfo.headers);
@@ -776,7 +818,7 @@ function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactor
776
818
  addAuthHeader_(headers, authToken);
777
819
  addVersionHeader_(headers, firebaseVersion);
778
820
  addAppCheckHeader_(headers, appCheckToken);
779
- return new NetworkRequest(url, requestInfo.method, headers, requestInfo.body, requestInfo.successCodes, requestInfo.additionalRetryCodes, requestInfo.handler, requestInfo.errorHandler, requestInfo.timeout, requestInfo.progressCallback, requestFactory);
821
+ return new NetworkRequest(url, requestInfo.method, headers, requestInfo.body, requestInfo.successCodes, requestInfo.additionalRetryCodes, requestInfo.handler, requestInfo.errorHandler, requestInfo.timeout, requestInfo.progressCallback, requestFactory, retry);
780
822
  }
781
823
 
782
824
  /**
@@ -1598,6 +1640,7 @@ function sharedErrorHandler(location) {
1598
1640
  }
1599
1641
  }
1600
1642
  }
1643
+ newErr.status = xhr.getStatus();
1601
1644
  newErr.serverResponse = err.serverResponse;
1602
1645
  return newErr;
1603
1646
  }
@@ -1882,7 +1925,16 @@ function continueResumableUpload(location, service, url, blob, chunkSize, mappin
1882
1925
  }
1883
1926
  const startByte = status_.current;
1884
1927
  const endByte = startByte + bytesToUpload;
1885
- const uploadCommand = bytesToUpload === bytesLeft ? 'upload, finalize' : 'upload';
1928
+ let uploadCommand = '';
1929
+ if (bytesToUpload === 0) {
1930
+ uploadCommand = 'finalize';
1931
+ }
1932
+ else if (bytesLeft === bytesToUpload) {
1933
+ uploadCommand = 'upload, finalize';
1934
+ }
1935
+ else {
1936
+ uploadCommand = 'upload';
1937
+ }
1886
1938
  const headers = {
1887
1939
  'X-Goog-Upload-Command': uploadCommand,
1888
1940
  'X-Goog-Upload-Offset': `${status_.current}`
@@ -2257,6 +2309,18 @@ class UploadTask {
2257
2309
  this.completeTransitions_();
2258
2310
  }
2259
2311
  else {
2312
+ const backoffExpired = this.isExponentialBackoffExpired();
2313
+ if (isRetryStatusCode(error.status, [])) {
2314
+ if (backoffExpired) {
2315
+ error = retryLimitExceeded();
2316
+ }
2317
+ else {
2318
+ this.sleepTime = Math.max(this.sleepTime * 2, DEFAULT_MIN_SLEEP_TIME_MILLIS);
2319
+ this._needToFetchStatus = true;
2320
+ this.completeTransitions_();
2321
+ return;
2322
+ }
2323
+ }
2260
2324
  this._error = error;
2261
2325
  this._transition("error" /* ERROR */);
2262
2326
  }
@@ -2271,6 +2335,8 @@ class UploadTask {
2271
2335
  this._transition("error" /* ERROR */);
2272
2336
  }
2273
2337
  };
2338
+ this.sleepTime = 0;
2339
+ this.maxSleepTime = this._ref.storage.maxUploadRetryTime;
2274
2340
  this._promise = new Promise((resolve, reject) => {
2275
2341
  this._resolve = resolve;
2276
2342
  this._reject = reject;
@@ -2280,6 +2346,9 @@ class UploadTask {
2280
2346
  // to the top level with a dummy handler.
2281
2347
  this._promise.then(null, () => { });
2282
2348
  }
2349
+ isExponentialBackoffExpired() {
2350
+ return this.sleepTime > this.maxSleepTime;
2351
+ }
2283
2352
  _makeProgressCallback() {
2284
2353
  const sizeBefore = this._transferred;
2285
2354
  return loaded => this._updateProgress(sizeBefore + loaded);
@@ -2309,7 +2378,9 @@ class UploadTask {
2309
2378
  this._fetchMetadata();
2310
2379
  }
2311
2380
  else {
2312
- this._continueUpload();
2381
+ setTimeout(() => {
2382
+ this._continueUpload();
2383
+ }, this.sleepTime);
2313
2384
  }
2314
2385
  }
2315
2386
  }
@@ -2385,7 +2456,9 @@ class UploadTask {
2385
2456
  this._transition("error" /* ERROR */);
2386
2457
  return;
2387
2458
  }
2388
- const uploadRequest = this._ref.storage._makeRequest(requestInfo, newTextConnection, authToken, appCheckToken);
2459
+ const uploadRequest = this._ref.storage._makeRequest(requestInfo, newTextConnection, authToken, appCheckToken,
2460
+ /*retry=*/ false // Upload requests should not be retried as each retry should be preceded by another query request. Which is handled in this file.
2461
+ );
2389
2462
  this._request = uploadRequest;
2390
2463
  uploadRequest.getPromise().then((newStatus) => {
2391
2464
  this._increaseMultiplier();
@@ -2404,7 +2477,7 @@ class UploadTask {
2404
2477
  _increaseMultiplier() {
2405
2478
  const currentSize = RESUMABLE_UPLOAD_CHUNK_SIZE * this._chunkMultiplier;
2406
2479
  // Max chunk size is 32M.
2407
- if (currentSize < 32 * 1024 * 1024) {
2480
+ if (currentSize * 2 < 32 * 1024 * 1024) {
2408
2481
  this._chunkMultiplier *= 2;
2409
2482
  }
2410
2483
  }
@@ -2553,6 +2626,7 @@ class UploadTask {
2553
2626
  * callbacks.
2554
2627
  */
2555
2628
  on(type, nextOrObserver, error, completed) {
2629
+ // Note: `type` isn't being used. Its type is also incorrect. TaskEvent should not be a string.
2556
2630
  const observer = new Observer(nextOrObserver || undefined, error || undefined, completed || undefined);
2557
2631
  this._addObserver(observer);
2558
2632
  return () => {
@@ -3255,9 +3329,9 @@ class FirebaseStorageImpl {
3255
3329
  * @param requestInfo - HTTP RequestInfo object
3256
3330
  * @param authToken - Firebase auth token
3257
3331
  */
3258
- _makeRequest(requestInfo, requestFactory, authToken, appCheckToken) {
3332
+ _makeRequest(requestInfo, requestFactory, authToken, appCheckToken, retry = true) {
3259
3333
  if (!this._deleted) {
3260
- const request = makeRequest(requestInfo, this._appId, authToken, appCheckToken, requestFactory, this._firebaseVersion);
3334
+ const request = makeRequest(requestInfo, this._appId, authToken, appCheckToken, requestFactory, this._firebaseVersion, retry);
3261
3335
  this._requests.add(request);
3262
3336
  // Request removes itself from set when complete.
3263
3337
  request.getPromise().then(() => this._requests.delete(request), () => this._requests.delete(request));
@@ -3277,7 +3351,7 @@ class FirebaseStorageImpl {
3277
3351
  }
3278
3352
 
3279
3353
  const name = "@firebase/storage";
3280
- const version = "0.9.10";
3354
+ const version = "0.9.11-20221010203322";
3281
3355
 
3282
3356
  /**
3283
3357
  * @license
@@ -3492,11 +3566,9 @@ function getStorage(app$1 = app.getApp(), bucketUrl) {
3492
3566
  const storageInstance = storageProvider.getImmediate({
3493
3567
  identifier: bucketUrl
3494
3568
  });
3495
- const storageEmulatorHost = util.getDefaultEmulatorHost('storage');
3496
- if (storageEmulatorHost) {
3497
- const [host, port] = storageEmulatorHost.split(':');
3498
- // eslint-disable-next-line no-restricted-globals
3499
- connectStorageEmulator(storageInstance, host, parseInt(port, 10));
3569
+ const emulator = util.getDefaultEmulatorHostnameAndPort('storage');
3570
+ if (emulator) {
3571
+ connectStorageEmulator(storageInstance, ...emulator);
3500
3572
  }
3501
3573
  return storageInstance;
3502
3574
  }