No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.esm2017.js 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. import { Component, ComponentContainer } from '@firebase/component';
  2. import { Logger, setUserLogHandler, setLogLevel as setLogLevel$1 } from '@firebase/logger';
  3. import { ErrorFactory, getDefaultAppConfig, deepEqual, FirebaseError, base64urlEncodeWithoutPadding, isIndexedDBAvailable, validateIndexedDBOpenable } from '@firebase/util';
  4. export { FirebaseError } from '@firebase/util';
  5. import { openDB } from 'idb';
  6. /**
  7. * @license
  8. * Copyright 2019 Google LLC
  9. *
  10. * Licensed under the Apache License, Version 2.0 (the "License");
  11. * you may not use this file except in compliance with the License.
  12. * You may obtain a copy of the License at
  13. *
  14. * http://www.apache.org/licenses/LICENSE-2.0
  15. *
  16. * Unless required by applicable law or agreed to in writing, software
  17. * distributed under the License is distributed on an "AS IS" BASIS,
  18. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  19. * See the License for the specific language governing permissions and
  20. * limitations under the License.
  21. */
  22. class PlatformLoggerServiceImpl {
  23. constructor(container) {
  24. this.container = container;
  25. }
  26. // In initial implementation, this will be called by installations on
  27. // auth token refresh, and installations will send this string.
  28. getPlatformInfoString() {
  29. const providers = this.container.getProviders();
  30. // Loop through providers and get library/version pairs from any that are
  31. // version components.
  32. return providers
  33. .map(provider => {
  34. if (isVersionServiceProvider(provider)) {
  35. const service = provider.getImmediate();
  36. return `${service.library}/${service.version}`;
  37. }
  38. else {
  39. return null;
  40. }
  41. })
  42. .filter(logString => logString)
  43. .join(' ');
  44. }
  45. }
  46. /**
  47. *
  48. * @param provider check if this provider provides a VersionService
  49. *
  50. * NOTE: Using Provider<'app-version'> is a hack to indicate that the provider
  51. * provides VersionService. The provider is not necessarily a 'app-version'
  52. * provider.
  53. */
  54. function isVersionServiceProvider(provider) {
  55. const component = provider.getComponent();
  56. return (component === null || component === void 0 ? void 0 : component.type) === "VERSION" /* ComponentType.VERSION */;
  57. }
  58. const name$o = "@firebase/app";
  59. const version$1 = "0.9.1";
  60. /**
  61. * @license
  62. * Copyright 2019 Google LLC
  63. *
  64. * Licensed under the Apache License, Version 2.0 (the "License");
  65. * you may not use this file except in compliance with the License.
  66. * You may obtain a copy of the License at
  67. *
  68. * http://www.apache.org/licenses/LICENSE-2.0
  69. *
  70. * Unless required by applicable law or agreed to in writing, software
  71. * distributed under the License is distributed on an "AS IS" BASIS,
  72. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  73. * See the License for the specific language governing permissions and
  74. * limitations under the License.
  75. */
  76. const logger = new Logger('@firebase/app');
  77. const name$n = "@firebase/app-compat";
  78. const name$m = "@firebase/analytics-compat";
  79. const name$l = "@firebase/analytics";
  80. const name$k = "@firebase/app-check-compat";
  81. const name$j = "@firebase/app-check";
  82. const name$i = "@firebase/auth";
  83. const name$h = "@firebase/auth-compat";
  84. const name$g = "@firebase/database";
  85. const name$f = "@firebase/database-compat";
  86. const name$e = "@firebase/functions";
  87. const name$d = "@firebase/functions-compat";
  88. const name$c = "@firebase/installations";
  89. const name$b = "@firebase/installations-compat";
  90. const name$a = "@firebase/messaging";
  91. const name$9 = "@firebase/messaging-compat";
  92. const name$8 = "@firebase/performance";
  93. const name$7 = "@firebase/performance-compat";
  94. const name$6 = "@firebase/remote-config";
  95. const name$5 = "@firebase/remote-config-compat";
  96. const name$4 = "@firebase/storage";
  97. const name$3 = "@firebase/storage-compat";
  98. const name$2 = "@firebase/firestore";
  99. const name$1 = "@firebase/firestore-compat";
  100. const name = "firebase";
  101. const version = "9.16.0";
  102. /**
  103. * @license
  104. * Copyright 2019 Google LLC
  105. *
  106. * Licensed under the Apache License, Version 2.0 (the "License");
  107. * you may not use this file except in compliance with the License.
  108. * You may obtain a copy of the License at
  109. *
  110. * http://www.apache.org/licenses/LICENSE-2.0
  111. *
  112. * Unless required by applicable law or agreed to in writing, software
  113. * distributed under the License is distributed on an "AS IS" BASIS,
  114. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  115. * See the License for the specific language governing permissions and
  116. * limitations under the License.
  117. */
  118. /**
  119. * The default app name
  120. *
  121. * @internal
  122. */
  123. const DEFAULT_ENTRY_NAME = '[DEFAULT]';
  124. const PLATFORM_LOG_STRING = {
  125. [name$o]: 'fire-core',
  126. [name$n]: 'fire-core-compat',
  127. [name$l]: 'fire-analytics',
  128. [name$m]: 'fire-analytics-compat',
  129. [name$j]: 'fire-app-check',
  130. [name$k]: 'fire-app-check-compat',
  131. [name$i]: 'fire-auth',
  132. [name$h]: 'fire-auth-compat',
  133. [name$g]: 'fire-rtdb',
  134. [name$f]: 'fire-rtdb-compat',
  135. [name$e]: 'fire-fn',
  136. [name$d]: 'fire-fn-compat',
  137. [name$c]: 'fire-iid',
  138. [name$b]: 'fire-iid-compat',
  139. [name$a]: 'fire-fcm',
  140. [name$9]: 'fire-fcm-compat',
  141. [name$8]: 'fire-perf',
  142. [name$7]: 'fire-perf-compat',
  143. [name$6]: 'fire-rc',
  144. [name$5]: 'fire-rc-compat',
  145. [name$4]: 'fire-gcs',
  146. [name$3]: 'fire-gcs-compat',
  147. [name$2]: 'fire-fst',
  148. [name$1]: 'fire-fst-compat',
  149. 'fire-js': 'fire-js',
  150. [name]: 'fire-js-all'
  151. };
  152. /**
  153. * @license
  154. * Copyright 2019 Google LLC
  155. *
  156. * Licensed under the Apache License, Version 2.0 (the "License");
  157. * you may not use this file except in compliance with the License.
  158. * You may obtain a copy of the License at
  159. *
  160. * http://www.apache.org/licenses/LICENSE-2.0
  161. *
  162. * Unless required by applicable law or agreed to in writing, software
  163. * distributed under the License is distributed on an "AS IS" BASIS,
  164. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  165. * See the License for the specific language governing permissions and
  166. * limitations under the License.
  167. */
  168. /**
  169. * @internal
  170. */
  171. const _apps = new Map();
  172. /**
  173. * Registered components.
  174. *
  175. * @internal
  176. */
  177. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  178. const _components = new Map();
  179. /**
  180. * @param component - the component being added to this app's container
  181. *
  182. * @internal
  183. */
  184. function _addComponent(app, component) {
  185. try {
  186. app.container.addComponent(component);
  187. }
  188. catch (e) {
  189. logger.debug(`Component ${component.name} failed to register with FirebaseApp ${app.name}`, e);
  190. }
  191. }
  192. /**
  193. *
  194. * @internal
  195. */
  196. function _addOrOverwriteComponent(app, component) {
  197. app.container.addOrOverwriteComponent(component);
  198. }
  199. /**
  200. *
  201. * @param component - the component to register
  202. * @returns whether or not the component is registered successfully
  203. *
  204. * @internal
  205. */
  206. function _registerComponent(component) {
  207. const componentName = component.name;
  208. if (_components.has(componentName)) {
  209. logger.debug(`There were multiple attempts to register component ${componentName}.`);
  210. return false;
  211. }
  212. _components.set(componentName, component);
  213. // add the component to existing app instances
  214. for (const app of _apps.values()) {
  215. _addComponent(app, component);
  216. }
  217. return true;
  218. }
  219. /**
  220. *
  221. * @param app - FirebaseApp instance
  222. * @param name - service name
  223. *
  224. * @returns the provider for the service with the matching name
  225. *
  226. * @internal
  227. */
  228. function _getProvider(app, name) {
  229. const heartbeatController = app.container
  230. .getProvider('heartbeat')
  231. .getImmediate({ optional: true });
  232. if (heartbeatController) {
  233. void heartbeatController.triggerHeartbeat();
  234. }
  235. return app.container.getProvider(name);
  236. }
  237. /**
  238. *
  239. * @param app - FirebaseApp instance
  240. * @param name - service name
  241. * @param instanceIdentifier - service instance identifier in case the service supports multiple instances
  242. *
  243. * @internal
  244. */
  245. function _removeServiceInstance(app, name, instanceIdentifier = DEFAULT_ENTRY_NAME) {
  246. _getProvider(app, name).clearInstance(instanceIdentifier);
  247. }
  248. /**
  249. * Test only
  250. *
  251. * @internal
  252. */
  253. function _clearComponents() {
  254. _components.clear();
  255. }
  256. /**
  257. * @license
  258. * Copyright 2019 Google LLC
  259. *
  260. * Licensed under the Apache License, Version 2.0 (the "License");
  261. * you may not use this file except in compliance with the License.
  262. * You may obtain a copy of the License at
  263. *
  264. * http://www.apache.org/licenses/LICENSE-2.0
  265. *
  266. * Unless required by applicable law or agreed to in writing, software
  267. * distributed under the License is distributed on an "AS IS" BASIS,
  268. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  269. * See the License for the specific language governing permissions and
  270. * limitations under the License.
  271. */
  272. const ERRORS = {
  273. ["no-app" /* AppError.NO_APP */]: "No Firebase App '{$appName}' has been created - " +
  274. 'call Firebase App.initializeApp()',
  275. ["bad-app-name" /* AppError.BAD_APP_NAME */]: "Illegal App name: '{$appName}",
  276. ["duplicate-app" /* AppError.DUPLICATE_APP */]: "Firebase App named '{$appName}' already exists with different options or config",
  277. ["app-deleted" /* AppError.APP_DELETED */]: "Firebase App named '{$appName}' already deleted",
  278. ["no-options" /* AppError.NO_OPTIONS */]: 'Need to provide options, when not being deployed to hosting via source.',
  279. ["invalid-app-argument" /* AppError.INVALID_APP_ARGUMENT */]: 'firebase.{$appName}() takes either no argument or a ' +
  280. 'Firebase App instance.',
  281. ["invalid-log-argument" /* AppError.INVALID_LOG_ARGUMENT */]: 'First argument to `onLog` must be null or a function.',
  282. ["idb-open" /* AppError.IDB_OPEN */]: 'Error thrown when opening IndexedDB. Original error: {$originalErrorMessage}.',
  283. ["idb-get" /* AppError.IDB_GET */]: 'Error thrown when reading from IndexedDB. Original error: {$originalErrorMessage}.',
  284. ["idb-set" /* AppError.IDB_WRITE */]: 'Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.',
  285. ["idb-delete" /* AppError.IDB_DELETE */]: 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.'
  286. };
  287. const ERROR_FACTORY = new ErrorFactory('app', 'Firebase', ERRORS);
  288. /**
  289. * @license
  290. * Copyright 2019 Google LLC
  291. *
  292. * Licensed under the Apache License, Version 2.0 (the "License");
  293. * you may not use this file except in compliance with the License.
  294. * You may obtain a copy of the License at
  295. *
  296. * http://www.apache.org/licenses/LICENSE-2.0
  297. *
  298. * Unless required by applicable law or agreed to in writing, software
  299. * distributed under the License is distributed on an "AS IS" BASIS,
  300. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  301. * See the License for the specific language governing permissions and
  302. * limitations under the License.
  303. */
  304. class FirebaseAppImpl {
  305. constructor(options, config, container) {
  306. this._isDeleted = false;
  307. this._options = Object.assign({}, options);
  308. this._config = Object.assign({}, config);
  309. this._name = config.name;
  310. this._automaticDataCollectionEnabled =
  311. config.automaticDataCollectionEnabled;
  312. this._container = container;
  313. this.container.addComponent(new Component('app', () => this, "PUBLIC" /* ComponentType.PUBLIC */));
  314. }
  315. get automaticDataCollectionEnabled() {
  316. this.checkDestroyed();
  317. return this._automaticDataCollectionEnabled;
  318. }
  319. set automaticDataCollectionEnabled(val) {
  320. this.checkDestroyed();
  321. this._automaticDataCollectionEnabled = val;
  322. }
  323. get name() {
  324. this.checkDestroyed();
  325. return this._name;
  326. }
  327. get options() {
  328. this.checkDestroyed();
  329. return this._options;
  330. }
  331. get config() {
  332. this.checkDestroyed();
  333. return this._config;
  334. }
  335. get container() {
  336. return this._container;
  337. }
  338. get isDeleted() {
  339. return this._isDeleted;
  340. }
  341. set isDeleted(val) {
  342. this._isDeleted = val;
  343. }
  344. /**
  345. * This function will throw an Error if the App has already been deleted -
  346. * use before performing API actions on the App.
  347. */
  348. checkDestroyed() {
  349. if (this.isDeleted) {
  350. throw ERROR_FACTORY.create("app-deleted" /* AppError.APP_DELETED */, { appName: this._name });
  351. }
  352. }
  353. }
  354. /**
  355. * @license
  356. * Copyright 2019 Google LLC
  357. *
  358. * Licensed under the Apache License, Version 2.0 (the "License");
  359. * you may not use this file except in compliance with the License.
  360. * You may obtain a copy of the License at
  361. *
  362. * http://www.apache.org/licenses/LICENSE-2.0
  363. *
  364. * Unless required by applicable law or agreed to in writing, software
  365. * distributed under the License is distributed on an "AS IS" BASIS,
  366. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  367. * See the License for the specific language governing permissions and
  368. * limitations under the License.
  369. */
  370. /**
  371. * The current SDK version.
  372. *
  373. * @public
  374. */
  375. const SDK_VERSION = version;
  376. function initializeApp(_options, rawConfig = {}) {
  377. let options = _options;
  378. if (typeof rawConfig !== 'object') {
  379. const name = rawConfig;
  380. rawConfig = { name };
  381. }
  382. const config = Object.assign({ name: DEFAULT_ENTRY_NAME, automaticDataCollectionEnabled: false }, rawConfig);
  383. const name = config.name;
  384. if (typeof name !== 'string' || !name) {
  385. throw ERROR_FACTORY.create("bad-app-name" /* AppError.BAD_APP_NAME */, {
  386. appName: String(name)
  387. });
  388. }
  389. options || (options = getDefaultAppConfig());
  390. if (!options) {
  391. throw ERROR_FACTORY.create("no-options" /* AppError.NO_OPTIONS */);
  392. }
  393. const existingApp = _apps.get(name);
  394. if (existingApp) {
  395. // return the existing app if options and config deep equal the ones in the existing app.
  396. if (deepEqual(options, existingApp.options) &&
  397. deepEqual(config, existingApp.config)) {
  398. return existingApp;
  399. }
  400. else {
  401. throw ERROR_FACTORY.create("duplicate-app" /* AppError.DUPLICATE_APP */, { appName: name });
  402. }
  403. }
  404. const container = new ComponentContainer(name);
  405. for (const component of _components.values()) {
  406. container.addComponent(component);
  407. }
  408. const newApp = new FirebaseAppImpl(options, config, container);
  409. _apps.set(name, newApp);
  410. return newApp;
  411. }
  412. /**
  413. * Retrieves a {@link @firebase/app#FirebaseApp} instance.
  414. *
  415. * When called with no arguments, the default app is returned. When an app name
  416. * is provided, the app corresponding to that name is returned.
  417. *
  418. * An exception is thrown if the app being retrieved has not yet been
  419. * initialized.
  420. *
  421. * @example
  422. * ```javascript
  423. * // Return the default app
  424. * const app = getApp();
  425. * ```
  426. *
  427. * @example
  428. * ```javascript
  429. * // Return a named app
  430. * const otherApp = getApp("otherApp");
  431. * ```
  432. *
  433. * @param name - Optional name of the app to return. If no name is
  434. * provided, the default is `"[DEFAULT]"`.
  435. *
  436. * @returns The app corresponding to the provided app name.
  437. * If no app name is provided, the default app is returned.
  438. *
  439. * @public
  440. */
  441. function getApp(name = DEFAULT_ENTRY_NAME) {
  442. const app = _apps.get(name);
  443. if (!app && name === DEFAULT_ENTRY_NAME) {
  444. return initializeApp();
  445. }
  446. if (!app) {
  447. throw ERROR_FACTORY.create("no-app" /* AppError.NO_APP */, { appName: name });
  448. }
  449. return app;
  450. }
  451. /**
  452. * A (read-only) array of all initialized apps.
  453. * @public
  454. */
  455. function getApps() {
  456. return Array.from(_apps.values());
  457. }
  458. /**
  459. * Renders this app unusable and frees the resources of all associated
  460. * services.
  461. *
  462. * @example
  463. * ```javascript
  464. * deleteApp(app)
  465. * .then(function() {
  466. * console.log("App deleted successfully");
  467. * })
  468. * .catch(function(error) {
  469. * console.log("Error deleting app:", error);
  470. * });
  471. * ```
  472. *
  473. * @public
  474. */
  475. async function deleteApp(app) {
  476. const name = app.name;
  477. if (_apps.has(name)) {
  478. _apps.delete(name);
  479. await Promise.all(app.container
  480. .getProviders()
  481. .map(provider => provider.delete()));
  482. app.isDeleted = true;
  483. }
  484. }
  485. /**
  486. * Registers a library's name and version for platform logging purposes.
  487. * @param library - Name of 1p or 3p library (e.g. firestore, angularfire)
  488. * @param version - Current version of that library.
  489. * @param variant - Bundle variant, e.g., node, rn, etc.
  490. *
  491. * @public
  492. */
  493. function registerVersion(libraryKeyOrName, version, variant) {
  494. var _a;
  495. // TODO: We can use this check to whitelist strings when/if we set up
  496. // a good whitelist system.
  497. let library = (_a = PLATFORM_LOG_STRING[libraryKeyOrName]) !== null && _a !== void 0 ? _a : libraryKeyOrName;
  498. if (variant) {
  499. library += `-${variant}`;
  500. }
  501. const libraryMismatch = library.match(/\s|\//);
  502. const versionMismatch = version.match(/\s|\//);
  503. if (libraryMismatch || versionMismatch) {
  504. const warning = [
  505. `Unable to register library "${library}" with version "${version}":`
  506. ];
  507. if (libraryMismatch) {
  508. warning.push(`library name "${library}" contains illegal characters (whitespace or "/")`);
  509. }
  510. if (libraryMismatch && versionMismatch) {
  511. warning.push('and');
  512. }
  513. if (versionMismatch) {
  514. warning.push(`version name "${version}" contains illegal characters (whitespace or "/")`);
  515. }
  516. logger.warn(warning.join(' '));
  517. return;
  518. }
  519. _registerComponent(new Component(`${library}-version`, () => ({ library, version }), "VERSION" /* ComponentType.VERSION */));
  520. }
  521. /**
  522. * Sets log handler for all Firebase SDKs.
  523. * @param logCallback - An optional custom log handler that executes user code whenever
  524. * the Firebase SDK makes a logging call.
  525. *
  526. * @public
  527. */
  528. function onLog(logCallback, options) {
  529. if (logCallback !== null && typeof logCallback !== 'function') {
  530. throw ERROR_FACTORY.create("invalid-log-argument" /* AppError.INVALID_LOG_ARGUMENT */);
  531. }
  532. setUserLogHandler(logCallback, options);
  533. }
  534. /**
  535. * Sets log level for all Firebase SDKs.
  536. *
  537. * All of the log types above the current log level are captured (i.e. if
  538. * you set the log level to `info`, errors are logged, but `debug` and
  539. * `verbose` logs are not).
  540. *
  541. * @public
  542. */
  543. function setLogLevel(logLevel) {
  544. setLogLevel$1(logLevel);
  545. }
  546. /**
  547. * @license
  548. * Copyright 2021 Google LLC
  549. *
  550. * Licensed under the Apache License, Version 2.0 (the "License");
  551. * you may not use this file except in compliance with the License.
  552. * You may obtain a copy of the License at
  553. *
  554. * http://www.apache.org/licenses/LICENSE-2.0
  555. *
  556. * Unless required by applicable law or agreed to in writing, software
  557. * distributed under the License is distributed on an "AS IS" BASIS,
  558. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  559. * See the License for the specific language governing permissions and
  560. * limitations under the License.
  561. */
  562. const DB_NAME = 'firebase-heartbeat-database';
  563. const DB_VERSION = 1;
  564. const STORE_NAME = 'firebase-heartbeat-store';
  565. let dbPromise = null;
  566. function getDbPromise() {
  567. if (!dbPromise) {
  568. dbPromise = openDB(DB_NAME, DB_VERSION, {
  569. upgrade: (db, oldVersion) => {
  570. // We don't use 'break' in this switch statement, the fall-through
  571. // behavior is what we want, because if there are multiple versions between
  572. // the old version and the current version, we want ALL the migrations
  573. // that correspond to those versions to run, not only the last one.
  574. // eslint-disable-next-line default-case
  575. switch (oldVersion) {
  576. case 0:
  577. db.createObjectStore(STORE_NAME);
  578. }
  579. }
  580. }).catch(e => {
  581. throw ERROR_FACTORY.create("idb-open" /* AppError.IDB_OPEN */, {
  582. originalErrorMessage: e.message
  583. });
  584. });
  585. }
  586. return dbPromise;
  587. }
  588. async function readHeartbeatsFromIndexedDB(app) {
  589. try {
  590. const db = await getDbPromise();
  591. return db
  592. .transaction(STORE_NAME)
  593. .objectStore(STORE_NAME)
  594. .get(computeKey(app));
  595. }
  596. catch (e) {
  597. if (e instanceof FirebaseError) {
  598. logger.warn(e.message);
  599. }
  600. else {
  601. const idbGetError = ERROR_FACTORY.create("idb-get" /* AppError.IDB_GET */, {
  602. originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
  603. });
  604. logger.warn(idbGetError.message);
  605. }
  606. }
  607. }
  608. async function writeHeartbeatsToIndexedDB(app, heartbeatObject) {
  609. try {
  610. const db = await getDbPromise();
  611. const tx = db.transaction(STORE_NAME, 'readwrite');
  612. const objectStore = tx.objectStore(STORE_NAME);
  613. await objectStore.put(heartbeatObject, computeKey(app));
  614. return tx.done;
  615. }
  616. catch (e) {
  617. if (e instanceof FirebaseError) {
  618. logger.warn(e.message);
  619. }
  620. else {
  621. const idbGetError = ERROR_FACTORY.create("idb-set" /* AppError.IDB_WRITE */, {
  622. originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
  623. });
  624. logger.warn(idbGetError.message);
  625. }
  626. }
  627. }
  628. function computeKey(app) {
  629. return `${app.name}!${app.options.appId}`;
  630. }
  631. /**
  632. * @license
  633. * Copyright 2021 Google LLC
  634. *
  635. * Licensed under the Apache License, Version 2.0 (the "License");
  636. * you may not use this file except in compliance with the License.
  637. * You may obtain a copy of the License at
  638. *
  639. * http://www.apache.org/licenses/LICENSE-2.0
  640. *
  641. * Unless required by applicable law or agreed to in writing, software
  642. * distributed under the License is distributed on an "AS IS" BASIS,
  643. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  644. * See the License for the specific language governing permissions and
  645. * limitations under the License.
  646. */
  647. const MAX_HEADER_BYTES = 1024;
  648. // 30 days
  649. const STORED_HEARTBEAT_RETENTION_MAX_MILLIS = 30 * 24 * 60 * 60 * 1000;
  650. class HeartbeatServiceImpl {
  651. constructor(container) {
  652. this.container = container;
  653. /**
  654. * In-memory cache for heartbeats, used by getHeartbeatsHeader() to generate
  655. * the header string.
  656. * Stores one record per date. This will be consolidated into the standard
  657. * format of one record per user agent string before being sent as a header.
  658. * Populated from indexedDB when the controller is instantiated and should
  659. * be kept in sync with indexedDB.
  660. * Leave public for easier testing.
  661. */
  662. this._heartbeatsCache = null;
  663. const app = this.container.getProvider('app').getImmediate();
  664. this._storage = new HeartbeatStorageImpl(app);
  665. this._heartbeatsCachePromise = this._storage.read().then(result => {
  666. this._heartbeatsCache = result;
  667. return result;
  668. });
  669. }
  670. /**
  671. * Called to report a heartbeat. The function will generate
  672. * a HeartbeatsByUserAgent object, update heartbeatsCache, and persist it
  673. * to IndexedDB.
  674. * Note that we only store one heartbeat per day. So if a heartbeat for today is
  675. * already logged, subsequent calls to this function in the same day will be ignored.
  676. */
  677. async triggerHeartbeat() {
  678. const platformLogger = this.container
  679. .getProvider('platform-logger')
  680. .getImmediate();
  681. // This is the "Firebase user agent" string from the platform logger
  682. // service, not the browser user agent.
  683. const agent = platformLogger.getPlatformInfoString();
  684. const date = getUTCDateString();
  685. if (this._heartbeatsCache === null) {
  686. this._heartbeatsCache = await this._heartbeatsCachePromise;
  687. }
  688. // Do not store a heartbeat if one is already stored for this day
  689. // or if a header has already been sent today.
  690. if (this._heartbeatsCache.lastSentHeartbeatDate === date ||
  691. this._heartbeatsCache.heartbeats.some(singleDateHeartbeat => singleDateHeartbeat.date === date)) {
  692. return;
  693. }
  694. else {
  695. // There is no entry for this date. Create one.
  696. this._heartbeatsCache.heartbeats.push({ date, agent });
  697. }
  698. // Remove entries older than 30 days.
  699. this._heartbeatsCache.heartbeats = this._heartbeatsCache.heartbeats.filter(singleDateHeartbeat => {
  700. const hbTimestamp = new Date(singleDateHeartbeat.date).valueOf();
  701. const now = Date.now();
  702. return now - hbTimestamp <= STORED_HEARTBEAT_RETENTION_MAX_MILLIS;
  703. });
  704. return this._storage.overwrite(this._heartbeatsCache);
  705. }
  706. /**
  707. * Returns a base64 encoded string which can be attached to the heartbeat-specific header directly.
  708. * It also clears all heartbeats from memory as well as in IndexedDB.
  709. *
  710. * NOTE: Consuming product SDKs should not send the header if this method
  711. * returns an empty string.
  712. */
  713. async getHeartbeatsHeader() {
  714. if (this._heartbeatsCache === null) {
  715. await this._heartbeatsCachePromise;
  716. }
  717. // If it's still null or the array is empty, there is no data to send.
  718. if (this._heartbeatsCache === null ||
  719. this._heartbeatsCache.heartbeats.length === 0) {
  720. return '';
  721. }
  722. const date = getUTCDateString();
  723. // Extract as many heartbeats from the cache as will fit under the size limit.
  724. const { heartbeatsToSend, unsentEntries } = extractHeartbeatsForHeader(this._heartbeatsCache.heartbeats);
  725. const headerString = base64urlEncodeWithoutPadding(JSON.stringify({ version: 2, heartbeats: heartbeatsToSend }));
  726. // Store last sent date to prevent another being logged/sent for the same day.
  727. this._heartbeatsCache.lastSentHeartbeatDate = date;
  728. if (unsentEntries.length > 0) {
  729. // Store any unsent entries if they exist.
  730. this._heartbeatsCache.heartbeats = unsentEntries;
  731. // This seems more likely than emptying the array (below) to lead to some odd state
  732. // since the cache isn't empty and this will be called again on the next request,
  733. // and is probably safest if we await it.
  734. await this._storage.overwrite(this._heartbeatsCache);
  735. }
  736. else {
  737. this._heartbeatsCache.heartbeats = [];
  738. // Do not wait for this, to reduce latency.
  739. void this._storage.overwrite(this._heartbeatsCache);
  740. }
  741. return headerString;
  742. }
  743. }
  744. function getUTCDateString() {
  745. const today = new Date();
  746. // Returns date format 'YYYY-MM-DD'
  747. return today.toISOString().substring(0, 10);
  748. }
  749. function extractHeartbeatsForHeader(heartbeatsCache, maxSize = MAX_HEADER_BYTES) {
  750. // Heartbeats grouped by user agent in the standard format to be sent in
  751. // the header.
  752. const heartbeatsToSend = [];
  753. // Single date format heartbeats that are not sent.
  754. let unsentEntries = heartbeatsCache.slice();
  755. for (const singleDateHeartbeat of heartbeatsCache) {
  756. // Look for an existing entry with the same user agent.
  757. const heartbeatEntry = heartbeatsToSend.find(hb => hb.agent === singleDateHeartbeat.agent);
  758. if (!heartbeatEntry) {
  759. // If no entry for this user agent exists, create one.
  760. heartbeatsToSend.push({
  761. agent: singleDateHeartbeat.agent,
  762. dates: [singleDateHeartbeat.date]
  763. });
  764. if (countBytes(heartbeatsToSend) > maxSize) {
  765. // If the header would exceed max size, remove the added heartbeat
  766. // entry and stop adding to the header.
  767. heartbeatsToSend.pop();
  768. break;
  769. }
  770. }
  771. else {
  772. heartbeatEntry.dates.push(singleDateHeartbeat.date);
  773. // If the header would exceed max size, remove the added date
  774. // and stop adding to the header.
  775. if (countBytes(heartbeatsToSend) > maxSize) {
  776. heartbeatEntry.dates.pop();
  777. break;
  778. }
  779. }
  780. // Pop unsent entry from queue. (Skipped if adding the entry exceeded
  781. // quota and the loop breaks early.)
  782. unsentEntries = unsentEntries.slice(1);
  783. }
  784. return {
  785. heartbeatsToSend,
  786. unsentEntries
  787. };
  788. }
  789. class HeartbeatStorageImpl {
  790. constructor(app) {
  791. this.app = app;
  792. this._canUseIndexedDBPromise = this.runIndexedDBEnvironmentCheck();
  793. }
  794. async runIndexedDBEnvironmentCheck() {
  795. if (!isIndexedDBAvailable()) {
  796. return false;
  797. }
  798. else {
  799. return validateIndexedDBOpenable()
  800. .then(() => true)
  801. .catch(() => false);
  802. }
  803. }
  804. /**
  805. * Read all heartbeats.
  806. */
  807. async read() {
  808. const canUseIndexedDB = await this._canUseIndexedDBPromise;
  809. if (!canUseIndexedDB) {
  810. return { heartbeats: [] };
  811. }
  812. else {
  813. const idbHeartbeatObject = await readHeartbeatsFromIndexedDB(this.app);
  814. return idbHeartbeatObject || { heartbeats: [] };
  815. }
  816. }
  817. // overwrite the storage with the provided heartbeats
  818. async overwrite(heartbeatsObject) {
  819. var _a;
  820. const canUseIndexedDB = await this._canUseIndexedDBPromise;
  821. if (!canUseIndexedDB) {
  822. return;
  823. }
  824. else {
  825. const existingHeartbeatsObject = await this.read();
  826. return writeHeartbeatsToIndexedDB(this.app, {
  827. lastSentHeartbeatDate: (_a = heartbeatsObject.lastSentHeartbeatDate) !== null && _a !== void 0 ? _a : existingHeartbeatsObject.lastSentHeartbeatDate,
  828. heartbeats: heartbeatsObject.heartbeats
  829. });
  830. }
  831. }
  832. // add heartbeats
  833. async add(heartbeatsObject) {
  834. var _a;
  835. const canUseIndexedDB = await this._canUseIndexedDBPromise;
  836. if (!canUseIndexedDB) {
  837. return;
  838. }
  839. else {
  840. const existingHeartbeatsObject = await this.read();
  841. return writeHeartbeatsToIndexedDB(this.app, {
  842. lastSentHeartbeatDate: (_a = heartbeatsObject.lastSentHeartbeatDate) !== null && _a !== void 0 ? _a : existingHeartbeatsObject.lastSentHeartbeatDate,
  843. heartbeats: [
  844. ...existingHeartbeatsObject.heartbeats,
  845. ...heartbeatsObject.heartbeats
  846. ]
  847. });
  848. }
  849. }
  850. }
  851. /**
  852. * Calculate bytes of a HeartbeatsByUserAgent array after being wrapped
  853. * in a platform logging header JSON object, stringified, and converted
  854. * to base 64.
  855. */
  856. function countBytes(heartbeatsCache) {
  857. // base64 has a restricted set of characters, all of which should be 1 byte.
  858. return base64urlEncodeWithoutPadding(
  859. // heartbeatsCache wrapper properties
  860. JSON.stringify({ version: 2, heartbeats: heartbeatsCache })).length;
  861. }
  862. /**
  863. * @license
  864. * Copyright 2019 Google LLC
  865. *
  866. * Licensed under the Apache License, Version 2.0 (the "License");
  867. * you may not use this file except in compliance with the License.
  868. * You may obtain a copy of the License at
  869. *
  870. * http://www.apache.org/licenses/LICENSE-2.0
  871. *
  872. * Unless required by applicable law or agreed to in writing, software
  873. * distributed under the License is distributed on an "AS IS" BASIS,
  874. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  875. * See the License for the specific language governing permissions and
  876. * limitations under the License.
  877. */
  878. function registerCoreComponents(variant) {
  879. _registerComponent(new Component('platform-logger', container => new PlatformLoggerServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */));
  880. _registerComponent(new Component('heartbeat', container => new HeartbeatServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */));
  881. // Register `app` package.
  882. registerVersion(name$o, version$1, variant);
  883. // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
  884. registerVersion(name$o, version$1, 'esm2017');
  885. // Register platform SDK identifier (no version).
  886. registerVersion('fire-js', '');
  887. }
  888. /**
  889. * Firebase App
  890. *
  891. * @remarks This package coordinates the communication between the different Firebase components
  892. * @packageDocumentation
  893. */
  894. registerCoreComponents('');
  895. export { SDK_VERSION, DEFAULT_ENTRY_NAME as _DEFAULT_ENTRY_NAME, _addComponent, _addOrOverwriteComponent, _apps, _clearComponents, _components, _getProvider, _registerComponent, _removeServiceInstance, deleteApp, getApp, getApps, initializeApp, onLog, registerVersion, setLogLevel };
  896. //# sourceMappingURL=index.esm2017.js.map