@@ -38,7 +38,7 @@ const FIRESTORE_ADDRESS_DEFAULT: string = 'localhost:8080';
3838
3939/** Environment variable to locate the Emulator Hub */
4040const HUB_HOST_ENV : string = 'FIREBASE_EMULATOR_HUB' ;
41- /** The default address for the Emulator hub */
41+ /** The default address for the Emulator Hub */
4242const HUB_HOST_DEFAULT : string = 'localhost:4400' ;
4343
4444/** The actual address for the database emulator */
@@ -47,6 +47,9 @@ let _databaseHost: string | undefined = undefined;
4747/** The actual address for the Firestore emulator */
4848let _firestoreHost : string | undefined = undefined ;
4949
50+ /** The actual address for the Emulator Hub */
51+ let _hubHost : string | undefined = undefined ;
52+
5053export type Provider =
5154 | 'custom'
5255 | 'email'
@@ -118,6 +121,24 @@ export type FirebaseIdToken = {
118121// new users should prefer 'sub' instead.
119122export type TokenOptions = Partial < FirebaseIdToken > & { uid ?: string } ;
120123
124+ /**
125+ * Host/port configuration for applicable Firebase Emulators.
126+ */
127+ export type FirebaseEmulatorOptions = {
128+ firestore ?: {
129+ host : string ;
130+ port : number ;
131+ } ;
132+ database ?: {
133+ host : string ;
134+ port : number ;
135+ } ;
136+ hub ?: {
137+ host : string ;
138+ port : number ;
139+ } ;
140+ } ;
141+
121142function createUnsecuredJwt ( token : TokenOptions , projectId ?: string ) : string {
122143 // Unsecured JWTs use "none" as the algorithm.
123144 const header = {
@@ -206,6 +227,98 @@ export function initializeAdminApp(options: AdminAppOptions): firebase.app.App {
206227 return app ;
207228}
208229
230+ /**
231+ * Set the host and port configuration for applicable emulators. This will override any values
232+ * found in environment variables. Must be called before initializeAdminApp or initializeTestApp.
233+ *
234+ * @param options options object.
235+ */
236+ export function useEmulators ( options : FirebaseEmulatorOptions ) : void {
237+ if ( ! ( options . database || options . firestore || options . hub ) ) {
238+ throw new Error (
239+ "Argument to useEmulators must contain at least one of 'database', 'firestore', or 'hub'."
240+ ) ;
241+ }
242+
243+ if ( options . database ) {
244+ _databaseHost = getAddress ( options . database . host , options . database . port ) ;
245+ }
246+
247+ if ( options . firestore ) {
248+ _firestoreHost = getAddress ( options . firestore . host , options . firestore . port ) ;
249+ }
250+
251+ if ( options . hub ) {
252+ _hubHost = getAddress ( options . hub . host , options . hub . port ) ;
253+ }
254+ }
255+
256+ /**
257+ * Use the Firebase Emulator hub to discover other running emulators. Call useEmulators() with
258+ * the result to configure the library to use the discovered emulators.
259+ *
260+ * @param hubHost the host where the Emulator Hub is running (ex: 'localhost')
261+ * @param hubPort the port where the Emulator Hub is running (ex: 4400)
262+ */
263+ export async function discoverEmulators (
264+ hubHost ?: string ,
265+ hubPort ?: number
266+ ) : Promise < FirebaseEmulatorOptions > {
267+ if ( ( hubHost && ! hubPort ) || ( ! hubHost && hubPort ) ) {
268+ throw new Error (
269+ `Invalid configuration hubHost=${ hubHost } and hubPort=${ hubPort } . If either parameter is supplied, both must be defined.`
270+ ) ;
271+ }
272+
273+ const hubAddress =
274+ hubHost && hubPort ? getAddress ( hubHost , hubPort ) : getHubHost ( ) ;
275+
276+ const res = await requestPromise ( request . get , {
277+ method : 'GET' ,
278+ uri : `http://${ hubAddress } /emulators`
279+ } ) ;
280+ if ( res . statusCode !== 200 ) {
281+ throw new Error (
282+ `HTTP Error ${ res . statusCode } when attempting to reach Emulator Hub at ${ hubAddress } , are you sure it is running?`
283+ ) ;
284+ }
285+
286+ const options : FirebaseEmulatorOptions = { } ;
287+
288+ const data = JSON . parse ( res . body ) ;
289+
290+ if ( data . database ) {
291+ options . database = {
292+ host : data . database . host ,
293+ port : data . database . port
294+ } ;
295+ }
296+
297+ if ( data . firestore ) {
298+ options . firestore = {
299+ host : data . firestore . host ,
300+ port : data . firestore . port
301+ } ;
302+ }
303+
304+ if ( data . hub ) {
305+ options . hub = {
306+ host : data . hub . host ,
307+ port : data . hub . port
308+ } ;
309+ }
310+
311+ return options ;
312+ }
313+
314+ function getAddress ( host : string , port : number ) {
315+ if ( host . includes ( '::' ) ) {
316+ return `[${ host } ]:${ port } ` ;
317+ } else {
318+ return `${ host } :${ port } ` ;
319+ }
320+ }
321+
209322function getDatabaseHost ( ) {
210323 if ( ! _databaseHost ) {
211324 const fromEnv = process . env [ DATABASE_ADDRESS_ENV ] ;
@@ -238,6 +351,22 @@ function getFirestoreHost() {
238351 return _firestoreHost ;
239352}
240353
354+ function getHubHost ( ) {
355+ if ( ! _hubHost ) {
356+ const fromEnv = process . env [ HUB_HOST_ENV ] ;
357+ if ( fromEnv ) {
358+ _hubHost = fromEnv ;
359+ } else {
360+ console . warn (
361+ `Warning: ${ HUB_HOST_ENV } not set, using default value ${ HUB_HOST_DEFAULT } `
362+ ) ;
363+ _hubHost = HUB_HOST_DEFAULT ;
364+ }
365+ }
366+
367+ return _hubHost ;
368+ }
369+
241370function getRandomAppName ( ) : string {
242371 return 'app-' + new Date ( ) . getTime ( ) + '-' + Math . random ( ) ;
243372}
@@ -406,13 +535,7 @@ export async function clearFirestoreData(
406535export async function withFunctionTriggersDisabled < TResult > (
407536 fn : ( ) => TResult | Promise < TResult >
408537) : Promise < TResult > {
409- let hubHost = process . env [ HUB_HOST_ENV ] ;
410- if ( ! hubHost ) {
411- console . warn (
412- `${ HUB_HOST_ENV } is not set, assuming the Emulator hub is running at ${ HUB_HOST_DEFAULT } `
413- ) ;
414- hubHost = HUB_HOST_DEFAULT ;
415- }
538+ const hubHost = getHubHost ( ) ;
416539
417540 // Disable background triggers
418541 const disableRes = await requestPromise ( request . put , {
0 commit comments