index.cjs.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var util = require('@firebase/util');
  4. /**
  5. * Component for service name T, e.g. `auth`, `auth-internal`
  6. */
  7. class Component {
  8. /**
  9. *
  10. * @param name The public service name, e.g. app, auth, firestore, database
  11. * @param instanceFactory Service factory responsible for creating the public interface
  12. * @param type whether the service provided by the component is public or private
  13. */
  14. constructor(name, instanceFactory, type) {
  15. this.name = name;
  16. this.instanceFactory = instanceFactory;
  17. this.type = type;
  18. this.multipleInstances = false;
  19. /**
  20. * Properties to be added to the service namespace
  21. */
  22. this.serviceProps = {};
  23. this.instantiationMode = "LAZY" /* InstantiationMode.LAZY */;
  24. this.onInstanceCreated = null;
  25. }
  26. setInstantiationMode(mode) {
  27. this.instantiationMode = mode;
  28. return this;
  29. }
  30. setMultipleInstances(multipleInstances) {
  31. this.multipleInstances = multipleInstances;
  32. return this;
  33. }
  34. setServiceProps(props) {
  35. this.serviceProps = props;
  36. return this;
  37. }
  38. setInstanceCreatedCallback(callback) {
  39. this.onInstanceCreated = callback;
  40. return this;
  41. }
  42. }
  43. /**
  44. * @license
  45. * Copyright 2019 Google LLC
  46. *
  47. * Licensed under the Apache License, Version 2.0 (the "License");
  48. * you may not use this file except in compliance with the License.
  49. * You may obtain a copy of the License at
  50. *
  51. * http://www.apache.org/licenses/LICENSE-2.0
  52. *
  53. * Unless required by applicable law or agreed to in writing, software
  54. * distributed under the License is distributed on an "AS IS" BASIS,
  55. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  56. * See the License for the specific language governing permissions and
  57. * limitations under the License.
  58. */
  59. const DEFAULT_ENTRY_NAME = '[DEFAULT]';
  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. /**
  77. * Provider for instance for service name T, e.g. 'auth', 'auth-internal'
  78. * NameServiceMapping[T] is an alias for the type of the instance
  79. */
  80. class Provider {
  81. constructor(name, container) {
  82. this.name = name;
  83. this.container = container;
  84. this.component = null;
  85. this.instances = new Map();
  86. this.instancesDeferred = new Map();
  87. this.instancesOptions = new Map();
  88. this.onInitCallbacks = new Map();
  89. }
  90. /**
  91. * @param identifier A provider can provide multiple instances of a service
  92. * if this.component.multipleInstances is true.
  93. */
  94. get(identifier) {
  95. // if multipleInstances is not supported, use the default name
  96. const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
  97. if (!this.instancesDeferred.has(normalizedIdentifier)) {
  98. const deferred = new util.Deferred();
  99. this.instancesDeferred.set(normalizedIdentifier, deferred);
  100. if (this.isInitialized(normalizedIdentifier) ||
  101. this.shouldAutoInitialize()) {
  102. // initialize the service if it can be auto-initialized
  103. try {
  104. const instance = this.getOrInitializeService({
  105. instanceIdentifier: normalizedIdentifier
  106. });
  107. if (instance) {
  108. deferred.resolve(instance);
  109. }
  110. }
  111. catch (e) {
  112. // when the instance factory throws an exception during get(), it should not cause
  113. // a fatal error. We just return the unresolved promise in this case.
  114. }
  115. }
  116. }
  117. return this.instancesDeferred.get(normalizedIdentifier).promise;
  118. }
  119. getImmediate(options) {
  120. var _a;
  121. // if multipleInstances is not supported, use the default name
  122. const normalizedIdentifier = this.normalizeInstanceIdentifier(options === null || options === void 0 ? void 0 : options.identifier);
  123. const optional = (_a = options === null || options === void 0 ? void 0 : options.optional) !== null && _a !== void 0 ? _a : false;
  124. if (this.isInitialized(normalizedIdentifier) ||
  125. this.shouldAutoInitialize()) {
  126. try {
  127. return this.getOrInitializeService({
  128. instanceIdentifier: normalizedIdentifier
  129. });
  130. }
  131. catch (e) {
  132. if (optional) {
  133. return null;
  134. }
  135. else {
  136. throw e;
  137. }
  138. }
  139. }
  140. else {
  141. // In case a component is not initialized and should/cannot be auto-initialized at the moment, return null if the optional flag is set, or throw
  142. if (optional) {
  143. return null;
  144. }
  145. else {
  146. throw Error(`Service ${this.name} is not available`);
  147. }
  148. }
  149. }
  150. getComponent() {
  151. return this.component;
  152. }
  153. setComponent(component) {
  154. if (component.name !== this.name) {
  155. throw Error(`Mismatching Component ${component.name} for Provider ${this.name}.`);
  156. }
  157. if (this.component) {
  158. throw Error(`Component for ${this.name} has already been provided`);
  159. }
  160. this.component = component;
  161. // return early without attempting to initialize the component if the component requires explicit initialization (calling `Provider.initialize()`)
  162. if (!this.shouldAutoInitialize()) {
  163. return;
  164. }
  165. // if the service is eager, initialize the default instance
  166. if (isComponentEager(component)) {
  167. try {
  168. this.getOrInitializeService({ instanceIdentifier: DEFAULT_ENTRY_NAME });
  169. }
  170. catch (e) {
  171. // when the instance factory for an eager Component throws an exception during the eager
  172. // initialization, it should not cause a fatal error.
  173. // TODO: Investigate if we need to make it configurable, because some component may want to cause
  174. // a fatal error in this case?
  175. }
  176. }
  177. // Create service instances for the pending promises and resolve them
  178. // NOTE: if this.multipleInstances is false, only the default instance will be created
  179. // and all promises with resolve with it regardless of the identifier.
  180. for (const [instanceIdentifier, instanceDeferred] of this.instancesDeferred.entries()) {
  181. const normalizedIdentifier = this.normalizeInstanceIdentifier(instanceIdentifier);
  182. try {
  183. // `getOrInitializeService()` should always return a valid instance since a component is guaranteed. use ! to make typescript happy.
  184. const instance = this.getOrInitializeService({
  185. instanceIdentifier: normalizedIdentifier
  186. });
  187. instanceDeferred.resolve(instance);
  188. }
  189. catch (e) {
  190. // when the instance factory throws an exception, it should not cause
  191. // a fatal error. We just leave the promise unresolved.
  192. }
  193. }
  194. }
  195. clearInstance(identifier = DEFAULT_ENTRY_NAME) {
  196. this.instancesDeferred.delete(identifier);
  197. this.instancesOptions.delete(identifier);
  198. this.instances.delete(identifier);
  199. }
  200. // app.delete() will call this method on every provider to delete the services
  201. // TODO: should we mark the provider as deleted?
  202. async delete() {
  203. const services = Array.from(this.instances.values());
  204. await Promise.all([
  205. ...services
  206. .filter(service => 'INTERNAL' in service) // legacy services
  207. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  208. .map(service => service.INTERNAL.delete()),
  209. ...services
  210. .filter(service => '_delete' in service) // modularized services
  211. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  212. .map(service => service._delete())
  213. ]);
  214. }
  215. isComponentSet() {
  216. return this.component != null;
  217. }
  218. isInitialized(identifier = DEFAULT_ENTRY_NAME) {
  219. return this.instances.has(identifier);
  220. }
  221. getOptions(identifier = DEFAULT_ENTRY_NAME) {
  222. return this.instancesOptions.get(identifier) || {};
  223. }
  224. initialize(opts = {}) {
  225. const { options = {} } = opts;
  226. const normalizedIdentifier = this.normalizeInstanceIdentifier(opts.instanceIdentifier);
  227. if (this.isInitialized(normalizedIdentifier)) {
  228. throw Error(`${this.name}(${normalizedIdentifier}) has already been initialized`);
  229. }
  230. if (!this.isComponentSet()) {
  231. throw Error(`Component ${this.name} has not been registered yet`);
  232. }
  233. const instance = this.getOrInitializeService({
  234. instanceIdentifier: normalizedIdentifier,
  235. options
  236. });
  237. // resolve any pending promise waiting for the service instance
  238. for (const [instanceIdentifier, instanceDeferred] of this.instancesDeferred.entries()) {
  239. const normalizedDeferredIdentifier = this.normalizeInstanceIdentifier(instanceIdentifier);
  240. if (normalizedIdentifier === normalizedDeferredIdentifier) {
  241. instanceDeferred.resolve(instance);
  242. }
  243. }
  244. return instance;
  245. }
  246. /**
  247. *
  248. * @param callback - a function that will be invoked after the provider has been initialized by calling provider.initialize().
  249. * The function is invoked SYNCHRONOUSLY, so it should not execute any longrunning tasks in order to not block the program.
  250. *
  251. * @param identifier An optional instance identifier
  252. * @returns a function to unregister the callback
  253. */
  254. onInit(callback, identifier) {
  255. var _a;
  256. const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
  257. const existingCallbacks = (_a = this.onInitCallbacks.get(normalizedIdentifier)) !== null && _a !== void 0 ? _a : new Set();
  258. existingCallbacks.add(callback);
  259. this.onInitCallbacks.set(normalizedIdentifier, existingCallbacks);
  260. const existingInstance = this.instances.get(normalizedIdentifier);
  261. if (existingInstance) {
  262. callback(existingInstance, normalizedIdentifier);
  263. }
  264. return () => {
  265. existingCallbacks.delete(callback);
  266. };
  267. }
  268. /**
  269. * Invoke onInit callbacks synchronously
  270. * @param instance the service instance`
  271. */
  272. invokeOnInitCallbacks(instance, identifier) {
  273. const callbacks = this.onInitCallbacks.get(identifier);
  274. if (!callbacks) {
  275. return;
  276. }
  277. for (const callback of callbacks) {
  278. try {
  279. callback(instance, identifier);
  280. }
  281. catch (_a) {
  282. // ignore errors in the onInit callback
  283. }
  284. }
  285. }
  286. getOrInitializeService({ instanceIdentifier, options = {} }) {
  287. let instance = this.instances.get(instanceIdentifier);
  288. if (!instance && this.component) {
  289. instance = this.component.instanceFactory(this.container, {
  290. instanceIdentifier: normalizeIdentifierForFactory(instanceIdentifier),
  291. options
  292. });
  293. this.instances.set(instanceIdentifier, instance);
  294. this.instancesOptions.set(instanceIdentifier, options);
  295. /**
  296. * Invoke onInit listeners.
  297. * Note this.component.onInstanceCreated is different, which is used by the component creator,
  298. * while onInit listeners are registered by consumers of the provider.
  299. */
  300. this.invokeOnInitCallbacks(instance, instanceIdentifier);
  301. /**
  302. * Order is important
  303. * onInstanceCreated() should be called after this.instances.set(instanceIdentifier, instance); which
  304. * makes `isInitialized()` return true.
  305. */
  306. if (this.component.onInstanceCreated) {
  307. try {
  308. this.component.onInstanceCreated(this.container, instanceIdentifier, instance);
  309. }
  310. catch (_a) {
  311. // ignore errors in the onInstanceCreatedCallback
  312. }
  313. }
  314. }
  315. return instance || null;
  316. }
  317. normalizeInstanceIdentifier(identifier = DEFAULT_ENTRY_NAME) {
  318. if (this.component) {
  319. return this.component.multipleInstances ? identifier : DEFAULT_ENTRY_NAME;
  320. }
  321. else {
  322. return identifier; // assume multiple instances are supported before the component is provided.
  323. }
  324. }
  325. shouldAutoInitialize() {
  326. return (!!this.component &&
  327. this.component.instantiationMode !== "EXPLICIT" /* InstantiationMode.EXPLICIT */);
  328. }
  329. }
  330. // undefined should be passed to the service factory for the default instance
  331. function normalizeIdentifierForFactory(identifier) {
  332. return identifier === DEFAULT_ENTRY_NAME ? undefined : identifier;
  333. }
  334. function isComponentEager(component) {
  335. return component.instantiationMode === "EAGER" /* InstantiationMode.EAGER */;
  336. }
  337. /**
  338. * @license
  339. * Copyright 2019 Google LLC
  340. *
  341. * Licensed under the Apache License, Version 2.0 (the "License");
  342. * you may not use this file except in compliance with the License.
  343. * You may obtain a copy of the License at
  344. *
  345. * http://www.apache.org/licenses/LICENSE-2.0
  346. *
  347. * Unless required by applicable law or agreed to in writing, software
  348. * distributed under the License is distributed on an "AS IS" BASIS,
  349. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  350. * See the License for the specific language governing permissions and
  351. * limitations under the License.
  352. */
  353. /**
  354. * ComponentContainer that provides Providers for service name T, e.g. `auth`, `auth-internal`
  355. */
  356. class ComponentContainer {
  357. constructor(name) {
  358. this.name = name;
  359. this.providers = new Map();
  360. }
  361. /**
  362. *
  363. * @param component Component being added
  364. * @param overwrite When a component with the same name has already been registered,
  365. * if overwrite is true: overwrite the existing component with the new component and create a new
  366. * provider with the new component. It can be useful in tests where you want to use different mocks
  367. * for different tests.
  368. * if overwrite is false: throw an exception
  369. */
  370. addComponent(component) {
  371. const provider = this.getProvider(component.name);
  372. if (provider.isComponentSet()) {
  373. throw new Error(`Component ${component.name} has already been registered with ${this.name}`);
  374. }
  375. provider.setComponent(component);
  376. }
  377. addOrOverwriteComponent(component) {
  378. const provider = this.getProvider(component.name);
  379. if (provider.isComponentSet()) {
  380. // delete the existing provider from the container, so we can register the new component
  381. this.providers.delete(component.name);
  382. }
  383. this.addComponent(component);
  384. }
  385. /**
  386. * getProvider provides a type safe interface where it can only be called with a field name
  387. * present in NameServiceMapping interface.
  388. *
  389. * Firebase SDKs providing services should extend NameServiceMapping interface to register
  390. * themselves.
  391. */
  392. getProvider(name) {
  393. if (this.providers.has(name)) {
  394. return this.providers.get(name);
  395. }
  396. // create a Provider for a service that hasn't registered with Firebase
  397. const provider = new Provider(name, this);
  398. this.providers.set(name, provider);
  399. return provider;
  400. }
  401. getProviders() {
  402. return Array.from(this.providers.values());
  403. }
  404. }
  405. exports.Component = Component;
  406. exports.ComponentContainer = ComponentContainer;
  407. exports.Provider = Provider;
  408. //# sourceMappingURL=index.cjs.js.map