@@ -221,6 +221,10 @@ let LocalPromise = local.Promise as typeof Promise;
221221
222222const DEFAULT_ENTRY_NAME = '[DEFAULT]' ;
223223
224+ // An array to capture listeners before the true auth functions
225+ // exist
226+ let tokenListeners = [ ] ;
227+
224228/**
225229 * Global context object for a collection of services using
226230 * a shared authentication state.
@@ -229,27 +233,19 @@ class FirebaseAppImpl implements FirebaseApp {
229233 private options_ : FirebaseOptions ;
230234 private name_ : string ;
231235 private isDeleted_ = false ;
232- private services_ : { [ name : string ] :
233- { [ instance : string ] : FirebaseService } } = { } ;
234- public INTERNAL : FirebaseAppInternals ;
236+ private services_ : {
237+ [ name : string ] : {
238+ [ serviceName : string ] : FirebaseService
239+ }
240+ } = { } ;
241+
242+ public INTERNAL ;
235243
236244 constructor ( options : FirebaseOptions ,
237245 name : string ,
238246 private firebase_ : FirebaseNamespace ) {
239247 this . name_ = name ;
240248 this . options_ = deepCopy < FirebaseOptions > ( options ) ;
241-
242- Object . keys ( firebase_ . INTERNAL . factories ) . forEach ( ( serviceName ) => {
243- // Ignore virtual services
244- let factoryName = firebase_ . INTERNAL . useAsService ( this , serviceName ) ;
245- if ( factoryName === null ) {
246- return ;
247- }
248-
249- // Defer calling createService until service is accessed.
250- let getService = this . getService . bind ( this , factoryName ) ;
251- patchProperty ( this , serviceName , getService ) ;
252- } ) ;
253249 }
254250
255251 get name ( ) : string {
@@ -286,34 +282,57 @@ class FirebaseAppImpl implements FirebaseApp {
286282 }
287283
288284 /**
289- * Return the service instance associated with this app (creating it
290- * on demand).
285+ * Return a service instance associated with this app (creating it
286+ * on demand), identified by the passed instanceIdentifier.
287+ *
288+ * NOTE: Currently storage is the only one that is leveraging this
289+ * functionality. They invoke it by calling:
290+ *
291+ * ```javascript
292+ * firebase.app().storage('STORAGE BUCKET ID')
293+ * ```
294+ *
295+ * The service name is passed to this already
296+ * @internal
291297 */
292- private getService ( name : string , instanceString ?: string ) : FirebaseService
293- | null {
298+ _getService ( name : string , instanceIdentifier : string = DEFAULT_ENTRY_NAME ) : FirebaseService {
294299 this . checkDestroyed_ ( ) ;
295300
296- if ( typeof this . services_ [ name ] === 'undefined' ) {
301+ if ( ! this . services_ [ name ] ) {
297302 this . services_ [ name ] = { } ;
298303 }
299304
300- let instanceSpecifier = instanceString || DEFAULT_ENTRY_NAME ;
301- if ( typeof this . services_ [ name ] ! [ instanceSpecifier ] === 'undefined' ) {
302- let firebaseService = this . firebase_ . INTERNAL . factories [ name ] (
303- this , this . extendApp . bind ( this ) , instanceString ) ;
304- this . services_ [ name ] ! [ instanceSpecifier ] = firebaseService ;
305- return firebaseService ;
306- } else {
307- return this . services_ [ name ] ! [ instanceSpecifier ] as FirebaseService | null ;
305+ if ( ! this . services_ [ name ] [ instanceIdentifier ] ) {
306+ let service = this . firebase_ . INTERNAL . factories [ name ] ( this , this . extendApp . bind ( this ) , instanceIdentifier ) ;
307+ this . services_ [ name ] [ instanceIdentifier ] = service ;
308308 }
309+
310+ return this . services_ [ name ] [ instanceIdentifier ] ;
309311 }
310312
311313 /**
312314 * Callback function used to extend an App instance at the time
313315 * of service instance creation.
314316 */
315317 private extendApp ( props : { [ name : string ] : any } ) : void {
316- deepExtend ( this , props ) ;
318+ // Copy the object onto the FirebaseAppImpl prototype
319+ deepExtend ( FirebaseAppImpl . prototype , props ) ;
320+
321+ /**
322+ * If the app has overwritten the addAuthTokenListener stub, forward
323+ * the active token listeners on to the true fxn.
324+ *
325+ * TODO: This function is required due to our current module
326+ * structure. Once we are able to rely strictly upon a single module
327+ * implementation, this code should be refactored and Auth should
328+ * provide these stubs and the upgrade logic
329+ */
330+ if ( props . INTERNAL && props . INTERNAL . addAuthTokenListener ) {
331+ tokenListeners . forEach ( listener => {
332+ this . INTERNAL . addAuthTokenListener ( listener ) ;
333+ } ) ;
334+ tokenListeners = [ ] ;
335+ }
317336 }
318337
319338 /**
@@ -327,6 +346,19 @@ class FirebaseAppImpl implements FirebaseApp {
327346 }
328347} ;
329348
349+ FirebaseAppImpl . prototype . INTERNAL = {
350+ 'getUid' : ( ) => null ,
351+ 'getToken' : ( ) => LocalPromise . resolve ( null ) ,
352+ 'addAuthTokenListener' : ( callback : ( token : string | null ) => void ) => {
353+ tokenListeners . push ( callback ) ;
354+ // Make sure callback is called, asynchronously, in the absence of the auth module
355+ setTimeout ( ( ) => callback ( null ) , 0 ) ;
356+ } ,
357+ 'removeAuthTokenListener' : ( callback ) => {
358+ tokenListeners = tokenListeners . filter ( listener => listener !== callback ) ;
359+ } ,
360+ }
361+
330362// Prevent dead-code elimination of these methods w/o invalid property
331363// copying.
332364FirebaseAppImpl . prototype . name &&
@@ -425,27 +457,12 @@ export function createFirebaseNamespace(): FirebaseNamespace {
425457 if ( apps_ [ name ! ] !== undefined ) {
426458 error ( 'duplicate-app' , { 'name' : name } ) ;
427459 }
428- let app = new FirebaseAppImpl ( options , name ! ,
429- ( ( namespace as any ) as FirebaseNamespace ) ) ;
460+
461+ let app = new FirebaseAppImpl ( options , name ! , namespace as FirebaseNamespace ) ;
462+
430463 apps_ [ name ! ] = app ;
431464 callAppHooks ( app , 'create' ) ;
432465
433- // Ensure that getUid, getToken, addAuthListener and removeAuthListener
434- // have a default implementation if no service has patched the App
435- // (i.e., Auth is not present).
436- if ( app . INTERNAL == undefined || app . INTERNAL . getToken == undefined ) {
437- deepExtend ( app , {
438- INTERNAL : {
439- 'getUid' : ( ) => null ,
440- 'getToken' : ( ) => LocalPromise . resolve ( null ) ,
441- 'addAuthTokenListener' : ( callback : ( token : string | null ) => void ) => {
442- // Make sure callback is called, asynchronously, in the absence of the auth module
443- setTimeout ( ( ) => callback ( null ) , 0 ) ;
444- } ,
445- 'removeAuthTokenListener' : ( ) => { /*_*/ } ,
446- }
447- } ) ;
448- }
449466 return app ;
450467 }
451468
@@ -471,39 +488,32 @@ export function createFirebaseNamespace(): FirebaseNamespace {
471488 appHook ?: AppHook ,
472489 allowMultipleInstances ?: boolean ) :
473490 FirebaseServiceNamespace < FirebaseService > {
491+ // Cannot re-register a service that already exists
474492 if ( factories [ name ] ) {
475493 error ( 'duplicate-service' , { 'name' : name } ) ;
476494 }
477- if ( ! ! allowMultipleInstances ) {
478- // Check if the service allows multiple instances per app
479- factories [ name ] = createService ;
480- } else {
481- // If not, always return the same instance when a service is instantiated
482- // with an instanceString different than the default.
483- factories [ name ] =
484- ( app : FirebaseApp , extendApp ?: ( props : { [ prop : string ] : any } ) => void ,
485- instanceString ?: string ) => {
486- // If a new instance is requested for a service that does not allow
487- // multiple instances, return the default instance
488- return createService ( app , extendApp , DEFAULT_ENTRY_NAME ) ;
489- } ;
490- }
495+
496+ // Capture the service factory for later service instantiation
497+ factories [ name ] = createService ;
498+
499+ // Capture the appHook, if passed
491500 if ( appHook ) {
492501 appHooks [ name ] = appHook ;
493- }
494502
495- let serviceNamespace : FirebaseServiceNamespace < FirebaseService > ;
503+ // Run the **new** app hook on all existing apps
504+ getApps ( ) . forEach ( app => {
505+ appHook ( 'create' , app ) ;
506+ } ) ;
507+ }
496508
497509 // The Service namespace is an accessor function ...
498- serviceNamespace = ( appArg ?: FirebaseApp ) => {
499- if ( appArg === undefined ) {
500- appArg = app ( ) ;
501- }
510+ const serviceNamespace = ( appArg : FirebaseApp = app ( ) ) => {
502511 if ( typeof ( appArg as any ) [ name ] !== 'function' ) {
503512 // Invalid argument.
504513 // This happens in the following case: firebase.storage('gs:/')
505514 error ( 'invalid-app-argument' , { 'name' : name } ) ;
506515 }
516+
507517 // Forward service instance lookup to the FirebaseApp.
508518 return ( appArg as any ) [ name ] ( ) ;
509519 } ;
@@ -516,6 +526,12 @@ export function createFirebaseNamespace(): FirebaseNamespace {
516526 // Monkey-patch the serviceNamespace onto the firebase namespace
517527 ( namespace as any ) [ name ] = serviceNamespace ;
518528
529+ // Patch the FirebaseAppImpl prototype
530+ FirebaseAppImpl . prototype [ name ] = function ( ...args ) {
531+ const serviceFxn = this . _getService . bind ( this , name ) ;
532+ return serviceFxn . apply ( this , allowMultipleInstances ? args : [ ] ) ;
533+ }
534+
519535 return serviceNamespace ;
520536 }
521537
0 commit comments