16
16
*/
17
17
18
18
import firebase from 'firebase' ;
19
+ import 'firebase/database' ;
20
+ import 'firebase/firestore' ;
21
+ import 'firebase/storage' ;
22
+
23
+ import type { app } from 'firebase-admin' ;
19
24
import { _FirebaseApp } from '@firebase/app-types/private' ;
20
25
import { FirebaseAuthInternal } from '@firebase/auth-interop-types' ;
21
26
import * as request from 'request' ;
22
27
import { base64 } from '@firebase/util' ;
23
28
import { setLogLevel , LogLevel } from '@firebase/logger' ;
24
29
import { Component , ComponentType } from '@firebase/component' ;
25
30
26
- const { firestore, database } = firebase ;
27
- export { firestore , database } ;
31
+ const { firestore, database, storage } = firebase ;
32
+ export { firestore , database , storage } ;
28
33
29
34
/** If this environment variable is set, use it for the database emulator's address. */
30
35
const DATABASE_ADDRESS_ENV : string = 'FIREBASE_DATABASE_EMULATOR_HOST' ;
31
36
/** The default address for the local database emulator. */
32
37
const DATABASE_ADDRESS_DEFAULT : string = 'localhost:9000' ;
33
38
34
- /** If any of environment variable is set, use it for the Firestore emulator. */
39
+ /** If this environment variable is set, use it for the Firestore emulator. */
35
40
const FIRESTORE_ADDRESS_ENV : string = 'FIRESTORE_EMULATOR_HOST' ;
36
41
/** The default address for the local Firestore emulator. */
37
42
const FIRESTORE_ADDRESS_DEFAULT : string = 'localhost:8080' ;
38
43
44
+ /** If this environment variable is set, use it for the Storage emulator. */
45
+ const FIREBASE_STORAGE_ADDRESS_ENV : string = 'FIREBASE_STORAGE_EMULATOR_HOST' ;
46
+ const CLOUD_STORAGE_ADDRESS_ENV : string = 'STORAGE_EMULATOR_HOST' ;
47
+ /** The default address for the local Firestore emulator. */
48
+ const STORAGE_ADDRESS_DEFAULT : string = 'localhost:9199' ;
49
+
39
50
/** Environment variable to locate the Emulator Hub */
40
51
const HUB_HOST_ENV : string = 'FIREBASE_EMULATOR_HUB' ;
41
52
/** The default address for the Emulator Hub */
@@ -47,6 +58,9 @@ let _databaseHost: string | undefined = undefined;
47
58
/** The actual address for the Firestore emulator */
48
59
let _firestoreHost : string | undefined = undefined ;
49
60
61
+ /** The actual address for the Storage emulator */
62
+ let _storageHost : string | undefined = undefined ;
63
+
50
64
/** The actual address for the Emulator Hub */
51
65
let _hubHost : string | undefined = undefined ;
52
66
@@ -133,6 +147,10 @@ export type FirebaseEmulatorOptions = {
133
147
host : string ;
134
148
port : number ;
135
149
} ;
150
+ storage ?: {
151
+ host : string ;
152
+ port : number ;
153
+ } ;
136
154
hub ?: {
137
155
host : string ;
138
156
port : number ;
@@ -193,6 +211,7 @@ export function apps(): firebase.app.App[] {
193
211
export type AppOptions = {
194
212
databaseName ?: string ;
195
213
projectId ?: string ;
214
+ storageBucket ?: string ;
196
215
auth ?: TokenOptions ;
197
216
} ;
198
217
/** Construct an App authenticated with options.auth. */
@@ -201,19 +220,29 @@ export function initializeTestApp(options: AppOptions): firebase.app.App {
201
220
? createUnsecuredJwt ( options . auth , options . projectId )
202
221
: undefined ;
203
222
204
- return initializeApp ( jwt , options . databaseName , options . projectId ) ;
223
+ return initializeApp (
224
+ jwt ,
225
+ options . databaseName ,
226
+ options . projectId ,
227
+ options . storageBucket
228
+ ) ;
205
229
}
206
230
207
231
export type AdminAppOptions = {
208
232
databaseName ?: string ;
209
233
projectId ?: string ;
234
+ storageBucket ?: string ;
210
235
} ;
211
236
/** Construct an App authenticated as an admin user. */
212
- export function initializeAdminApp ( options : AdminAppOptions ) : firebase . app . App {
237
+ export function initializeAdminApp ( options : AdminAppOptions ) : app . App {
213
238
const admin = require ( 'firebase-admin' ) ;
214
239
215
- const app = admin . initializeApp (
216
- getAppOptions ( options . databaseName , options . projectId ) ,
240
+ const app : app . App = admin . initializeApp (
241
+ getAppOptions (
242
+ options . databaseName ,
243
+ options . projectId ,
244
+ options . storageBucket
245
+ ) ,
217
246
getRandomAppName ( )
218
247
) ;
219
248
@@ -248,6 +277,10 @@ export function useEmulators(options: FirebaseEmulatorOptions): void {
248
277
_firestoreHost = getAddress ( options . firestore . host , options . firestore . port ) ;
249
278
}
250
279
280
+ if ( options . storage ) {
281
+ _storageHost = getAddress ( options . storage . host , options . storage . port ) ;
282
+ }
283
+
251
284
if ( options . hub ) {
252
285
_hubHost = getAddress ( options . hub . host , options . hub . port ) ;
253
286
}
@@ -301,6 +334,13 @@ export async function discoverEmulators(
301
334
} ;
302
335
}
303
336
337
+ if ( data . storage ) {
338
+ options . storage = {
339
+ host : data . storage . host ,
340
+ port : data . storage . port
341
+ } ;
342
+ }
343
+
304
344
if ( data . hub ) {
305
345
options . hub = {
306
346
host : data . hub . host ,
@@ -351,6 +391,27 @@ function getFirestoreHost() {
351
391
return _firestoreHost ;
352
392
}
353
393
394
+ function getStorageHost ( ) {
395
+ if ( ! _storageHost ) {
396
+ const fromEnv =
397
+ process . env [ FIREBASE_STORAGE_ADDRESS_ENV ] ||
398
+ process . env [ CLOUD_STORAGE_ADDRESS_ENV ] ;
399
+ if ( fromEnv ) {
400
+ // The STORAGE_EMULATOR_HOST env var is an older Cloud Standard which includes http:// while
401
+ // the FIREBASE_STORAGE_EMULATOR_HOST is a newer variable supported beginning in the Admin
402
+ // SDK v9.7.0 which does not have the protocol.
403
+ _storageHost = fromEnv . replace ( 'http://' , '' ) ;
404
+ } else {
405
+ console . warn (
406
+ `Warning: ${ FIREBASE_STORAGE_ADDRESS_ENV } not set, using default value ${ STORAGE_ADDRESS_DEFAULT } `
407
+ ) ;
408
+ _storageHost = STORAGE_ADDRESS_DEFAULT ;
409
+ }
410
+ }
411
+
412
+ return _storageHost ;
413
+ }
414
+
354
415
function getHubHost ( ) {
355
416
if ( ! _hubHost ) {
356
417
const fromEnv = process . env [ HUB_HOST_ENV ] ;
@@ -367,34 +428,52 @@ function getHubHost() {
367
428
return _hubHost ;
368
429
}
369
430
431
+ function parseHost ( host : string ) : { hostname : string ; port : number } {
432
+ const withProtocol = host . startsWith ( "http" ) ? host : `http://${ host } ` ;
433
+ const u = new URL ( withProtocol ) ;
434
+ return {
435
+ hostname : u . hostname ,
436
+ port : Number . parseInt ( u . port , 10 )
437
+ } ;
438
+ }
439
+
370
440
function getRandomAppName ( ) : string {
371
441
return 'app-' + new Date ( ) . getTime ( ) + '-' + Math . random ( ) ;
372
442
}
373
443
444
+ function getDatabaseUrl ( databaseName : string ) {
445
+ return `http://${ getDatabaseHost ( ) } ?ns=${ databaseName } ` ;
446
+ }
447
+
374
448
function getAppOptions (
375
449
databaseName ?: string ,
376
- projectId ?: string
450
+ projectId ?: string ,
451
+ storageBucket ?: string
377
452
) : { [ key : string ] : string } {
378
453
let appOptions : { [ key : string ] : string } = { } ;
379
454
380
455
if ( databaseName ) {
381
- appOptions [
382
- 'databaseURL'
383
- ] = `http://${ getDatabaseHost ( ) } ?ns=${ databaseName } ` ;
456
+ appOptions [ 'databaseURL' ] = getDatabaseUrl ( databaseName ) ;
384
457
}
458
+
385
459
if ( projectId ) {
386
460
appOptions [ 'projectId' ] = projectId ;
387
461
}
388
462
463
+ if ( storageBucket ) {
464
+ appOptions [ 'storageBucket' ] = storageBucket ;
465
+ }
466
+
389
467
return appOptions ;
390
468
}
391
469
392
470
function initializeApp (
393
471
accessToken ?: string ,
394
472
databaseName ?: string ,
395
- projectId ?: string
473
+ projectId ?: string ,
474
+ storageBucket ?: string
396
475
) : firebase . app . App {
397
- const appOptions = getAppOptions ( databaseName , projectId ) ;
476
+ const appOptions = getAppOptions ( databaseName , projectId , storageBucket ) ;
398
477
const app = firebase . initializeApp ( appOptions , getRandomAppName ( ) ) ;
399
478
if ( accessToken ) {
400
479
const mockAuthComponent = new Component (
@@ -417,17 +496,22 @@ function initializeApp(
417
496
) ;
418
497
}
419
498
if ( databaseName ) {
499
+ const { hostname, port } = parseHost ( getDatabaseHost ( ) ) ;
500
+ app . database ( ) . useEmulator ( hostname , port ) ;
501
+
420
502
// Toggle network connectivity to force a reauthentication attempt.
421
503
// This mitigates a minor race condition where the client can send the
422
504
// first database request before authenticating.
423
505
app . database ( ) . goOffline ( ) ;
424
506
app . database ( ) . goOnline ( ) ;
425
507
}
426
508
if ( projectId ) {
427
- app . firestore ( ) . settings ( {
428
- host : getFirestoreHost ( ) ,
429
- ssl : false
430
- } ) ;
509
+ const { hostname, port } = parseHost ( getFirestoreHost ( ) ) ;
510
+ app . firestore ( ) . useEmulator ( hostname , port ) ;
511
+ }
512
+ if ( storageBucket ) {
513
+ const { hostname, port } = parseHost ( getStorageHost ( ) ) ;
514
+ app . storage ( ) . useEmulator ( hostname , port ) ;
431
515
}
432
516
/**
433
517
Mute warnings for the previously-created database and whatever other
@@ -498,6 +582,34 @@ export async function loadFirestoreRules(
498
582
}
499
583
}
500
584
585
+ export type LoadStorageRulesOptions = {
586
+ rules : string ;
587
+ } ;
588
+ export async function loadStorageRules (
589
+ options : LoadStorageRulesOptions
590
+ ) : Promise < void > {
591
+ if ( ! options . rules ) {
592
+ throw new Error ( 'must provide rules to loadStorageRules' ) ;
593
+ }
594
+
595
+ const resp = await requestPromise ( request . put , {
596
+ method : 'PUT' ,
597
+ uri : `http://${ getStorageHost ( ) } /internal/setRules` ,
598
+ headers : {
599
+ 'Content-Type' : 'application/json'
600
+ } ,
601
+ body : JSON . stringify ( {
602
+ rules : {
603
+ files : [ { name : 'storage.rules' , content : options . rules } ]
604
+ }
605
+ } )
606
+ } ) ;
607
+
608
+ if ( resp . statusCode !== 200 ) {
609
+ throw new Error ( resp . body ) ;
610
+ }
611
+ }
612
+
501
613
export type ClearFirestoreDataOptions = {
502
614
projectId : string ;
503
615
} ;
0 commit comments