Skip to content

Commit c28ac52

Browse files
authored
Static Loading of Service Namespaces (#106)
* Getting rid of the monkeypatching code * Implemented static on-demand loading for all services except RTDB * Using Object.assign() instead of _.assign()
1 parent 4cb5104 commit c28ac52

17 files changed

+437
-315
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@
5656
"@types/node": "^8.0.32",
5757
"faye-websocket": "0.9.3",
5858
"jsonwebtoken": "7.1.9",
59-
"node-forge": "0.7.1",
60-
"lodash": "^4.6.1"
59+
"node-forge": "0.7.1"
6160
},
6261
"devDependencies": {
6362
"@types/chai": "^3.4.34",
@@ -84,6 +83,7 @@
8483
"gulp-replace": "^0.5.4",
8584
"gulp-tslint": "^6.0.2",
8685
"gulp-typescript": "^3.1.2",
86+
"lodash": "^4.6.1",
8787
"merge2": "^1.0.2",
8888
"mocha": "^3.5.0",
8989
"nock": "^8.0.0",

src/auth/auth.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import * as validator from '../utils/validator';
2828
/**
2929
* Internals of an Auth instance.
3030
*/
31-
export class AuthInternals implements FirebaseServiceInternalsInterface {
31+
class AuthInternals implements FirebaseServiceInternalsInterface {
3232
/**
3333
* Deletes the service and its associated resources.
3434
*
@@ -51,7 +51,7 @@ export interface ListUsersResult {
5151
/**
5252
* Auth service bound to the provided app.
5353
*/
54-
class Auth implements FirebaseServiceInterface {
54+
export class Auth implements FirebaseServiceInterface {
5555
public INTERNAL: AuthInternals = new AuthInternals();
5656

5757
private app_: FirebaseApp;
@@ -286,8 +286,3 @@ class Auth implements FirebaseServiceInterface {
286286
});
287287
};
288288
};
289-
290-
291-
export {
292-
Auth,
293-
}

src/auth/register-auth.ts

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/firebase-app.ts

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import {FirebaseServiceInterface} from './firebase-service';
2222
import {FirebaseNamespaceInternals} from './firebase-namespace';
2323
import {AppErrorCodes, FirebaseAppError} from './utils/error';
2424
import {Firestore} from '@google-cloud/firestore';
25+
import {FirestoreService} from './firestore/firestore';
2526

27+
import {Auth} from './auth/auth';
28+
import {Messaging} from './messaging/messaging';
29+
import {Storage} from './storage/storage';
2630

2731
/**
2832
* Type representing a callback which is called every time an app lifecycle event occurs.
@@ -276,16 +280,14 @@ export class FirebaseApp {
276280
}
277281

278282
/**
279-
* Firebase services available off of a FirebaseApp instance. These are monkey-patched via
280-
* registerService(), but we need to include a dummy implementation to get TypeScript to
281-
* compile it without errors.
283+
* Returns the Auth service instance associated with this app.
284+
*
285+
* @return {Auth} The Auth service instance of this app.
282286
*/
283-
/* istanbul ignore next */
284-
public auth(): FirebaseServiceInterface {
285-
throw new FirebaseAppError(
286-
AppErrorCodes.INTERNAL_ERROR,
287-
'INTERNAL ASSERT FAILED: Firebase auth() service has not been registered.',
288-
);
287+
public auth(): Auth {
288+
return this.ensureService_('auth', () => {
289+
return new Auth(this);
290+
});
289291
}
290292

291293
/* istanbul ignore next */
@@ -296,28 +298,33 @@ export class FirebaseApp {
296298
);
297299
}
298300

299-
/* istanbul ignore next */
300-
public messaging(): FirebaseServiceInterface {
301-
throw new FirebaseAppError(
302-
AppErrorCodes.INTERNAL_ERROR,
303-
'INTERNAL ASSERT FAILED: Firebase messaging() service has not been registered.',
304-
);
301+
/**
302+
* Returns the Messaging service instance associated with this app.
303+
*
304+
* @return {Messaging} The Messaging service instance of this app.
305+
*/
306+
public messaging(): Messaging {
307+
return this.ensureService_('messaging', () => {
308+
return new Messaging(this);
309+
});
305310
}
306311

307-
/* istanbul ignore next */
308-
public storage(): FirebaseServiceInterface {
309-
throw new FirebaseAppError(
310-
AppErrorCodes.INTERNAL_ERROR,
311-
'INTERNAL ASSERT FAILED: Firebase storage() service has not been registered.',
312-
);
312+
/**
313+
* Returns the Storage service instance associated with this app.
314+
*
315+
* @return {Storage} The Storage service instance of this app.
316+
*/
317+
public storage(): Storage {
318+
return this.ensureService_('storage', () => {
319+
return new Storage(this);
320+
});
313321
}
314322

315-
/* istanbul ignore next */
316323
public firestore(): Firestore {
317-
throw new FirebaseAppError(
318-
AppErrorCodes.INTERNAL_ERROR,
319-
'INTERNAL ASSERT FAILED: Firebase firestore() service has not been registered.',
320-
);
324+
let service: FirestoreService = this.ensureService_('firestore', () => {
325+
return new FirestoreService(this);
326+
});
327+
return service.client;
321328
}
322329

323330
/**
@@ -359,9 +366,22 @@ export class FirebaseApp {
359366
});
360367
}
361368

369+
private ensureService_<T extends FirebaseServiceInterface>(serviceName: string, initializer: () => T): T {
370+
this.checkDestroyed_();
371+
372+
let service: T;
373+
if (serviceName in this.services_) {
374+
service = this.services_[serviceName] as T;
375+
} else {
376+
service = initializer();
377+
this.services_[serviceName] = service;
378+
}
379+
return service;
380+
}
381+
362382
/**
363383
* Returns the service instance associated with this FirebaseApp instance (creating it on demand
364-
* if needed).
384+
* if needed). This is used for looking up monkeypatched service instances.
365385
*
366386
* @param {string} serviceName The name of the service instance to return.
367387
* @return {FirebaseServiceInterface} The service instance with the provided name.

src/firebase-namespace.ts

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,20 @@ import {
2626
} from './auth/credential';
2727
import {Firestore} from '@google-cloud/firestore';
2828

29+
import {Auth} from './auth/auth';
30+
import {Messaging} from './messaging/messaging';
31+
import {Storage} from './storage/storage';
32+
2933
const DEFAULT_APP_NAME = '[DEFAULT]';
3034

3135
let globalAppDefaultCred: ApplicationDefaultCredential;
3236
let globalCertCreds: { [key: string]: CertCredential } = {};
3337
let globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {};
3438

3539

36-
export interface FirebaseServiceNamespace <T extends FirebaseServiceInterface> {
40+
export interface FirebaseServiceNamespace <T> {
3741
(app?: FirebaseApp): T;
42+
[key: string]: any;
3843
}
3944

4045

@@ -273,16 +278,15 @@ export class FirebaseNamespace {
273278
}
274279

275280
/**
276-
* Firebase services available off of a FirebaseNamespace instance. These are monkey-patched via
277-
* registerService(), but we need to include a dummy implementation to get TypeScript to
278-
* compile it without errors.
281+
* Gets the `Auth` service namespace. The returned namespace can be used to get the
282+
* `Auth` service for the default app or an explicitly specified app.
279283
*/
280-
/* istanbul ignore next */
281-
public auth(): FirebaseServiceInterface {
282-
throw new FirebaseAppError(
283-
AppErrorCodes.INTERNAL_ERROR,
284-
'INTERNAL ASSERT FAILED: Firebase auth() service has not been registered.',
285-
);
284+
get auth(): FirebaseServiceNamespace<Auth> {
285+
const ns: FirebaseNamespace = this;
286+
let fn: FirebaseServiceNamespace<Auth> = (app?: FirebaseApp) => {
287+
return ns.ensureApp(app).auth();
288+
};
289+
return Object.assign(fn, {Auth});
286290
}
287291

288292
/* istanbul ignore next */
@@ -293,28 +297,40 @@ export class FirebaseNamespace {
293297
);
294298
}
295299

296-
/* istanbul ignore next */
297-
public messaging(): FirebaseServiceInterface {
298-
throw new FirebaseAppError(
299-
AppErrorCodes.INTERNAL_ERROR,
300-
'INTERNAL ASSERT FAILED: Firebase messaging() service has not been registered.',
301-
);
300+
/**
301+
* Gets the `Messaging` service namespace. The returned namespace can be used to get the
302+
* `Messaging` service for the default app or an explicitly specified app.
303+
*/
304+
get messaging(): FirebaseServiceNamespace<Messaging> {
305+
const ns: FirebaseNamespace = this;
306+
let fn: FirebaseServiceNamespace<Messaging> = (app?: FirebaseApp) => {
307+
return ns.ensureApp(app).messaging();
308+
};
309+
return Object.assign(fn, {Messaging});
302310
}
303311

304-
/* istanbul ignore next */
305-
public storage(): FirebaseServiceInterface {
306-
throw new FirebaseAppError(
307-
AppErrorCodes.INTERNAL_ERROR,
308-
'INTERNAL ASSERT FAILED: Firebase storage() service has not been registered.',
309-
);
312+
/**
313+
* Gets the `Storage` service namespace. The returned namespace can be used to get the
314+
* `Storage` service for the default app or an explicitly specified app.
315+
*/
316+
get storage(): FirebaseServiceNamespace<Storage> {
317+
const ns: FirebaseNamespace = this;
318+
let fn: FirebaseServiceNamespace<Storage> = (app?: FirebaseApp) => {
319+
return ns.ensureApp(app).storage();
320+
};
321+
return Object.assign(fn, {Storage});
310322
}
311323

312-
/* istanbul ignore next */
313-
public firestore(): Firestore {
314-
throw new FirebaseAppError(
315-
AppErrorCodes.INTERNAL_ERROR,
316-
'INTERNAL ASSERT FAILED: Firebase firestore() service has not been registered.',
317-
);
324+
/**
325+
* Gets the `Firestore` service namespace. The returned namespace can be used to get the
326+
* `Firestore` service for the default app or an explicitly specified app.
327+
*/
328+
get firestore(): FirebaseServiceNamespace<Firestore> {
329+
const ns: FirebaseNamespace = this;
330+
let fn: FirebaseServiceNamespace<Firestore> = (app?: FirebaseApp) => {
331+
return ns.ensureApp(app).firestore();
332+
};
333+
return Object.assign(fn, require('@google-cloud/firestore'));
318334
}
319335

320336
/**
@@ -348,4 +364,11 @@ export class FirebaseNamespace {
348364
public get apps(): FirebaseApp[] {
349365
return this.INTERNAL.apps;
350366
}
367+
368+
private ensureApp(app?: FirebaseApp): FirebaseApp {
369+
if (typeof app === 'undefined') {
370+
app = this.app();
371+
}
372+
return app;
373+
}
351374
}

src/firestore/firestore.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,31 @@ class FirestoreInternals implements FirebaseServiceInternalsInterface {
3838
}
3939
}
4040

41+
export class FirestoreService implements FirebaseServiceInterface {
42+
public INTERNAL: FirestoreInternals = new FirestoreInternals();
43+
44+
private appInternal: FirebaseApp;
45+
private firestoreClient: Firestore;
46+
47+
constructor(app: FirebaseApp) {
48+
this.firestoreClient = initFirestore(app);
49+
this.appInternal = app;
50+
}
51+
52+
/**
53+
* Returns the app associated with this Storage instance.
54+
*
55+
* @return {FirebaseApp} The app associated with this Storage instance.
56+
*/
57+
get app(): FirebaseApp {
58+
return this.appInternal;
59+
}
60+
61+
get client(): Firestore {
62+
return this.firestoreClient;
63+
}
64+
}
65+
4166
function initFirestore(app: FirebaseApp): Firestore {
4267
if (!validator.isNonNullObject(app) || !('options' in app)) {
4368
throw new FirebaseFirestoreError({
@@ -88,18 +113,3 @@ function initFirestore(app: FirebaseApp): Firestore {
88113
}
89114
return new Firestore(options);
90115
}
91-
92-
/**
93-
* Creates a new Firestore service instance for the given FirebaseApp.
94-
*
95-
* @param {FirebaseApp} app The App for this Firestore service.
96-
* @return {FirebaseServiceInterface} A Firestore service instance.
97-
*/
98-
export function initFirestoreService(app: FirebaseApp): FirebaseServiceInterface {
99-
let firestore: any = initFirestore(app);
100-
101-
// Extend the Firestore client object so it implements FirebaseServiceInterface.
102-
utils.addReadonlyGetter(firestore, 'app', app);
103-
firestore.INTERNAL = new FirestoreInternals();
104-
return firestore;
105-
}

0 commit comments

Comments
 (0)