|
1 |
| -import { writeFile } from "fs/promises"; |
2 |
| - |
3 |
| -let firebaseConfig = {}; |
4 |
| -if (process.env.FIREBASE_CONFIG?.startsWith("{")) { |
5 |
| - // TODO probably want a more robust yaml parse |
6 |
| - firebaseConfig = Object.fromEntries(process.env.FIREBASE_CONFIG.match(/[^(\:\{\},)]+\:[^(,})]+/g).map(it => { |
7 |
| - const parts = it.split(":"); |
8 |
| - return [parts[0], parts.slice(1).join(":")] |
9 |
| - })); |
10 |
| -} |
| 1 | +import { writeFile, readFile } from "node:fs/promises"; |
| 2 | +import { pathToFileURL } from "node:url"; |
| 3 | +import { isAbsolute, join } from "node:path"; |
11 | 4 |
|
12 |
| -const projectId = firebaseConfig.projectId; |
13 |
| -const appId = firebaseConfig.appId; |
14 |
| -const apiKey = firebaseConfig.apiKey; |
| 5 | +async function getWebConfig() { |
| 6 | + let configFromEnvironment = undefined; |
| 7 | + // $FIREBASE_WEBAPP_CONFIG can be either a JSON representation of FirebaseOptions or the path |
| 8 | + // to a filename |
| 9 | + if (process.env.FIREBASE_WEBAPP_CONFIG) { |
| 10 | + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{")) { |
| 11 | + try { |
| 12 | + configFromEnvironment = JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); |
| 13 | + } catch(e) { |
| 14 | + console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.", e); |
| 15 | + } |
| 16 | + } else { |
| 17 | + const fileName = process.env.FIREBASE_WEBAPP_CONFIG; |
| 18 | + const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); |
| 19 | + const fileContents = await readFile(fileURL, "utf-8").catch((err) => { |
| 20 | + console.error(err); |
| 21 | + return undefined; |
| 22 | + }); |
| 23 | + if (fileContents) { |
| 24 | + try { |
| 25 | + configFromEnvironment = JSON.parse(fileContents); |
| 26 | + } catch(e) { |
| 27 | + console.error(`Contents of ${fileName} could not be parsed.`, e); |
| 28 | + } |
| 29 | + } |
| 30 | + } |
| 31 | + } |
15 | 32 |
|
16 |
| -const config = projectId && appId && apiKey && await (await fetch( |
17 |
| - `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, |
18 |
| - { headers: { "x-goog-api-key": apiKey } } |
19 |
| -)).json(); |
| 33 | + // In Firebase App Hosting the config provided to the environment variable is up-to-date and |
| 34 | + // "complete" we should not reach out to the webConfig endpoint to freshen it |
| 35 | + if (process.env.X_GOOGLE_TARGET_PLATFORM === "fah") { |
| 36 | + return configFromEnvironment; |
| 37 | + } |
20 | 38 |
|
21 |
| -if (config) { |
22 |
| - config.apiKey = apiKey; |
| 39 | + if (!configFromEnvironment) { |
| 40 | + return undefined; |
| 41 | + } |
| 42 | + const projectId = configFromEnvironment.projectId || "-"; |
| 43 | + const appId = configFromEnvironment.appId; |
| 44 | + const apiKey = configFromEnvironment.apiKey; |
| 45 | + if (!appId || !apiKey) { |
| 46 | + console.error("appId and apiKey are needed"); |
| 47 | + return undefined; |
| 48 | + } |
| 49 | + const response = await fetch( |
| 50 | + `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, |
| 51 | + { headers: { "x-goog-api-key": apiKey } } |
| 52 | + ); |
| 53 | + if (!response.ok) { |
| 54 | + // TODO add sensible error |
| 55 | + console.error("yikes."); |
| 56 | + return undefined; |
| 57 | + } |
| 58 | + const json = await response.json().catch(() => { |
| 59 | + // TODO add sensible error |
| 60 | + console.error("also yikes."); |
| 61 | + return undefined; |
| 62 | + }); |
| 63 | + return { ...json, apiKey }; |
23 | 64 | }
|
24 | 65 |
|
25 |
| -let emulatorHosts = { |
| 66 | +const config = await getWebConfig(); |
| 67 | + |
| 68 | +const emulatorHosts = { |
| 69 | + // TODO: remote config, functions, and data connect emulators? |
26 | 70 | firestore: process.env.FIRESTORE_EMULATOR_HOST,
|
27 | 71 | database: process.env.FIREBASE_DATABASE_EMULATOR_HOST,
|
28 | 72 | storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST,
|
29 | 73 | auth: process.env.FIREBASE_AUTH_EMULATOR_HOST,
|
30 | 74 | };
|
31 | 75 |
|
32 |
| -if (!Object.values(emulatorHosts).filter(it => it).length) { |
33 |
| - emulatorHosts = undefined; |
34 |
| -} |
| 76 | +const anyEmulatorHosts = Object.values(emulatorHosts).filter(it => it).length > 0; |
35 | 77 |
|
36 |
| -const defaults = (config || emulatorHosts) && { config, emulatorHosts }; |
| 78 | +// getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's |
| 79 | +// truthy—if we've done nothing here, make it falsy. |
| 80 | +const defaults = (config || anyEmulatorHosts) ? { config, emulatorHosts } : undefined; |
37 | 81 |
|
38 | 82 | await Promise.all([
|
39 |
| - writeFile("./defaults.js", `module.exports = ${JSON.stringify(defaults)}`), |
40 |
| - writeFile("./defaults.mjs", `export default ${JSON.stringify(defaults)}`), |
| 83 | + writeFile(join(import.meta.dirname, "defaults.js"), `module.exports = ${JSON.stringify(defaults)}`), |
| 84 | + writeFile(join(import.meta.dirname, "defaults.mjs"), `export default ${JSON.stringify(defaults)}`), |
41 | 85 | ]);
|
0 commit comments