@@ -221,6 +221,10 @@ let LocalPromise = local.Promise as typeof Promise;
221
221
222
222
const DEFAULT_ENTRY_NAME = '[DEFAULT]' ;
223
223
224
+ // An array to capture listeners before the true auth functions
225
+ // exist
226
+ let tokenListeners = [ ] ;
227
+
224
228
/**
225
229
* Global context object for a collection of services using
226
230
* a shared authentication state.
@@ -229,27 +233,19 @@ class FirebaseAppImpl implements FirebaseApp {
229
233
private options_ : FirebaseOptions ;
230
234
private name_ : string ;
231
235
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 ;
235
243
236
244
constructor ( options : FirebaseOptions ,
237
245
name : string ,
238
246
private firebase_ : FirebaseNamespace ) {
239
247
this . name_ = name ;
240
248
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
- } ) ;
253
249
}
254
250
255
251
get name ( ) : string {
@@ -286,34 +282,57 @@ class FirebaseAppImpl implements FirebaseApp {
286
282
}
287
283
288
284
/**
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
291
297
*/
292
- private getService ( name : string , instanceString ?: string ) : FirebaseService
293
- | null {
298
+ _getService ( name : string , instanceIdentifier : string = DEFAULT_ENTRY_NAME ) : FirebaseService {
294
299
this . checkDestroyed_ ( ) ;
295
300
296
- if ( typeof this . services_ [ name ] === 'undefined' ) {
301
+ if ( ! this . services_ [ name ] ) {
297
302
this . services_ [ name ] = { } ;
298
303
}
299
304
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 ;
308
308
}
309
+
310
+ return this . services_ [ name ] [ instanceIdentifier ] ;
309
311
}
310
312
311
313
/**
312
314
* Callback function used to extend an App instance at the time
313
315
* of service instance creation.
314
316
*/
315
317
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
+ }
317
336
}
318
337
319
338
/**
@@ -327,6 +346,19 @@ class FirebaseAppImpl implements FirebaseApp {
327
346
}
328
347
} ;
329
348
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
+
330
362
// Prevent dead-code elimination of these methods w/o invalid property
331
363
// copying.
332
364
FirebaseAppImpl . prototype . name &&
@@ -425,27 +457,12 @@ export function createFirebaseNamespace(): FirebaseNamespace {
425
457
if ( apps_ [ name ! ] !== undefined ) {
426
458
error ( 'duplicate-app' , { 'name' : name } ) ;
427
459
}
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
+
430
463
apps_ [ name ! ] = app ;
431
464
callAppHooks ( app , 'create' ) ;
432
465
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
- }
449
466
return app ;
450
467
}
451
468
@@ -471,39 +488,32 @@ export function createFirebaseNamespace(): FirebaseNamespace {
471
488
appHook ?: AppHook ,
472
489
allowMultipleInstances ?: boolean ) :
473
490
FirebaseServiceNamespace < FirebaseService > {
491
+ // Cannot re-register a service that already exists
474
492
if ( factories [ name ] ) {
475
493
error ( 'duplicate-service' , { 'name' : name } ) ;
476
494
}
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
491
500
if ( appHook ) {
492
501
appHooks [ name ] = appHook ;
493
- }
494
502
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
+ }
496
508
497
509
// 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 ( ) ) => {
502
511
if ( typeof ( appArg as any ) [ name ] !== 'function' ) {
503
512
// Invalid argument.
504
513
// This happens in the following case: firebase.storage('gs:/')
505
514
error ( 'invalid-app-argument' , { 'name' : name } ) ;
506
515
}
516
+
507
517
// Forward service instance lookup to the FirebaseApp.
508
518
return ( appArg as any ) [ name ] ( ) ;
509
519
} ;
@@ -516,6 +526,12 @@ export function createFirebaseNamespace(): FirebaseNamespace {
516
526
// Monkey-patch the serviceNamespace onto the firebase namespace
517
527
( namespace as any ) [ name ] = serviceNamespace ;
518
528
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
+
519
535
return serviceNamespace ;
520
536
}
521
537
0 commit comments