Skip to content

Commit 2f6da89

Browse files
authored
fix: Removing FirebaseServiceInterface and FirebaseServiceInternalsInterface (#1128)
* fix: Removing FirebaseService and FirebaseServiceInterface internal APIs * chore: Added unit tests for service caching behavior * fix: Using equal instead of deep.equal for reference tests
1 parent b60191e commit 2f6da89

File tree

19 files changed

+135
-505
lines changed

19 files changed

+135
-505
lines changed

src/auth/auth.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
AbstractAuthRequestHandler, AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator,
2626
} from './auth-api-request';
2727
import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error';
28-
import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service';
2928
import * as utils from '../utils/index';
3029
import * as validator from '../utils/validator';
3130
import { auth } from './index';
@@ -59,22 +58,6 @@ import BaseAuthInterface = auth.BaseAuth;
5958
import AuthInterface = auth.Auth;
6059
import TenantAwareAuthInterface = auth.TenantAwareAuth;
6160

62-
/**
63-
* Internals of an Auth instance.
64-
*/
65-
class AuthInternals implements FirebaseServiceInternalsInterface {
66-
/**
67-
* Deletes the service and its associated resources.
68-
*
69-
* @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted.
70-
*/
71-
public delete(): Promise<void> {
72-
// There are no resources to clean up
73-
return Promise.resolve(undefined);
74-
}
75-
}
76-
77-
7861
/**
7962
* Base Auth class. Mainly used for user management APIs.
8063
*/
@@ -820,10 +803,8 @@ export class TenantAwareAuth
820803
* Auth service bound to the provided app.
821804
* An Auth instance can have multiple tenants.
822805
*/
823-
export class Auth extends BaseAuth<AuthRequestHandler>
824-
implements FirebaseServiceInterface, AuthInterface {
806+
export class Auth extends BaseAuth<AuthRequestHandler> implements AuthInterface {
825807

826-
public INTERNAL: AuthInternals = new AuthInternals();
827808
private readonly tenantManager_: TenantManager;
828809
private readonly app_: FirebaseApp;
829810

src/database/database-internal.ts

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import * as path from 'path';
1919

2020
import { FirebaseApp } from '../firebase-app';
2121
import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error';
22-
import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service';
2322
import { Database as DatabaseImpl } from '@firebase/database';
2423
import { database } from './index';
2524

@@ -29,35 +28,14 @@ import { getSdkVersion } from '../utils/index';
2928

3029
import Database = database.Database;
3130

32-
/**
33-
* Internals of a Database instance.
34-
*/
35-
class DatabaseInternals implements FirebaseServiceInternalsInterface {
31+
export class DatabaseService {
32+
33+
private readonly appInternal: FirebaseApp;
3634

37-
public databases: {
35+
private databases: {
3836
[dbUrl: string]: Database;
3937
} = {};
4038

41-
/**
42-
* Deletes the service and its associated resources.
43-
*
44-
* @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted.
45-
*/
46-
public delete(): Promise<void> {
47-
for (const dbUrl of Object.keys(this.databases)) {
48-
const db: DatabaseImpl = ((this.databases[dbUrl] as any) as DatabaseImpl);
49-
db.INTERNAL.delete();
50-
}
51-
return Promise.resolve(undefined);
52-
}
53-
}
54-
55-
export class DatabaseService implements FirebaseServiceInterface {
56-
57-
public readonly INTERNAL: DatabaseInternals = new DatabaseInternals();
58-
59-
private readonly appInternal: FirebaseApp;
60-
6139
constructor(app: FirebaseApp) {
6240
if (!validator.isNonNullObject(app) || !('options' in app)) {
6341
throw new FirebaseDatabaseError({
@@ -68,6 +46,20 @@ export class DatabaseService implements FirebaseServiceInterface {
6846
this.appInternal = app;
6947
}
7048

49+
/**
50+
* @internal
51+
*/
52+
public delete(): Promise<void> {
53+
const promises = [];
54+
for (const dbUrl of Object.keys(this.databases)) {
55+
const db: DatabaseImpl = ((this.databases[dbUrl] as any) as DatabaseImpl);
56+
promises.push(db.INTERNAL.delete());
57+
}
58+
return Promise.all(promises).then(() => {
59+
this.databases = {};
60+
});
61+
}
62+
7163
/**
7264
* Returns the app associated with this DatabaseService instance.
7365
*
@@ -86,7 +78,7 @@ export class DatabaseService implements FirebaseServiceInterface {
8678
});
8779
}
8880

89-
let db: Database = this.INTERNAL.databases[dbUrl];
81+
let db: Database = this.databases[dbUrl];
9082
if (typeof db === 'undefined') {
9183
const rtdb = require('@firebase/database'); // eslint-disable-line @typescript-eslint/no-var-requires
9284
db = rtdb.initStandalone(this.appInternal, dbUrl, getSdkVersion()).instance;
@@ -102,7 +94,7 @@ export class DatabaseService implements FirebaseServiceInterface {
10294
return rulesClient.setRules(source);
10395
};
10496

105-
this.INTERNAL.databases[dbUrl] = db;
97+
this.databases[dbUrl] = db;
10698
}
10799
return db;
108100
}

src/firebase-app.ts

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import { AppOptions, app } from './firebase-namespace-api';
1919
import { credential, GoogleOAuthAccessToken } from './credential/index';
2020
import { getApplicationDefault } from './credential/credential-internal';
2121
import * as validator from './utils/validator';
22-
import { deepCopy, deepExtend } from './utils/deep-copy';
23-
import { FirebaseServiceInterface } from './firebase-service';
22+
import { deepCopy } from './utils/deep-copy';
2423
import { FirebaseNamespaceInternals } from './firebase-namespace';
2524
import { AppErrorCodes, FirebaseAppError } from './utils/error';
2625

@@ -238,7 +237,7 @@ export class FirebaseApp implements app.App {
238237

239238
private name_: string;
240239
private options_: AppOptions;
241-
private services_: {[name: string]: FirebaseServiceInterface} = {};
240+
private services_: {[name: string]: unknown} = {};
242241
private isDeleted_ = false;
243242

244243
constructor(options: AppOptions, name: string, private firebaseInternals_: FirebaseNamespaceInternals) {
@@ -268,11 +267,6 @@ export class FirebaseApp implements app.App {
268267
);
269268
}
270269

271-
Object.keys(firebaseInternals_.serviceFactories).forEach((serviceName) => {
272-
// Defer calling createService() until the service is accessed
273-
(this as {[key: string]: any})[serviceName] = this.getService_.bind(this, serviceName);
274-
});
275-
276270
this.INTERNAL = new FirebaseAppInternals(credential);
277271
}
278272

@@ -428,51 +422,24 @@ export class FirebaseApp implements app.App {
428422
this.INTERNAL.delete();
429423

430424
return Promise.all(Object.keys(this.services_).map((serviceName) => {
431-
return this.services_[serviceName].INTERNAL.delete();
425+
const service = this.services_[serviceName];
426+
if (isStateful(service)) {
427+
return service.delete();
428+
}
429+
return Promise.resolve();
432430
})).then(() => {
433431
this.services_ = {};
434432
this.isDeleted_ = true;
435433
});
436434
}
437435

438-
private ensureService_<T extends FirebaseServiceInterface>(serviceName: string, initializer: () => T): T {
439-
this.checkDestroyed_();
440-
441-
let service: T;
442-
if (serviceName in this.services_) {
443-
service = this.services_[serviceName] as T;
444-
} else {
445-
service = initializer();
446-
this.services_[serviceName] = service;
447-
}
448-
return service;
449-
}
450-
451-
/**
452-
* Returns the service instance associated with this FirebaseApp instance (creating it on demand
453-
* if needed). This is used for looking up monkeypatched service instances.
454-
*
455-
* @param serviceName The name of the service instance to return.
456-
* @return The service instance with the provided name.
457-
*/
458-
private getService_(serviceName: string): FirebaseServiceInterface {
436+
private ensureService_<T>(serviceName: string, initializer: () => T): T {
459437
this.checkDestroyed_();
460-
461438
if (!(serviceName in this.services_)) {
462-
this.services_[serviceName] = this.firebaseInternals_.serviceFactories[serviceName](
463-
this,
464-
this.extendApp_.bind(this),
465-
);
439+
this.services_[serviceName] = initializer();
466440
}
467441

468-
return this.services_[serviceName];
469-
}
470-
471-
/**
472-
* Callback function used to extend an App instance at the time of service instance creation.
473-
*/
474-
private extendApp_(props: {[prop: string]: any}): void {
475-
deepExtend(this, props);
442+
return this.services_[serviceName] as T;
476443
}
477444

478445
/**
@@ -487,3 +454,11 @@ export class FirebaseApp implements app.App {
487454
}
488455
}
489456
}
457+
458+
interface StatefulFirebaseService {
459+
delete(): Promise<void>;
460+
}
461+
462+
function isStateful(service: any): service is StatefulFirebaseService {
463+
return typeof service.delete === 'function';
464+
}

src/firebase-namespace.ts

Lines changed: 2 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@
1717

1818
import fs = require('fs');
1919

20-
import { deepExtend } from './utils/deep-copy';
2120
import { AppErrorCodes, FirebaseAppError } from './utils/error';
2221
import { AppOptions, app } from './firebase-namespace-api';
23-
import { AppHook, FirebaseApp } from './firebase-app';
24-
import { FirebaseServiceFactory, FirebaseServiceInterface } from './firebase-service';
22+
import { FirebaseApp } from './firebase-app';
2523
import { cert, refreshToken, applicationDefault } from './credential/credential';
2624
import { getApplicationDefault } from './credential/credential-internal';
2725

@@ -69,11 +67,8 @@ export interface FirebaseServiceNamespace <T> {
6967
* Internals of a FirebaseNamespace instance.
7068
*/
7169
export class FirebaseNamespaceInternals {
72-
public serviceFactories: {[serviceName: string]: FirebaseServiceFactory} = {};
7370

7471
private apps_: {[appName: string]: App} = {};
75-
private appHooks_: {[service: string]: AppHook} = {};
76-
7772
constructor(public firebase_: {[key: string]: any}) {}
7873

7974
/**
@@ -118,11 +113,7 @@ export class FirebaseNamespaceInternals {
118113
}
119114

120115
const app = new FirebaseApp(options, appName, this);
121-
122116
this.apps_[appName] = app;
123-
124-
this.callAppHooks_(app, 'create');
125-
126117
return app;
127118
}
128119

@@ -170,80 +161,7 @@ export class FirebaseNamespaceInternals {
170161
}
171162

172163
const appToRemove = this.app(appName);
173-
this.callAppHooks_(appToRemove, 'delete');
174-
delete this.apps_[appName];
175-
}
176-
177-
/**
178-
* Registers a new service on this Firebase namespace.
179-
*
180-
* @param serviceName The name of the Firebase service to register.
181-
* @param createService A factory method to generate an instance of the Firebase service.
182-
* @param serviceProperties Optional properties to extend this Firebase namespace with.
183-
* @param appHook Optional callback that handles app-related events like app creation and deletion.
184-
* @return The Firebase service's namespace.
185-
*/
186-
public registerService(
187-
serviceName: string,
188-
createService: FirebaseServiceFactory,
189-
serviceProperties?: object,
190-
appHook?: AppHook): FirebaseServiceNamespace<FirebaseServiceInterface> {
191-
let errorMessage;
192-
if (typeof serviceName === 'undefined') {
193-
errorMessage = 'No service name provided. Service name must be a non-empty string.';
194-
} else if (typeof serviceName !== 'string' || serviceName === '') {
195-
errorMessage = `Invalid service name "${serviceName}" provided. Service name must be a non-empty string.`;
196-
} else if (serviceName in this.serviceFactories) {
197-
errorMessage = `Firebase service named "${serviceName}" has already been registered.`;
198-
}
199-
200-
if (typeof errorMessage !== 'undefined') {
201-
throw new FirebaseAppError(
202-
AppErrorCodes.INTERNAL_ERROR,
203-
`INTERNAL ASSERT FAILED: ${errorMessage}`,
204-
);
205-
}
206-
207-
this.serviceFactories[serviceName] = createService;
208-
if (appHook) {
209-
this.appHooks_[serviceName] = appHook;
210-
}
211-
212-
// The service namespace is an accessor function which takes a FirebaseApp instance
213-
// or uses the default app if no FirebaseApp instance is provided
214-
const serviceNamespace: FirebaseServiceNamespace<FirebaseServiceInterface> = (appArg?: App) => {
215-
if (typeof appArg === 'undefined') {
216-
appArg = this.app();
217-
}
218-
219-
// Forward service instance lookup to the FirebaseApp
220-
return (appArg as any)[serviceName]();
221-
};
222-
223-
// ... and a container for service-level properties.
224-
if (serviceProperties !== undefined) {
225-
deepExtend(serviceNamespace, serviceProperties);
226-
}
227-
228-
// Monkey-patch the service namespace onto the Firebase namespace
229-
this.firebase_[serviceName] = serviceNamespace;
230-
231-
return serviceNamespace;
232-
}
233-
234-
/**
235-
* Calls the app hooks corresponding to the provided event name for each service within the
236-
* provided App instance.
237-
*
238-
* @param app The App instance whose app hooks to call.
239-
* @param eventName The event name representing which app hooks to call.
240-
*/
241-
private callAppHooks_(app: App, eventName: string): void {
242-
Object.keys(this.serviceFactories).forEach((serviceName) => {
243-
if (this.appHooks_[serviceName]) {
244-
this.appHooks_[serviceName](eventName, app);
245-
}
246-
});
164+
delete this.apps_[appToRemove.name];
247165
}
248166

249167
/**

src/firebase-service.ts

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

0 commit comments

Comments
 (0)