GSI - Employe Self Service Mobile
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.

409 lines
16 KiB

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