firebase 9.4.1 → 9.5.0

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/app/dist/index.cjs.js +1 -1
  3. package/app/dist/index.esm.js +1 -1
  4. package/app/dist/index.mjs +1 -1
  5. package/compat/app/dist/index.cjs.js +1 -1
  6. package/compat/app/dist/index.esm.js +1 -1
  7. package/compat/app/dist/index.mjs +1 -1
  8. package/compat/dist/index.esm.js +2 -2
  9. package/compat/dist/index.node.cjs +2 -2
  10. package/compat/dist/index.rn.cjs.js +2 -2
  11. package/firebase-analytics.js +3 -3
  12. package/firebase-app-check-compat.js +2 -2
  13. package/firebase-app-check-compat.js.map +1 -1
  14. package/firebase-app-check.js +285 -50
  15. package/firebase-app-check.js.map +1 -1
  16. package/firebase-app-compat.js +2 -2
  17. package/firebase-app-compat.js.map +1 -1
  18. package/firebase-app.js +9 -9
  19. package/firebase-auth.js +2 -2
  20. package/firebase-compat.js +4 -4
  21. package/firebase-compat.js.map +1 -1
  22. package/firebase-database.js +4 -4
  23. package/firebase-firestore-compat.js +1 -1
  24. package/firebase-firestore-compat.js.map +1 -1
  25. package/firebase-firestore-lite.js +7 -7
  26. package/firebase-firestore-lite.js.map +1 -1
  27. package/firebase-firestore.js +12 -12
  28. package/firebase-firestore.js.map +1 -1
  29. package/firebase-functions.js +2 -2
  30. package/firebase-messaging-sw.js +1 -1
  31. package/firebase-messaging.js +1 -1
  32. package/firebase-performance-standalone-compat.es2017.js +5 -5
  33. package/firebase-performance-standalone-compat.es2017.js.map +1 -1
  34. package/firebase-performance-standalone-compat.js +1 -1
  35. package/firebase-performance-standalone-compat.js.map +1 -1
  36. package/firebase-performance.js +3 -3
  37. package/firebase-remote-config.js +2 -2
  38. package/firebase-storage-compat.js +1 -1
  39. package/firebase-storage-compat.js.map +1 -1
  40. package/firebase-storage.js +177 -24
  41. package/firebase-storage.js.map +1 -1
  42. package/package.json +9 -9
@@ -1,4 +1,4 @@
1
- import { _getProvider, getApp, _registerComponent, registerVersion } from 'https://www.gstatic.com/firebasejs/9.4.1/firebase-app.js';
1
+ import { _getProvider, getApp, _registerComponent, registerVersion } from 'https://www.gstatic.com/firebasejs/9.5.0/firebase-app.js';
2
2
 
3
3
  /**
4
4
  * @license
@@ -571,6 +571,71 @@ const issuedAtTime = function (token) {
571
571
  return null;
572
572
  };
573
573
 
574
+ /**
575
+ * @license
576
+ * Copyright 2019 Google LLC
577
+ *
578
+ * Licensed under the Apache License, Version 2.0 (the "License");
579
+ * you may not use this file except in compliance with the License.
580
+ * You may obtain a copy of the License at
581
+ *
582
+ * http://www.apache.org/licenses/LICENSE-2.0
583
+ *
584
+ * Unless required by applicable law or agreed to in writing, software
585
+ * distributed under the License is distributed on an "AS IS" BASIS,
586
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
587
+ * See the License for the specific language governing permissions and
588
+ * limitations under the License.
589
+ */
590
+ /**
591
+ * The amount of milliseconds to exponentially increase.
592
+ */
593
+ const DEFAULT_INTERVAL_MILLIS = 1000;
594
+ /**
595
+ * The factor to backoff by.
596
+ * Should be a number greater than 1.
597
+ */
598
+ const DEFAULT_BACKOFF_FACTOR = 2;
599
+ /**
600
+ * The maximum milliseconds to increase to.
601
+ *
602
+ * <p>Visible for testing
603
+ */
604
+ const MAX_VALUE_MILLIS = 4 * 60 * 60 * 1000; // Four hours, like iOS and Android.
605
+ /**
606
+ * The percentage of backoff time to randomize by.
607
+ * See
608
+ * http://go/safe-client-behavior#step-1-determine-the-appropriate-retry-interval-to-handle-spike-traffic
609
+ * for context.
610
+ *
611
+ * <p>Visible for testing
612
+ */
613
+ const RANDOM_FACTOR = 0.5;
614
+ /**
615
+ * Based on the backoff method from
616
+ * https://github.com/google/closure-library/blob/master/closure/goog/math/exponentialbackoff.js.
617
+ * Extracted here so we don't need to pass metadata and a stateful ExponentialBackoff object around.
618
+ */
619
+ function calculateBackoffMillis(backoffCount, intervalMillis = DEFAULT_INTERVAL_MILLIS, backoffFactor = DEFAULT_BACKOFF_FACTOR) {
620
+ // Calculates an exponentially increasing value.
621
+ // Deviation: calculates value from count and a constant interval, so we only need to save value
622
+ // and count to restore state.
623
+ const currBaseValue = intervalMillis * Math.pow(backoffFactor, backoffCount);
624
+ // A random "fuzz" to avoid waves of retries.
625
+ // Deviation: randomFactor is required.
626
+ const randomWait = Math.round(
627
+ // A fraction of the backoff value to add/subtract.
628
+ // Deviation: changes multiplication order to improve readability.
629
+ RANDOM_FACTOR *
630
+ currBaseValue *
631
+ // A random float (rounded to int by Math.round above) in the range [-1, 1]. Determines
632
+ // if we add or subtract.
633
+ (Math.random() - 0.5) *
634
+ 2);
635
+ // Limits backoff to max to avoid effectively permanent backoff.
636
+ return Math.min(MAX_VALUE_MILLIS, currBaseValue + randomWait);
637
+ }
638
+
574
639
  /**
575
640
  * @license
576
641
  * Copyright 2021 Google LLC
@@ -862,7 +927,11 @@ const TOKEN_REFRESH_TIME = {
862
927
  * This is the maximum retrial wait, currently 16 minutes.
863
928
  */
864
929
  RETRIAL_MAX_WAIT: 16 * 60 * 1000
865
- };
930
+ };
931
+ /**
932
+ * One day in millis, for certain error code backoffs.
933
+ */
934
+ const ONE_DAY = 24 * 60 * 60 * 1000;
866
935
 
867
936
  /**
868
937
  * @license
@@ -1003,7 +1072,8 @@ const ERRORS = {
1003
1072
  ["storage-open" /* STORAGE_OPEN */]: 'Error thrown when opening storage. Original error: {$originalErrorMessage}.',
1004
1073
  ["storage-get" /* STORAGE_GET */]: 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.',
1005
1074
  ["storage-set" /* STORAGE_WRITE */]: 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
1006
- ["recaptcha-error" /* RECAPTCHA_ERROR */]: 'ReCAPTCHA error.'
1075
+ ["recaptcha-error" /* RECAPTCHA_ERROR */]: 'ReCAPTCHA error.',
1076
+ ["throttled" /* THROTTLED */]: `Requests throttled due to {$httpStatus} error. Attempts allowed again after {$time}`
1007
1077
  };
1008
1078
  const ERROR_FACTORY = new ErrorFactory('appCheck', 'AppCheck', ERRORS);
1009
1079
 
@@ -1045,6 +1115,28 @@ function uuidv4() {
1045
1115
  const r = (Math.random() * 16) | 0, v = c === 'x' ? r : (r & 0x3) | 0x8;
1046
1116
  return v.toString(16);
1047
1117
  });
1118
+ }
1119
+ function getDurationString(durationInMillis) {
1120
+ const totalSeconds = Math.round(durationInMillis / 1000);
1121
+ const days = Math.floor(totalSeconds / (3600 * 24));
1122
+ const hours = Math.floor((totalSeconds - days * 3600 * 24) / 3600);
1123
+ const minutes = Math.floor((totalSeconds - days * 3600 * 24 - hours * 3600) / 60);
1124
+ const seconds = totalSeconds - days * 3600 * 24 - hours * 3600 - minutes * 60;
1125
+ let result = '';
1126
+ if (days) {
1127
+ result += pad(days) + 'd:';
1128
+ }
1129
+ if (hours) {
1130
+ result += pad(hours) + 'h:';
1131
+ }
1132
+ result += pad(minutes) + 'm:' + pad(seconds) + 's';
1133
+ return result;
1134
+ }
1135
+ function pad(value) {
1136
+ if (value === 0) {
1137
+ return '00';
1138
+ }
1139
+ return value >= 10 ? value.toString() : '0' + value;
1048
1140
  }
1049
1141
 
1050
1142
  /**
@@ -1284,7 +1376,7 @@ function computeKey(app) {
1284
1376
  * See the License for the specific language governing permissions and
1285
1377
  * limitations under the License.
1286
1378
  */
1287
- const logger = new Logger('https://www.gstatic.com/firebasejs/9.4.1/firebase-app.js-check');
1379
+ const logger = new Logger('https://www.gstatic.com/firebasejs/9.5.0/firebase-app.js-check');
1288
1380
 
1289
1381
  /**
1290
1382
  * @license
@@ -1462,9 +1554,6 @@ async function getToken$2(appCheck, forceRefresh = false) {
1462
1554
  const cachedToken = await state.cachedTokenPromise;
1463
1555
  if (cachedToken && isValid(cachedToken)) {
1464
1556
  token = cachedToken;
1465
- setState(app, Object.assign(Object.assign({}, state), { token }));
1466
- // notify all listeners with the cached token
1467
- notifyTokenListeners(app, { token: token.token });
1468
1557
  }
1469
1558
  }
1470
1559
  // Return the cached token (from either memory or indexedDB) if it's valid
@@ -1473,13 +1562,25 @@ async function getToken$2(appCheck, forceRefresh = false) {
1473
1562
  token: token.token
1474
1563
  };
1475
1564
  }
1565
+ // Only set to true if this `getToken()` call is making the actual
1566
+ // REST call to the exchange endpoint, versus waiting for an already
1567
+ // in-flight call (see debug and regular exchange endpoint paths below)
1568
+ let shouldCallListeners = false;
1476
1569
  /**
1477
1570
  * DEBUG MODE
1478
1571
  * If debug mode is set, and there is no cached token, fetch a new App
1479
1572
  * Check token using the debug token, and return it directly.
1480
1573
  */
1481
1574
  if (isDebugMode()) {
1482
- const tokenFromDebugExchange = await exchangeToken(getExchangeDebugTokenRequest(app, await getDebugToken()), appCheck.platformLoggerProvider);
1575
+ // Avoid making another call to the exchange endpoint if one is in flight.
1576
+ if (!state.exchangeTokenPromise) {
1577
+ state.exchangeTokenPromise = exchangeToken(getExchangeDebugTokenRequest(app, await getDebugToken()), appCheck.platformLoggerProvider).then(token => {
1578
+ state.exchangeTokenPromise = undefined;
1579
+ return token;
1580
+ });
1581
+ shouldCallListeners = true;
1582
+ }
1583
+ const tokenFromDebugExchange = await state.exchangeTokenPromise;
1483
1584
  // Write debug token to indexedDB.
1484
1585
  await writeTokenToStorage(app, tokenFromDebugExchange);
1485
1586
  // Write debug token to state.
@@ -1490,14 +1591,29 @@ async function getToken$2(appCheck, forceRefresh = false) {
1490
1591
  * request a new token
1491
1592
  */
1492
1593
  try {
1493
- // state.provider is populated in initializeAppCheck()
1494
- // ensureActivated() at the top of this function checks that
1495
- // initializeAppCheck() has been called.
1496
- token = await state.provider.getToken();
1594
+ // Avoid making another call to the exchange endpoint if one is in flight.
1595
+ if (!state.exchangeTokenPromise) {
1596
+ // state.provider is populated in initializeAppCheck()
1597
+ // ensureActivated() at the top of this function checks that
1598
+ // initializeAppCheck() has been called.
1599
+ state.exchangeTokenPromise = state.provider.getToken().then(token => {
1600
+ state.exchangeTokenPromise = undefined;
1601
+ return token;
1602
+ });
1603
+ shouldCallListeners = true;
1604
+ }
1605
+ token = await state.exchangeTokenPromise;
1497
1606
  }
1498
1607
  catch (e) {
1499
- // `getToken()` should never throw, but logging error text to console will aid debugging.
1500
- logger.error(e);
1608
+ if (e.code === `appCheck/${"throttled" /* THROTTLED */}`) {
1609
+ // Warn if throttled, but do not treat it as an error.
1610
+ logger.warn(e.message);
1611
+ }
1612
+ else {
1613
+ // `getToken()` should never throw, but logging error text to console will aid debugging.
1614
+ logger.error(e);
1615
+ }
1616
+ // Always save error to be added to dummy token.
1501
1617
  error = e;
1502
1618
  }
1503
1619
  let interopTokenResult;
@@ -1515,7 +1631,9 @@ async function getToken$2(appCheck, forceRefresh = false) {
1515
1631
  setState(app, Object.assign(Object.assign({}, state), { token }));
1516
1632
  await writeTokenToStorage(app, token);
1517
1633
  }
1518
- notifyTokenListeners(app, interopTokenResult);
1634
+ if (shouldCallListeners) {
1635
+ notifyTokenListeners(app, interopTokenResult);
1636
+ }
1519
1637
  return interopTokenResult;
1520
1638
  }
1521
1639
  function addTokenListener(appCheck, type, listener, onError) {
@@ -1526,44 +1644,31 @@ function addTokenListener(appCheck, type, listener, onError) {
1526
1644
  error: onError,
1527
1645
  type
1528
1646
  };
1529
- const newState = Object.assign(Object.assign({}, state), { tokenObservers: [...state.tokenObservers, tokenObserver] });
1530
- /**
1531
- * Invoke the listener with the valid token, then start the token refresher
1532
- */
1533
- if (!newState.tokenRefresher) {
1534
- const tokenRefresher = createTokenRefresher(appCheck);
1535
- newState.tokenRefresher = tokenRefresher;
1536
- }
1537
- // Create the refresher but don't start it if `isTokenAutoRefreshEnabled`
1538
- // is not true.
1539
- if (!newState.tokenRefresher.isRunning() && state.isTokenAutoRefreshEnabled) {
1540
- newState.tokenRefresher.start();
1541
- }
1647
+ setState(app, Object.assign(Object.assign({}, state), { tokenObservers: [...state.tokenObservers, tokenObserver] }));
1542
1648
  // Invoke the listener async immediately if there is a valid token
1543
1649
  // in memory.
1544
1650
  if (state.token && isValid(state.token)) {
1545
1651
  const validToken = state.token;
1546
1652
  Promise.resolve()
1547
- .then(() => listener({ token: validToken.token }))
1548
- .catch(() => {
1549
- /* we don't care about exceptions thrown in listeners */
1550
- });
1551
- }
1552
- else if (state.token == null) {
1553
- // Only check cache if there was no token. If the token was invalid,
1554
- // skip this and rely on exchange endpoint.
1555
- void state
1556
- .cachedTokenPromise // Storage token promise. Always populated in `activate()`.
1557
- .then(cachedToken => {
1558
- if (cachedToken && isValid(cachedToken)) {
1559
- listener({ token: cachedToken.token });
1560
- }
1653
+ .then(() => {
1654
+ listener({ token: validToken.token });
1655
+ initTokenRefresher(appCheck);
1561
1656
  })
1562
1657
  .catch(() => {
1563
- /** Ignore errors in listeners. */
1658
+ /* we don't care about exceptions thrown in listeners */
1564
1659
  });
1565
1660
  }
1566
- setState(app, newState);
1661
+ /**
1662
+ * Wait for any cached token promise to resolve before starting the token
1663
+ * refresher. The refresher checks to see if there is an existing token
1664
+ * in state and calls the exchange endpoint if not. We should first let the
1665
+ * IndexedDB check have a chance to populate state if it can.
1666
+ *
1667
+ * Listener call isn't needed here because cachedTokenPromise will call any
1668
+ * listeners that exist when it resolves.
1669
+ */
1670
+ // state.cachedTokenPromise is always populated in `activate()`.
1671
+ void state.cachedTokenPromise.then(() => initTokenRefresher(appCheck));
1567
1672
  }
1568
1673
  function removeTokenListener(app, listener) {
1569
1674
  const state = getState(app);
@@ -1575,6 +1680,23 @@ function removeTokenListener(app, listener) {
1575
1680
  }
1576
1681
  setState(app, Object.assign(Object.assign({}, state), { tokenObservers: newObservers }));
1577
1682
  }
1683
+ /**
1684
+ * Logic to create and start refresher as needed.
1685
+ */
1686
+ function initTokenRefresher(appCheck) {
1687
+ const { app } = appCheck;
1688
+ const state = getState(app);
1689
+ // Create the refresher but don't start it if `isTokenAutoRefreshEnabled`
1690
+ // is not true.
1691
+ let refresher = state.tokenRefresher;
1692
+ if (!refresher) {
1693
+ refresher = createTokenRefresher(appCheck);
1694
+ setState(app, Object.assign(Object.assign({}, state), { tokenRefresher: refresher }));
1695
+ }
1696
+ if (!refresher.isRunning() && state.isTokenAutoRefreshEnabled) {
1697
+ refresher.start();
1698
+ }
1699
+ }
1578
1700
  function createTokenRefresher(appCheck) {
1579
1701
  const { app } = appCheck;
1580
1702
  return new Refresher(
@@ -1596,7 +1718,6 @@ function createTokenRefresher(appCheck) {
1596
1718
  throw result.error;
1597
1719
  }
1598
1720
  }, () => {
1599
- // TODO: when should we retry?
1600
1721
  return true;
1601
1722
  }, () => {
1602
1723
  const state = getState(app);
@@ -1691,8 +1812,8 @@ function internalFactory(appCheck) {
1691
1812
  };
1692
1813
  }
1693
1814
 
1694
- const name = "https://www.gstatic.com/firebasejs/9.4.1/firebase-app.js-check";
1695
- const version = "0.5.1";
1815
+ const name = "https://www.gstatic.com/firebasejs/9.5.0/firebase-app.js-check";
1816
+ const version = "0.5.2";
1696
1817
 
1697
1818
  /**
1698
1819
  * @license
@@ -1850,19 +1971,44 @@ class ReCaptchaV3Provider {
1850
1971
  */
1851
1972
  constructor(_siteKey) {
1852
1973
  this._siteKey = _siteKey;
1974
+ /**
1975
+ * Throttle requests on certain error codes to prevent too many retries
1976
+ * in a short time.
1977
+ */
1978
+ this._throttleData = null;
1853
1979
  }
1854
1980
  /**
1855
1981
  * Returns an App Check token.
1856
1982
  * @internal
1857
1983
  */
1858
1984
  async getToken() {
1985
+ var _a;
1986
+ throwIfThrottled(this._throttleData);
1859
1987
  // Top-level `getToken()` has already checked that App Check is initialized
1860
1988
  // and therefore this._app and this._platformLoggerProvider are available.
1861
1989
  const attestedClaimsToken = await getToken$1(this._app).catch(_e => {
1862
1990
  // reCaptcha.execute() throws null which is not very descriptive.
1863
1991
  throw ERROR_FACTORY.create("recaptcha-error" /* RECAPTCHA_ERROR */);
1864
1992
  });
1865
- return exchangeToken(getExchangeRecaptchaV3TokenRequest(this._app, attestedClaimsToken), this._platformLoggerProvider);
1993
+ let result;
1994
+ try {
1995
+ result = await exchangeToken(getExchangeRecaptchaV3TokenRequest(this._app, attestedClaimsToken), this._platformLoggerProvider);
1996
+ }
1997
+ catch (e) {
1998
+ if (e.code === "fetch-status-error" /* FETCH_STATUS_ERROR */) {
1999
+ this._throttleData = setBackoff(Number((_a = e.customData) === null || _a === void 0 ? void 0 : _a.httpStatus), this._throttleData);
2000
+ throw ERROR_FACTORY.create("throttled" /* THROTTLED */, {
2001
+ time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()),
2002
+ httpStatus: this._throttleData.httpStatus
2003
+ });
2004
+ }
2005
+ else {
2006
+ throw e;
2007
+ }
2008
+ }
2009
+ // If successful, clear throttle data.
2010
+ this._throttleData = null;
2011
+ return result;
1866
2012
  }
1867
2013
  /**
1868
2014
  * @internal
@@ -1899,19 +2045,44 @@ class ReCaptchaEnterpriseProvider {
1899
2045
  */
1900
2046
  constructor(_siteKey) {
1901
2047
  this._siteKey = _siteKey;
2048
+ /**
2049
+ * Throttle requests on certain error codes to prevent too many retries
2050
+ * in a short time.
2051
+ */
2052
+ this._throttleData = null;
1902
2053
  }
1903
2054
  /**
1904
2055
  * Returns an App Check token.
1905
2056
  * @internal
1906
2057
  */
1907
2058
  async getToken() {
2059
+ var _a;
2060
+ throwIfThrottled(this._throttleData);
1908
2061
  // Top-level `getToken()` has already checked that App Check is initialized
1909
2062
  // and therefore this._app and this._platformLoggerProvider are available.
1910
2063
  const attestedClaimsToken = await getToken$1(this._app).catch(_e => {
1911
2064
  // reCaptcha.execute() throws null which is not very descriptive.
1912
2065
  throw ERROR_FACTORY.create("recaptcha-error" /* RECAPTCHA_ERROR */);
1913
2066
  });
1914
- return exchangeToken(getExchangeRecaptchaEnterpriseTokenRequest(this._app, attestedClaimsToken), this._platformLoggerProvider);
2067
+ let result;
2068
+ try {
2069
+ result = await exchangeToken(getExchangeRecaptchaEnterpriseTokenRequest(this._app, attestedClaimsToken), this._platformLoggerProvider);
2070
+ }
2071
+ catch (e) {
2072
+ if (e.code === "fetch-status-error" /* FETCH_STATUS_ERROR */) {
2073
+ this._throttleData = setBackoff(Number((_a = e.customData) === null || _a === void 0 ? void 0 : _a.httpStatus), this._throttleData);
2074
+ throw ERROR_FACTORY.create("throttled" /* THROTTLED */, {
2075
+ time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()),
2076
+ httpStatus: this._throttleData.httpStatus
2077
+ });
2078
+ }
2079
+ else {
2080
+ throw e;
2081
+ }
2082
+ }
2083
+ // If successful, clear throttle data.
2084
+ this._throttleData = null;
2085
+ return result;
1915
2086
  }
1916
2087
  /**
1917
2088
  * @internal
@@ -1979,6 +2150,57 @@ class CustomProvider {
1979
2150
  return false;
1980
2151
  }
1981
2152
  }
2153
+ }
2154
+ /**
2155
+ * Set throttle data to block requests until after a certain time
2156
+ * depending on the failed request's status code.
2157
+ * @param httpStatus - Status code of failed request.
2158
+ * @param throttleData - `ThrottleData` object containing previous throttle
2159
+ * data state.
2160
+ * @returns Data about current throttle state and expiration time.
2161
+ */
2162
+ function setBackoff(httpStatus, throttleData) {
2163
+ /**
2164
+ * Block retries for 1 day for the following error codes:
2165
+ *
2166
+ * 404: Likely malformed URL.
2167
+ *
2168
+ * 403:
2169
+ * - Attestation failed
2170
+ * - Wrong API key
2171
+ * - Project deleted
2172
+ */
2173
+ if (httpStatus === 404 || httpStatus === 403) {
2174
+ return {
2175
+ backoffCount: 1,
2176
+ allowRequestsAfter: Date.now() + ONE_DAY,
2177
+ httpStatus
2178
+ };
2179
+ }
2180
+ else {
2181
+ /**
2182
+ * For all other error codes, the time when it is ok to retry again
2183
+ * is based on exponential backoff.
2184
+ */
2185
+ const backoffCount = throttleData ? throttleData.backoffCount : 0;
2186
+ const backoffMillis = calculateBackoffMillis(backoffCount, 1000, 2);
2187
+ return {
2188
+ backoffCount: backoffCount + 1,
2189
+ allowRequestsAfter: Date.now() + backoffMillis,
2190
+ httpStatus
2191
+ };
2192
+ }
2193
+ }
2194
+ function throwIfThrottled(throttleData) {
2195
+ if (throttleData) {
2196
+ if (Date.now() - throttleData.allowRequestsAfter <= 0) {
2197
+ // If before, throw.
2198
+ throw ERROR_FACTORY.create("throttled" /* THROTTLED */, {
2199
+ time: getDurationString(throttleData.allowRequestsAfter - Date.now()),
2200
+ httpStatus: throttleData.httpStatus
2201
+ });
2202
+ }
2203
+ }
1982
2204
  }
1983
2205
 
1984
2206
  /**
@@ -1999,7 +2221,7 @@ class CustomProvider {
1999
2221
  */
2000
2222
  /**
2001
2223
  * Activate App Check for the given app. Can be called only once per app.
2002
- * @param app - the {@link https://www.gstatic.com/firebasejs/9.4.1/firebase-app.js#FirebaseApp} to activate App Check for
2224
+ * @param app - the {@link https://www.gstatic.com/firebasejs/9.5.0/firebase-app.js#FirebaseApp} to activate App Check for
2003
2225
  * @param options - App Check initialization options
2004
2226
  * @public
2005
2227
  */
@@ -2034,6 +2256,17 @@ function initializeAppCheck(app = getApp(), options) {
2034
2256
  }
2035
2257
  const appCheck = provider.initialize({ options });
2036
2258
  _activate(app, options.provider, options.isTokenAutoRefreshEnabled);
2259
+ // If isTokenAutoRefreshEnabled is false, do not send any requests to the
2260
+ // exchange endpoint without an explicit call from the user either directly
2261
+ // or through another Firebase library (storage, functions, etc.)
2262
+ if (getState(app).isTokenAutoRefreshEnabled) {
2263
+ // Adding a listener will start the refresher and fetch a token if needed.
2264
+ // This gets a token ready and prevents a delay when an internal library
2265
+ // requests the token.
2266
+ // Listener function does not need to do anything, its base functionality
2267
+ // of calling getToken() already fetches token and writes it to memory/storage.
2268
+ addTokenListener(appCheck, "INTERNAL" /* INTERNAL */, () => { });
2269
+ }
2037
2270
  return appCheck;
2038
2271
  }
2039
2272
  /**
@@ -2053,6 +2286,8 @@ function _activate(app, provider, isTokenAutoRefreshEnabled) {
2053
2286
  newState.cachedTokenPromise = readTokenFromStorage(app).then(cachedToken => {
2054
2287
  if (cachedToken && isValid(cachedToken)) {
2055
2288
  setState(app, Object.assign(Object.assign({}, getState(app)), { token: cachedToken }));
2289
+ // notify all listeners with the cached token
2290
+ notifyTokenListeners(app, { token: cachedToken.token });
2056
2291
  }
2057
2292
  return cachedToken;
2058
2293
  });