1
- import { readFile , access , constants } from 'node:fs/promises'
2
- import stripJsonComments from 'strip-json-comments'
3
1
import type { ConsolaInstance } from 'consola'
4
2
import type { VueFireNuxtModuleOptionsResolved } from './options'
5
3
6
- export async function willUseEmulators (
7
- { emulators } : VueFireNuxtModuleOptionsResolved ,
8
- firebaseJsonPath : string ,
4
+ /**
5
+ * Detects the emulators to enable based on their API. Returns an object of all the emulators that should be enabled.
6
+ *
7
+ * @param options - The module options
8
+ * @param logger - The logger instance
9
+ */
10
+ export async function autodetectEmulators (
11
+ { emulators : options , auth } : VueFireNuxtModuleOptionsResolved ,
9
12
logger : ConsolaInstance
10
- ) : Promise < NonNullable < FirebaseEmulatorsJSON [ 'emulators' ] > | null > {
13
+ ) {
14
+ const defaultHost : string = options . host || '127.0.0.1'
15
+
11
16
const isEmulatorEnabled =
12
17
// emulators is always defined
13
- emulators . enabled &&
18
+ options . enabled &&
14
19
// Disable emulators on production unless the user explicitly enables them
15
20
( process . env . NODE_ENV !== 'production' ||
16
21
( process . env . VUEFIRE_EMULATORS &&
@@ -21,66 +26,33 @@ export async function willUseEmulators(
21
26
return null
22
27
}
23
28
24
- // return true if the file doesn't exist instead of throwing
25
- if ( await access ( firebaseJsonPath , constants . F_OK ) . catch ( ( ) => true ) ) {
26
- logger . warn (
27
- `The "firebase.json" file doesn't exist at "${ firebaseJsonPath } ".`
28
- )
29
- return null
30
- }
31
-
32
- let firebaseJson : FirebaseEmulatorsJSON | null = null
33
- try {
34
- firebaseJson = JSON . parse (
35
- stripJsonComments ( await readFile ( firebaseJsonPath , 'utf8' ) , {
36
- trailingCommas : true ,
37
- } )
38
- )
39
- } catch ( err ) {
40
- logger . error ( 'Error parsing the `firebase.json` file' , err )
41
- logger . error ( 'Cannot enable Emulators' )
42
- }
43
-
44
- return firebaseJson ?. emulators ?? null
45
- }
46
-
47
- /**
48
- * Detects the emulators to enable based on the `firebase.json` file. Returns an object of all the emulators that should
49
- * be enabled based on the `firebase.json` file and other options and environment variables.
50
- *
51
- * @param options - The module options
52
- * @param firebaseJsonPath - resolved path to the `firebase.json` file
53
- * @param logger - The logger instance
54
- */
55
- export function detectEmulators (
56
- {
57
- emulators : _vuefireEmulatorsOptions ,
58
- auth,
59
- } : VueFireNuxtModuleOptionsResolved ,
60
- firebaseEmulatorsConfig : NonNullable < FirebaseEmulatorsJSON [ 'emulators' ] > ,
61
- logger : ConsolaInstance
62
- ) {
63
- // normalize the emulators option
64
- const vuefireEmulatorsOptions =
65
- typeof _vuefireEmulatorsOptions === 'object'
66
- ? _vuefireEmulatorsOptions
67
- : {
68
- enabled : _vuefireEmulatorsOptions ,
69
- }
29
+ const emulatorsResponse : EmulatorsAPIResponse | null = await fetch (
30
+ `http://${ defaultHost } :4400/emulators`
31
+ )
32
+ . then ( ( res ) => {
33
+ return res . status === 200 ? res . json ( ) : null
34
+ } )
35
+ . catch ( ( err : Error ) => {
36
+ // skip errors of emulators not running
37
+ if (
38
+ err instanceof Error &&
39
+ typeof err . cause === 'object' &&
40
+ // @ts -expect-error: not in the types
41
+ err . cause ?. code !== 'ECONNREFUSED'
42
+ ) {
43
+ logger . error ( 'Error fetching emulators' , err )
44
+ }
45
+ return null
46
+ } )
70
47
71
- if ( ! firebaseEmulatorsConfig ) {
72
- if ( vuefireEmulatorsOptions . enabled !== false ) {
73
- logger . warn (
74
- 'You enabled emulators but there is no `emulators` key in your `firebase.json` file. Emulators will not be enabled.'
75
- )
76
- }
77
- return
48
+ if ( ! emulatorsResponse ) {
49
+ return null
78
50
}
79
51
80
- const defaultHost : string = vuefireEmulatorsOptions . host || '127.0.0.1'
81
-
82
52
const emulatorsToEnable = services . reduce ( ( acc , service ) => {
83
- if ( firebaseEmulatorsConfig [ service ] ) {
53
+ if ( emulatorsResponse [ service ] ) {
54
+ let { host, port } = emulatorsResponse [ service ] !
55
+
84
56
// these env variables are automatically picked up by the admin SDK too
85
57
// https://firebase.google.com/docs/emulator-suite/connect_rtdb?hl=en&authuser=0#admin_sdks
86
58
// Also, Firestore is the only one that has a different env variable
@@ -89,8 +61,6 @@ export function detectEmulators(
89
61
? 'FIRESTORE_EMULATOR_HOST'
90
62
: `FIREBASE_${ service . toUpperCase ( ) } _EMULATOR_HOST`
91
63
92
- let host : string | undefined
93
- let port : number | undefined
94
64
// Pick up the values from the env variables if set by the user
95
65
if ( process . env [ envKey ] ) {
96
66
logger . debug (
@@ -110,52 +80,7 @@ export function detectEmulators(
110
80
}
111
81
}
112
82
113
- // take the values from the firebase.json file
114
- const emulatorsServiceConfig = firebaseEmulatorsConfig [ service ]
115
- // they might be picked up from the environment variables
116
- host ??= emulatorsServiceConfig ?. host || defaultHost
117
- port ??= emulatorsServiceConfig ?. port
118
-
119
- const missingHostServices : FirebaseEmulatorService [ ] = [ ]
120
- if ( emulatorsServiceConfig ?. host == null ) {
121
- // we push to warn later in one single warning
122
- missingHostServices . push ( service )
123
- } else if ( emulatorsServiceConfig . host !== host ) {
124
- logger . error (
125
- `The "${ service } " emulator is enabled but the "host" property in the "emulators.${ service } " section of your "firebase.json" file is different from the "vuefire.emulators.host" value. You might encounter errors in your app if this is not fixed.`
126
- )
127
- }
128
-
129
- // The default value is 127.0.0.1, so it's fine if the user doesn't set it at all
130
- if ( missingHostServices . length > 0 && host !== '127.0.0.1' ) {
131
- logger . warn (
132
- `The "${ service . at (
133
- 0
134
- ) ! } " 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. You should probably set it to "${ defaultHost } " ("vuefire.emulators.host" value).` +
135
- ( missingHostServices . length > 1
136
- ? ` The following emulators are also missing the "host" key: ${ missingHostServices
137
- . slice ( 1 )
138
- . join ( ', ' ) } .`
139
- : '' )
140
- )
141
- }
142
-
143
- if ( ! port ) {
144
- logger . error (
145
- `The "${ service } " emulator is enabled but there is no "port" property in the "emulators" section of your "firebase.json" file. It must be specified to enable emulators. The "${ service } " emulator won't be enabled.`
146
- )
147
- return acc
148
- // if the port is set in the config, it must match the env variable
149
- } else if (
150
- emulatorsServiceConfig &&
151
- emulatorsServiceConfig . port !== port
152
- ) {
153
- logger . error (
154
- `The "${ service } " emulator is enabled but the "port" property in the "emulators.${ service } " section of your "firebase.json" file is different from the "${ envKey } " env variable. You might encounter errors in your app if this is not fixed.`
155
- )
156
- }
157
-
158
- // add the emulator to the list
83
+ // add them
159
84
acc [ service ] = { host, port }
160
85
}
161
86
return acc
@@ -177,67 +102,6 @@ export function detectEmulators(
177
102
return emulatorsToEnable
178
103
}
179
104
180
- /**
181
- * Extracted from as we cannot install firebase-tools just for the types
182
- * - https://github.com/firebase/firebase-tools/blob/master/src/firebaseConfig.ts#L183
183
- * - https://github.com/firebase/firebase-tools/blob/master/schema/firebase-config.json
184
- * @internal
185
- */
186
- export interface FirebaseEmulatorsJSON {
187
- emulators ?: {
188
- auth ?: {
189
- host ?: string
190
- port ?: number
191
- }
192
- database ?: {
193
- host ?: string
194
- port ?: number
195
- }
196
- eventarc ?: {
197
- host ?: string
198
- port ?: number
199
- }
200
- extensions ?: {
201
- [ k : string ] : unknown
202
- }
203
- firestore ?: {
204
- host ?: string
205
- port ?: number
206
- websocketPort ?: number
207
- }
208
- functions ?: {
209
- host ?: string
210
- port ?: number
211
- }
212
- hosting ?: {
213
- host ?: string
214
- port ?: number
215
- }
216
- hub ?: {
217
- host ?: string
218
- port ?: number
219
- }
220
- logging ?: {
221
- host ?: string
222
- port ?: number
223
- }
224
- pubsub ?: {
225
- host ?: string
226
- port ?: number
227
- }
228
- singleProjectMode ?: boolean
229
- storage ?: {
230
- host ?: string
231
- port ?: number
232
- }
233
- ui ?: {
234
- enabled ?: boolean
235
- host ?: string
236
- port ?: string | number
237
- }
238
- }
239
- }
240
-
241
105
export type FirebaseEmulatorService =
242
106
| 'auth'
243
107
| 'database'
@@ -266,3 +130,32 @@ export interface FirebaseEmulatorsToEnable
266
130
options ?: Parameters < typeof import ( 'firebase/auth' ) . connectAuthEmulator > [ 2 ]
267
131
}
268
132
}
133
+
134
+ interface EmulatorServiceAddressInfo {
135
+ address : string
136
+ family : string // Assuming this will contain valid IPv4 or IPv6 strings
137
+ port : number
138
+ }
139
+
140
+ interface EmulatorService {
141
+ listen : EmulatorServiceAddressInfo [ ]
142
+ name : string
143
+ host : string
144
+ port : number
145
+ pid ?: number // Assuming this field is optional
146
+ reservedPorts ?: number [ ] // Assuming this field is optional and can be an array of numbers
147
+ webSocketHost ?: string // Assuming this field is optional
148
+ webSocketPort ?: number // Assuming this field is optional
149
+ }
150
+
151
+ interface EmulatorsAPIResponse {
152
+ hub ?: EmulatorService
153
+ ui ?: EmulatorService
154
+ logging ?: EmulatorService
155
+ auth ?: EmulatorService
156
+ functions ?: EmulatorService
157
+ firestore ?: EmulatorService
158
+ database ?: EmulatorService
159
+ hosting ?: EmulatorService
160
+ storage ?: EmulatorService
161
+ }
0 commit comments