@@ -38,7 +38,7 @@ const FIRESTORE_ADDRESS_DEFAULT: string = 'localhost:8080';
38
38
39
39
/** Environment variable to locate the Emulator Hub */
40
40
const HUB_HOST_ENV : string = 'FIREBASE_EMULATOR_HUB' ;
41
- /** The default address for the Emulator hub */
41
+ /** The default address for the Emulator Hub */
42
42
const HUB_HOST_DEFAULT : string = 'localhost:4400' ;
43
43
44
44
/** The actual address for the database emulator */
@@ -47,6 +47,9 @@ let _databaseHost: string | undefined = undefined;
47
47
/** The actual address for the Firestore emulator */
48
48
let _firestoreHost : string | undefined = undefined ;
49
49
50
+ /** The actual address for the Emulator Hub */
51
+ let _hubHost : string | undefined = undefined ;
52
+
50
53
export type Provider =
51
54
| 'custom'
52
55
| 'email'
@@ -118,6 +121,24 @@ export type FirebaseIdToken = {
118
121
// new users should prefer 'sub' instead.
119
122
export type TokenOptions = Partial < FirebaseIdToken > & { uid ?: string } ;
120
123
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
+
121
142
function createUnsecuredJwt ( token : TokenOptions , projectId ?: string ) : string {
122
143
// Unsecured JWTs use "none" as the algorithm.
123
144
const header = {
@@ -206,6 +227,98 @@ export function initializeAdminApp(options: AdminAppOptions): firebase.app.App {
206
227
return app ;
207
228
}
208
229
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
+
209
322
function getDatabaseHost ( ) {
210
323
if ( ! _databaseHost ) {
211
324
const fromEnv = process . env [ DATABASE_ADDRESS_ENV ] ;
@@ -238,6 +351,22 @@ function getFirestoreHost() {
238
351
return _firestoreHost ;
239
352
}
240
353
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
+
241
370
function getRandomAppName ( ) : string {
242
371
return 'app-' + new Date ( ) . getTime ( ) + '-' + Math . random ( ) ;
243
372
}
@@ -406,13 +535,7 @@ export async function clearFirestoreData(
406
535
export async function withFunctionTriggersDisabled < TResult > (
407
536
fn : ( ) => TResult | Promise < TResult >
408
537
) : 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 ( ) ;
416
539
417
540
// Disable background triggers
418
541
const disableRes = await requestPromise ( request . put , {
0 commit comments