index.esm2017.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  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.13";
  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.23.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 initializeApp() first',
  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 && getDefaultAppConfig()) {
  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. const result = await db
  592. .transaction(STORE_NAME)
  593. .objectStore(STORE_NAME)
  594. .get(computeKey(app));
  595. return result;
  596. }
  597. catch (e) {
  598. if (e instanceof FirebaseError) {
  599. logger.warn(e.message);
  600. }
  601. else {
  602. const idbGetError = ERROR_FACTORY.create("idb-get" /* AppError.IDB_GET */, {
  603. originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
  604. });
  605. logger.warn(idbGetError.message);
  606. }
  607. }
  608. }
  609. async function writeHeartbeatsToIndexedDB(app, heartbeatObject) {
  610. try {
  611. const db = await getDbPromise();
  612. const tx = db.transaction(STORE_NAME, 'readwrite');
  613. const objectStore = tx.objectStore(STORE_NAME);
  614. await objectStore.put(heartbeatObject, computeKey(app));
  615. await tx.done;
  616. }
  617. catch (e) {
  618. if (e instanceof FirebaseError) {
  619. logger.warn(e.message);
  620. }
  621. else {
  622. const idbGetError = ERROR_FACTORY.create("idb-set" /* AppError.IDB_WRITE */, {
  623. originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
  624. });
  625. logger.warn(idbGetError.message);
  626. }
  627. }
  628. }
  629. function computeKey(app) {
  630. return `${app.name}!${app.options.appId}`;
  631. }
  632. /**
  633. * @license
  634. * Copyright 2021 Google LLC
  635. *
  636. * Licensed under the Apache License, Version 2.0 (the "License");
  637. * you may not use this file except in compliance with the License.
  638. * You may obtain a copy of the License at
  639. *
  640. * http://www.apache.org/licenses/LICENSE-2.0
  641. *
  642. * Unless required by applicable law or agreed to in writing, software
  643. * distributed under the License is distributed on an "AS IS" BASIS,
  644. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  645. * See the License for the specific language governing permissions and
  646. * limitations under the License.
  647. */
  648. const MAX_HEADER_BYTES = 1024;
  649. // 30 days
  650. const STORED_HEARTBEAT_RETENTION_MAX_MILLIS = 30 * 24 * 60 * 60 * 1000;
  651. class HeartbeatServiceImpl {
  652. constructor(container) {
  653. this.container = container;
  654. /**
  655. * In-memory cache for heartbeats, used by getHeartbeatsHeader() to generate
  656. * the header string.
  657. * Stores one record per date. This will be consolidated into the standard
  658. * format of one record per user agent string before being sent as a header.
  659. * Populated from indexedDB when the controller is instantiated and should
  660. * be kept in sync with indexedDB.
  661. * Leave public for easier testing.
  662. */
  663. this._heartbeatsCache = null;
  664. const app = this.container.getProvider('app').getImmediate();
  665. this._storage = new HeartbeatStorageImpl(app);
  666. this._heartbeatsCachePromise = this._storage.read().then(result => {
  667. this._heartbeatsCache = result;
  668. return result;
  669. });
  670. }
  671. /**
  672. * Called to report a heartbeat. The function will generate
  673. * a HeartbeatsByUserAgent object, update heartbeatsCache, and persist it
  674. * to IndexedDB.
  675. * Note that we only store one heartbeat per day. So if a heartbeat for today is
  676. * already logged, subsequent calls to this function in the same day will be ignored.
  677. */
  678. async triggerHeartbeat() {
  679. const platformLogger = this.container
  680. .getProvider('platform-logger')
  681. .getImmediate();
  682. // This is the "Firebase user agent" string from the platform logger
  683. // service, not the browser user agent.
  684. const agent = platformLogger.getPlatformInfoString();
  685. const date = getUTCDateString();
  686. if (this._heartbeatsCache === null) {
  687. this._heartbeatsCache = await this._heartbeatsCachePromise;
  688. }
  689. // Do not store a heartbeat if one is already stored for this day
  690. // or if a header has already been sent today.
  691. if (this._heartbeatsCache.lastSentHeartbeatDate === date ||
  692. this._heartbeatsCache.heartbeats.some(singleDateHeartbeat => singleDateHeartbeat.date === date)) {
  693. return;
  694. }
  695. else {
  696. // There is no entry for this date. Create one.
  697. this._heartbeatsCache.heartbeats.push({ date, agent });
  698. }
  699. // Remove entries older than 30 days.
  700. this._heartbeatsCache.heartbeats = this._heartbeatsCache.heartbeats.filter(singleDateHeartbeat => {
  701. const hbTimestamp = new Date(singleDateHeartbeat.date).valueOf();
  702. const now = Date.now();
  703. return now - hbTimestamp <= STORED_HEARTBEAT_RETENTION_MAX_MILLIS;
  704. });
  705. return this._storage.overwrite(this._heartbeatsCache);
  706. }
  707. /**
  708. * Returns a base64 encoded string which can be attached to the heartbeat-specific header directly.
  709. * It also clears all heartbeats from memory as well as in IndexedDB.
  710. *
  711. * NOTE: Consuming product SDKs should not send the header if this method
  712. * returns an empty string.
  713. */
  714. async getHeartbeatsHeader() {
  715. if (this._heartbeatsCache === null) {
  716. await this._heartbeatsCachePromise;
  717. }
  718. // If it's still null or the array is empty, there is no data to send.
  719. if (this._heartbeatsCache === null ||
  720. this._heartbeatsCache.heartbeats.length === 0) {
  721. return '';
  722. }
  723. const date = getUTCDateString();
  724. // Extract as many heartbeats from the cache as will fit under the size limit.
  725. const { heartbeatsToSend, unsentEntries } = extractHeartbeatsForHeader(this._heartbeatsCache.heartbeats);
  726. const headerString = base64urlEncodeWithoutPadding(JSON.stringify({ version: 2, heartbeats: heartbeatsToSend }));
  727. // Store last sent date to prevent another being logged/sent for the same day.
  728. this._heartbeatsCache.lastSentHeartbeatDate = date;
  729. if (unsentEntries.length > 0) {
  730. // Store any unsent entries if they exist.
  731. this._heartbeatsCache.heartbeats = unsentEntries;
  732. // This seems more likely than emptying the array (below) to lead to some odd state
  733. // since the cache isn't empty and this will be called again on the next request,
  734. // and is probably safest if we await it.
  735. await this._storage.overwrite(this._heartbeatsCache);
  736. }
  737. else {
  738. this._heartbeatsCache.heartbeats = [];
  739. // Do not wait for this, to reduce latency.
  740. void this._storage.overwrite(this._heartbeatsCache);
  741. }
  742. return headerString;
  743. }
  744. }
  745. function getUTCDateString() {
  746. const today = new Date();
  747. // Returns date format 'YYYY-MM-DD'
  748. return today.toISOString().substring(0, 10);
  749. }
  750. function extractHeartbeatsForHeader(heartbeatsCache, maxSize = MAX_HEADER_BYTES) {
  751. // Heartbeats grouped by user agent in the standard format to be sent in
  752. // the header.
  753. const heartbeatsToSend = [];
  754. // Single date format heartbeats that are not sent.
  755. let unsentEntries = heartbeatsCache.slice();
  756. for (const singleDateHeartbeat of heartbeatsCache) {
  757. // Look for an existing entry with the same user agent.
  758. const heartbeatEntry = heartbeatsToSend.find(hb => hb.agent === singleDateHeartbeat.agent);
  759. if (!heartbeatEntry) {
  760. // If no entry for this user agent exists, create one.
  761. heartbeatsToSend.push({
  762. agent: singleDateHeartbeat.agent,
  763. dates: [singleDateHeartbeat.date]
  764. });
  765. if (countBytes(heartbeatsToSend) > maxSize) {
  766. // If the header would exceed max size, remove the added heartbeat
  767. // entry and stop adding to the header.
  768. heartbeatsToSend.pop();
  769. break;
  770. }
  771. }
  772. else {
  773. heartbeatEntry.dates.push(singleDateHeartbeat.date);
  774. // If the header would exceed max size, remove the added date
  775. // and stop adding to the header.
  776. if (countBytes(heartbeatsToSend) > maxSize) {
  777. heartbeatEntry.dates.pop();
  778. break;
  779. }
  780. }
  781. // Pop unsent entry from queue. (Skipped if adding the entry exceeded
  782. // quota and the loop breaks early.)
  783. unsentEntries = unsentEntries.slice(1);
  784. }
  785. return {
  786. heartbeatsToSend,
  787. unsentEntries
  788. };
  789. }
  790. class HeartbeatStorageImpl {
  791. constructor(app) {
  792. this.app = app;
  793. this._canUseIndexedDBPromise = this.runIndexedDBEnvironmentCheck();
  794. }
  795. async runIndexedDBEnvironmentCheck() {
  796. if (!isIndexedDBAvailable()) {
  797. return false;
  798. }
  799. else {
  800. return validateIndexedDBOpenable()
  801. .then(() => true)
  802. .catch(() => false);
  803. }
  804. }
  805. /**
  806. * Read all heartbeats.
  807. */
  808. async read() {
  809. const canUseIndexedDB = await this._canUseIndexedDBPromise;
  810. if (!canUseIndexedDB) {
  811. return { heartbeats: [] };
  812. }
  813. else {
  814. const idbHeartbeatObject = await readHeartbeatsFromIndexedDB(this.app);
  815. return idbHeartbeatObject || { heartbeats: [] };
  816. }
  817. }
  818. // overwrite the storage with the provided heartbeats
  819. async overwrite(heartbeatsObject) {
  820. var _a;
  821. const canUseIndexedDB = await this._canUseIndexedDBPromise;
  822. if (!canUseIndexedDB) {
  823. return;
  824. }
  825. else {
  826. const existingHeartbeatsObject = await this.read();
  827. return writeHeartbeatsToIndexedDB(this.app, {
  828. lastSentHeartbeatDate: (_a = heartbeatsObject.lastSentHeartbeatDate) !== null && _a !== void 0 ? _a : existingHeartbeatsObject.lastSentHeartbeatDate,
  829. heartbeats: heartbeatsObject.heartbeats
  830. });
  831. }
  832. }
  833. // add heartbeats
  834. async add(heartbeatsObject) {
  835. var _a;
  836. const canUseIndexedDB = await this._canUseIndexedDBPromise;
  837. if (!canUseIndexedDB) {
  838. return;
  839. }
  840. else {
  841. const existingHeartbeatsObject = await this.read();
  842. return writeHeartbeatsToIndexedDB(this.app, {
  843. lastSentHeartbeatDate: (_a = heartbeatsObject.lastSentHeartbeatDate) !== null && _a !== void 0 ? _a : existingHeartbeatsObject.lastSentHeartbeatDate,
  844. heartbeats: [
  845. ...existingHeartbeatsObject.heartbeats,
  846. ...heartbeatsObject.heartbeats
  847. ]
  848. });
  849. }
  850. }
  851. }
  852. /**
  853. * Calculate bytes of a HeartbeatsByUserAgent array after being wrapped
  854. * in a platform logging header JSON object, stringified, and converted
  855. * to base 64.
  856. */
  857. function countBytes(heartbeatsCache) {
  858. // base64 has a restricted set of characters, all of which should be 1 byte.
  859. return base64urlEncodeWithoutPadding(
  860. // heartbeatsCache wrapper properties
  861. JSON.stringify({ version: 2, heartbeats: heartbeatsCache })).length;
  862. }
  863. /**
  864. * @license
  865. * Copyright 2019 Google LLC
  866. *
  867. * Licensed under the Apache License, Version 2.0 (the "License");
  868. * you may not use this file except in compliance with the License.
  869. * You may obtain a copy of the License at
  870. *
  871. * http://www.apache.org/licenses/LICENSE-2.0
  872. *
  873. * Unless required by applicable law or agreed to in writing, software
  874. * distributed under the License is distributed on an "AS IS" BASIS,
  875. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  876. * See the License for the specific language governing permissions and
  877. * limitations under the License.
  878. */
  879. function registerCoreComponents(variant) {
  880. _registerComponent(new Component('platform-logger', container => new PlatformLoggerServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */));
  881. _registerComponent(new Component('heartbeat', container => new HeartbeatServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */));
  882. // Register `app` package.
  883. registerVersion(name$o, version$1, variant);
  884. // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
  885. registerVersion(name$o, version$1, 'esm2017');
  886. // Register platform SDK identifier (no version).
  887. registerVersion('fire-js', '');
  888. }
  889. /**
  890. * Firebase App
  891. *
  892. * @remarks This package coordinates the communication between the different Firebase components
  893. * @packageDocumentation
  894. */
  895. registerCoreComponents('');
  896. 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 };
  897. //# sourceMappingURL=index.esm2017.js.map