1
1
import { fileURLToPath } from 'node:url'
2
2
import { normalize } from 'node:path'
3
+ import { readFile , stat } from 'node:fs/promises'
4
+ import stripJsonComments from 'strip-json-comments'
3
5
import {
4
6
addImports ,
5
7
addPlugin ,
@@ -61,6 +63,18 @@ export interface VueFireNuxtModuleOptions {
61
63
* Enables Authentication
62
64
*/
63
65
auth ?: boolean
66
+
67
+ /**
68
+ * Controls whether to use emulators or not. Pass `false` to disable emulators. When set to `true`, emulators are enabled when they are detected in the `firebase.json` file. You still need to run the emulators in parallel to your app.
69
+ */
70
+ emulators ?:
71
+ | boolean
72
+ | {
73
+ /**
74
+ * The host for the Firestore emulator. Defaults to `localhost`.
75
+ */
76
+ host ?: string
77
+ }
64
78
}
65
79
66
80
const logger = consola . withTag ( 'nuxt-vuefire module' )
@@ -76,9 +90,10 @@ export default defineNuxtModule<VueFireNuxtModuleOptions>({
76
90
77
91
defaults : {
78
92
optionsApiPlugin : false ,
93
+ emulators : true ,
79
94
} ,
80
95
81
- setup ( options , nuxt ) {
96
+ async setup ( options , nuxt ) {
82
97
// ensure provided options are valid
83
98
if ( ! options . config ) {
84
99
throw new Error (
@@ -185,6 +200,35 @@ export default defineNuxtModule<VueFireNuxtModuleOptions>({
185
200
] )
186
201
}
187
202
203
+ // Emulators must be enabled after the app is initialized but before some APIs like auth.signinWithCustomToken() are called
204
+ if (
205
+ // Disable emulators on production unless the user explicitly enables them
206
+ ( process . env . NODE_ENV !== 'production' ||
207
+ process . env . VUEFIRE_EMULATORS ) &&
208
+ options . emulators
209
+ ) {
210
+ const emulators = await enableEmulators (
211
+ options . emulators ,
212
+ resolve ( nuxt . options . rootDir , 'firebase.json' ) ,
213
+ logger
214
+ )
215
+
216
+ nuxt . options . runtimeConfig . public . vuefire ??= { }
217
+ nuxt . options . runtimeConfig . public . vuefire . emulators = emulators
218
+
219
+ for ( const serviceName in emulators ) {
220
+ const { host, port } = emulators [ serviceName as keyof typeof emulators ]
221
+ // set the env variables so they are picked up automatically by the admin SDK
222
+ process . env [
223
+ serviceName === 'firestore'
224
+ ? 'FIRESTORE_EMULATOR_HOST'
225
+ : `FIREBASE_${ serviceName . toUpperCase ( ) } _EMULATOR_HOST`
226
+ ] = `${ host } :${ port } `
227
+ logger . info ( `Enabling ${ serviceName } emulator at ${ host } :${ port } ` )
228
+ addPlugin ( resolve ( runtimeDir , `emulators/${ serviceName } .plugin` ) )
229
+ }
230
+ }
231
+
188
232
// adds the firebase app to each application
189
233
addPlugin ( resolve ( runtimeDir , 'app/plugin.client' ) )
190
234
addPlugin ( resolve ( runtimeDir , 'app/plugin.server' ) )
@@ -292,6 +336,12 @@ interface VueFireRuntimeConfig {
292
336
vuefireAdminOptions ?: Omit < AppOptions , 'credential' >
293
337
}
294
338
339
+ interface VueFirePublicRuntimeConfig {
340
+ vuefire ?: {
341
+ emulators ?: FirebaseEmulatorsToEnable
342
+ }
343
+ }
344
+
295
345
interface VueFireAppConfig {
296
346
/**
297
347
* Firebase config to initialize the app.
@@ -309,6 +359,7 @@ interface VueFireAppConfig {
309
359
declare module '@nuxt/schema' {
310
360
export interface AppConfig extends VueFireAppConfig { }
311
361
export interface RuntimeConfig extends VueFireRuntimeConfig { }
362
+ export interface PublicRuntimeConfig extends VueFirePublicRuntimeConfig { }
312
363
}
313
364
314
365
// @ts -ignore: #app not found error when building
@@ -336,3 +387,160 @@ declare module '@vue/runtime-core' {
336
387
$firebaseAdminApp : FirebaseAdminApp
337
388
}
338
389
}
390
+
391
+ async function enableEmulators (
392
+ emulatorOptions : VueFireNuxtModuleOptions [ 'emulators' ] ,
393
+ firebaseJsonPath : string ,
394
+ logger : typeof consola
395
+ ) {
396
+ const fileStats = await stat ( firebaseJsonPath )
397
+ if ( ! fileStats . isFile ( ) ) {
398
+ return
399
+ }
400
+ let firebaseJson : FirebaseEmulatorsJSON
401
+ try {
402
+ firebaseJson = JSON . parse (
403
+ stripJsonComments ( await readFile ( firebaseJsonPath , 'utf8' ) , {
404
+ trailingCommas : true ,
405
+ } )
406
+ )
407
+ } catch ( err ) {
408
+ logger . error ( 'Error parsing the `firebase.json` file' , err )
409
+ logger . error ( 'Cannot enable Emulators' )
410
+ return
411
+ }
412
+
413
+ if ( ! firebaseJson . emulators ) {
414
+ if ( emulatorOptions === true ) {
415
+ logger . warn (
416
+ 'You enabled emulators but there is no `emulators` key in your `firebase.json` file. Emulators will not be enabled.'
417
+ )
418
+ }
419
+ return
420
+ }
421
+
422
+ const services = [ 'auth' , 'database' , 'firestore' , 'functions' ] as const
423
+
424
+ const defaultHost =
425
+ typeof emulatorOptions === 'object' ? emulatorOptions . host : 'localhost'
426
+
427
+ const emulatorsToEnable = services . reduce ( ( acc , service ) => {
428
+ if ( firebaseJson . emulators ! [ service ] ) {
429
+ // these env variables are automatically picked up by the admin SDK too
430
+ // https://firebase.google.com/docs/emulator-suite/connect_rtdb?hl=en&authuser=0#admin_sdks
431
+ const envKey =
432
+ service === 'firestore'
433
+ ? 'FIRESTORE_EMULATOR_HOST'
434
+ : `FIREBASE_${ service . toUpperCase ( ) } _EMULATOR_HOST`
435
+
436
+ if ( process . env [ envKey ] ) {
437
+ try {
438
+ const url = new URL ( `http://${ process . env [ envKey ] } ` )
439
+ acc [ service ] = {
440
+ host : url . hostname ,
441
+ port : Number ( url . port ) ,
442
+ }
443
+ return acc
444
+ } catch ( err ) {
445
+ logger . error (
446
+ `The "${ envKey } " env variable is set but it is not a valid URL. It should be something like "localhost:8080" or "127.0.0.1:8080". It will be ignored.`
447
+ )
448
+ logger . error ( `Cannot enable the ${ service } Emulator.` )
449
+ }
450
+ }
451
+ // take the values from the firebase.json file
452
+ const serviceEmulatorConfig = firebaseJson . emulators ! [ service ]
453
+ if ( serviceEmulatorConfig ?. host == null ) {
454
+ logger . warn (
455
+ `The "${ service } " emulator is enabled but there is no "host" key in the "emulators.${ service } " key of your "firebase.json" file. It is recommended to set it to avoid mismatches between origins. Set it to "${ defaultHost } ".`
456
+ )
457
+ }
458
+
459
+ const host = serviceEmulatorConfig ?. host || defaultHost
460
+ const port = serviceEmulatorConfig ?. port
461
+ if ( ! host || ! port ) {
462
+ logger . error (
463
+ `The "${ service } " emulator is enabled but there is no "host" or "port" key in the "emulators" key of your "firebase.json" file. You must specify *both*. It will be ignored.`
464
+ )
465
+ return acc
466
+ }
467
+ acc [ service ] = { host, port }
468
+ }
469
+ return acc
470
+ } , { } as FirebaseEmulatorsToEnable )
471
+
472
+ return emulatorsToEnable
473
+ }
474
+
475
+ /**
476
+ * Extracted from as we cannot install firebase-tools just for the types
477
+ * - https://github.com/firebase/firebase-tools/blob/master/src/firebaseConfig.ts#L183
478
+ * - https://github.com/firebase/firebase-tools/blob/master/schema/firebase-config.json
479
+ * @internal
480
+ */
481
+ interface FirebaseEmulatorsJSON {
482
+ emulators ?: {
483
+ auth ?: {
484
+ host ?: string
485
+ port ?: number
486
+ }
487
+ database ?: {
488
+ host ?: string
489
+ port ?: number
490
+ }
491
+ eventarc ?: {
492
+ host ?: string
493
+ port ?: number
494
+ }
495
+ extensions ?: {
496
+ [ k : string ] : unknown
497
+ }
498
+ firestore ?: {
499
+ host ?: string
500
+ port ?: number
501
+ websocketPort ?: number
502
+ }
503
+ functions ?: {
504
+ host ?: string
505
+ port ?: number
506
+ }
507
+ hosting ?: {
508
+ host ?: string
509
+ port ?: number
510
+ }
511
+ hub ?: {
512
+ host ?: string
513
+ port ?: number
514
+ }
515
+ logging ?: {
516
+ host ?: string
517
+ port ?: number
518
+ }
519
+ pubsub ?: {
520
+ host ?: string
521
+ port ?: number
522
+ }
523
+ singleProjectMode ?: boolean
524
+ storage ?: {
525
+ host ?: string
526
+ port ?: number
527
+ }
528
+ ui ?: {
529
+ enabled ?: boolean
530
+ host ?: string
531
+ port ?: string | number
532
+ }
533
+ }
534
+ }
535
+
536
+ type FirebaseEmulatorService =
537
+ | 'auth'
538
+ | 'database'
539
+ | 'firestore'
540
+ | 'functions'
541
+ // | 'hosting' we are the hosting emulator
542
+ | 'storage'
543
+
544
+ type FirebaseEmulatorsToEnable = {
545
+ [ key in FirebaseEmulatorService ] : { host : string ; port : number }
546
+ }
0 commit comments