From 64702993ce0a40efb5dfeb4e7251b82d5546970d Mon Sep 17 00:00:00 2001 From: James Daniels Date: Fri, 6 Sep 2024 01:32:42 -0400 Subject: [PATCH 01/22] First. --- packages/util/defaults.d.ts | 8 +++++++ packages/util/defaults.js | 1 + packages/util/defaults.mjs | 1 + packages/util/package.json | 14 ++++++++++-- packages/util/postinstall.mjs | 41 +++++++++++++++++++++++++++++++++++ packages/util/src/defaults.ts | 3 +++ 6 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 packages/util/defaults.d.ts create mode 100644 packages/util/defaults.js create mode 100644 packages/util/defaults.mjs create mode 100644 packages/util/postinstall.mjs diff --git a/packages/util/defaults.d.ts b/packages/util/defaults.d.ts new file mode 100644 index 00000000000..94f995baace --- /dev/null +++ b/packages/util/defaults.d.ts @@ -0,0 +1,8 @@ +import { FirebaseOptions } from "@firebase/app-types"; + +declare const FirebaseDefaults: { + config?: FirebaseOptions; + emulatorHosts?: Record; +} | undefined; + +export default FirebaseDefaults; diff --git a/packages/util/defaults.js b/packages/util/defaults.js new file mode 100644 index 00000000000..a02d4480cb5 --- /dev/null +++ b/packages/util/defaults.js @@ -0,0 +1 @@ +module.exports = undefined \ No newline at end of file diff --git a/packages/util/defaults.mjs b/packages/util/defaults.mjs new file mode 100644 index 00000000000..523ba88a708 --- /dev/null +++ b/packages/util/defaults.mjs @@ -0,0 +1 @@ +export default undefined \ No newline at end of file diff --git a/packages/util/package.json b/packages/util/package.json index 6d97f6e4795..728d4611247 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -21,10 +21,19 @@ }, "default": "./dist/index.esm2017.js" }, + "./postinstall": { + "types": "./defaults.d.ts", + "require": "./defaults.js", + "import": "./defaults.mjs", + "default": "./defaults.mjs" + }, "./package.json": "./package.json" }, "files": [ - "dist" + "dist", + "defaults.*js", + "defaults.d.ts", + "postinstall.mjs" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", @@ -40,7 +49,8 @@ "test:node": "TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha test/**/*.test.* --config ../../config/mocharc.node.js", "trusted-type-check": "tsec -p tsconfig.json --noEmit", "api-report": "api-extractor run --local --verbose", - "typings:public": "node ../../scripts/build/use_typings.js ./dist/util-public.d.ts" + "typings:public": "node ../../scripts/build/use_typings.js ./dist/util-public.d.ts", + "postinstall": "node ./postinstall.mjs" }, "license": "Apache-2.0", "dependencies": { diff --git a/packages/util/postinstall.mjs b/packages/util/postinstall.mjs new file mode 100644 index 00000000000..2a6653f2f13 --- /dev/null +++ b/packages/util/postinstall.mjs @@ -0,0 +1,41 @@ +import { writeFile } from "fs/promises"; + +let firebaseConfig = {}; +if (process.env.FIREBASE_CONFIG?.startsWith("{")) { + // TODO probably want a more robust yaml parse + firebaseConfig = Object.fromEntries(process.env.FIREBASE_CONFIG.match(/[^(\:\{\},)]+\:[^(,})]+/g).map(it => { + const parts = it.split(":"); + return [parts[0], parts.slice(1).join(":")] + })); +} + +const projectId = firebaseConfig.projectId; +const appId = firebaseConfig.appId; +const apiKey = firebaseConfig.apiKey; + +const config = projectId && appId && apiKey && await (await fetch( + `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, + { headers: { "x-goog-api-key": apiKey } } +)).json(); + +if (config) { + config.apiKey = apiKey; +} + +let emulatorHosts = { + firestore: process.env.FIRESTORE_EMULATOR_HOST, + database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, + auth: process.env.FIREBASE_AUTH_EMULATOR_HOST, +}; + +if (!Object.values(emulatorHosts).filter(it => it).length) { + emulatorHosts = undefined; +} + +const defaults = (config || emulatorHosts) && { config, emulatorHosts }; + +await Promise.all([ + writeFile("./defaults.js", `module.exports = ${JSON.stringify(defaults)}`), + writeFile("./defaults.mjs", `export default ${JSON.stringify(defaults)}`), +]); diff --git a/packages/util/src/defaults.ts b/packages/util/src/defaults.ts index 2c972604663..a168efadec6 100644 --- a/packages/util/src/defaults.ts +++ b/packages/util/src/defaults.ts @@ -17,6 +17,8 @@ import { base64Decode } from './crypt'; import { getGlobal } from './global'; +// @ts-ignore +import postinstallDefaults from "@firebase/util/postinstall"; /** * Keys for experimental properties on the `FirebaseDefaults` object. @@ -100,6 +102,7 @@ const getDefaultsFromCookie = (): FirebaseDefaults | undefined => { export const getDefaults = (): FirebaseDefaults | undefined => { try { return ( + postinstallDefaults || getDefaultsFromGlobal() || getDefaultsFromEnvVariable() || getDefaultsFromCookie() From cd6d61a0344208b0d34ffacf66cb067dcafd5dd8 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Dec 2024 10:41:54 -0500 Subject: [PATCH 02/22] Making postinstall.mjs more robust --- packages/util/postinstall.mjs | 96 +++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/packages/util/postinstall.mjs b/packages/util/postinstall.mjs index 2a6653f2f13..c734e69a5cd 100644 --- a/packages/util/postinstall.mjs +++ b/packages/util/postinstall.mjs @@ -1,41 +1,85 @@ -import { writeFile } from "fs/promises"; - -let firebaseConfig = {}; -if (process.env.FIREBASE_CONFIG?.startsWith("{")) { - // TODO probably want a more robust yaml parse - firebaseConfig = Object.fromEntries(process.env.FIREBASE_CONFIG.match(/[^(\:\{\},)]+\:[^(,})]+/g).map(it => { - const parts = it.split(":"); - return [parts[0], parts.slice(1).join(":")] - })); -} +import { writeFile, readFile } from "node:fs/promises"; +import { pathToFileURL } from "node:url"; +import { isAbsolute, join } from "node:path"; -const projectId = firebaseConfig.projectId; -const appId = firebaseConfig.appId; -const apiKey = firebaseConfig.apiKey; +async function getWebConfig() { + let configFromEnvironment = undefined; + // $FIREBASE_WEBAPP_CONFIG can be either a JSON representation of FirebaseOptions or the path + // to a filename + if (process.env.FIREBASE_WEBAPP_CONFIG) { + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{")) { + try { + configFromEnvironment = JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); + } catch(e) { + console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.", e); + } + } else { + const fileName = process.env.FIREBASE_WEBAPP_CONFIG; + const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); + const fileContents = await readFile(fileURL, "utf-8").catch((err) => { + console.error(err); + return undefined; + }); + if (fileContents) { + try { + configFromEnvironment = JSON.parse(fileContents); + } catch(e) { + console.error(`Contents of ${fileName} could not be parsed.`, e); + } + } + } + } -const config = projectId && appId && apiKey && await (await fetch( - `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, - { headers: { "x-goog-api-key": apiKey } } -)).json(); + // In Firebase App Hosting the config provided to the environment variable is up-to-date and + // "complete" we should not reach out to the webConfig endpoint to freshen it + if (process.env.X_GOOGLE_TARGET_PLATFORM === "fah") { + return configFromEnvironment; + } -if (config) { - config.apiKey = apiKey; + if (!configFromEnvironment) { + return undefined; + } + const projectId = configFromEnvironment.projectId || "-"; + const appId = configFromEnvironment.appId; + const apiKey = configFromEnvironment.apiKey; + if (!appId || !apiKey) { + console.error("appId and apiKey are needed"); + return undefined; + } + const response = await fetch( + `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, + { headers: { "x-goog-api-key": apiKey } } + ); + if (!response.ok) { + // TODO add sensible error + console.error("yikes."); + return undefined; + } + const json = await response.json().catch(() => { + // TODO add sensible error + console.error("also yikes."); + return undefined; + }); + return { ...json, apiKey }; } -let emulatorHosts = { +const config = await getWebConfig(); + +const emulatorHosts = { + // TODO: remote config, functions, and data connect emulators? firestore: process.env.FIRESTORE_EMULATOR_HOST, database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, auth: process.env.FIREBASE_AUTH_EMULATOR_HOST, }; -if (!Object.values(emulatorHosts).filter(it => it).length) { - emulatorHosts = undefined; -} +const anyEmulatorHosts = Object.values(emulatorHosts).filter(it => it).length > 0; -const defaults = (config || emulatorHosts) && { config, emulatorHosts }; +// getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's +// truthy—if we've done nothing here, make it falsy. +const defaults = (config || anyEmulatorHosts) ? { config, emulatorHosts } : undefined; await Promise.all([ - writeFile("./defaults.js", `module.exports = ${JSON.stringify(defaults)}`), - writeFile("./defaults.mjs", `export default ${JSON.stringify(defaults)}`), + writeFile(join(import.meta.dirname, "defaults.js"), `module.exports = ${JSON.stringify(defaults)}`), + writeFile(join(import.meta.dirname, "defaults.mjs"), `export default ${JSON.stringify(defaults)}`), ]); From 1381961221440cd6a48d4bff4d67b5daabe2a884 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Dec 2024 10:48:52 -0500 Subject: [PATCH 03/22] Catch fetch --- packages/util/postinstall.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/util/postinstall.mjs b/packages/util/postinstall.mjs index c734e69a5cd..60031d641a9 100644 --- a/packages/util/postinstall.mjs +++ b/packages/util/postinstall.mjs @@ -49,7 +49,11 @@ async function getWebConfig() { const response = await fetch( `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, { headers: { "x-goog-api-key": apiKey } } - ); + ).catch((e) => { + // TODO add sensible error + console.error(e); + return undefined; + }); if (!response.ok) { // TODO add sensible error console.error("yikes."); From 6b0c6b1ddf492739fc9924e810152d4f7e84df76 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Dec 2024 10:52:00 -0500 Subject: [PATCH 04/22] Use what we have --- packages/util/postinstall.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/util/postinstall.mjs b/packages/util/postinstall.mjs index 60031d641a9..04f649552a8 100644 --- a/packages/util/postinstall.mjs +++ b/packages/util/postinstall.mjs @@ -52,17 +52,17 @@ async function getWebConfig() { ).catch((e) => { // TODO add sensible error console.error(e); - return undefined; + return configFromEnvironment; }); if (!response.ok) { // TODO add sensible error console.error("yikes."); - return undefined; + return configFromEnvironment; } const json = await response.json().catch(() => { // TODO add sensible error console.error("also yikes."); - return undefined; + return configFromEnvironment; }); return { ...json, apiKey }; } From 1fb66a5aa6cbbc21457cbfb7a247948743d7cf3d Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Dec 2024 15:13:15 -0500 Subject: [PATCH 05/22] Clean up and temp fix for escaping issue --- packages/util/postinstall.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/util/postinstall.mjs b/packages/util/postinstall.mjs index 04f649552a8..c69a4a4bb72 100644 --- a/packages/util/postinstall.mjs +++ b/packages/util/postinstall.mjs @@ -7,12 +7,21 @@ async function getWebConfig() { // $FIREBASE_WEBAPP_CONFIG can be either a JSON representation of FirebaseOptions or the path // to a filename if (process.env.FIREBASE_WEBAPP_CONFIG) { - if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{")) { + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{\"")) { try { configFromEnvironment = JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); } catch(e) { console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.", e); } + } if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{")) { + // TODO temporary + configFromEnvironment = Object.fromEntries( + process.env.FIREBASE_WEBAPP_CONFIG + .match(/^{(.+)}$/)[1] + .split(',') + .map(it => it.match("([^\:]+)\:(.+)")?.slice(1)) + .filter(it => it) + ); } else { const fileName = process.env.FIREBASE_WEBAPP_CONFIG; const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); From 525a4e21e53fbb9d963b6fd57ce2e99ae1460e5a Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 12:49:40 -0500 Subject: [PATCH 06/22] Cleanup --- packages/util/defaults.d.ts | 8 --- packages/util/defaults.js | 1 - packages/util/defaults.mjs | 1 - packages/util/package.json | 16 ++--- packages/util/postinstall.mjs | 98 ------------------------------- packages/util/postinstall.ts | 90 ++++++++++++++++++++++++++++ packages/util/rollup.config.js | 31 +++++++++- packages/util/src/autoinit_env.ts | 6 ++ packages/util/src/defaults.ts | 4 +- 9 files changed, 134 insertions(+), 121 deletions(-) delete mode 100644 packages/util/defaults.d.ts delete mode 100644 packages/util/defaults.js delete mode 100644 packages/util/defaults.mjs delete mode 100644 packages/util/postinstall.mjs create mode 100644 packages/util/postinstall.ts create mode 100644 packages/util/src/autoinit_env.ts diff --git a/packages/util/defaults.d.ts b/packages/util/defaults.d.ts deleted file mode 100644 index 94f995baace..00000000000 --- a/packages/util/defaults.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { FirebaseOptions } from "@firebase/app-types"; - -declare const FirebaseDefaults: { - config?: FirebaseOptions; - emulatorHosts?: Record; -} | undefined; - -export default FirebaseDefaults; diff --git a/packages/util/defaults.js b/packages/util/defaults.js deleted file mode 100644 index a02d4480cb5..00000000000 --- a/packages/util/defaults.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = undefined \ No newline at end of file diff --git a/packages/util/defaults.mjs b/packages/util/defaults.mjs deleted file mode 100644 index 523ba88a708..00000000000 --- a/packages/util/defaults.mjs +++ /dev/null @@ -1 +0,0 @@ -export default undefined \ No newline at end of file diff --git a/packages/util/package.json b/packages/util/package.json index 82063d1704b..22edf52e830 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -19,19 +19,15 @@ }, "default": "./dist/index.esm2017.js" }, - "./postinstall": { - "types": "./defaults.d.ts", - "require": "./defaults.js", - "import": "./defaults.mjs", - "default": "./defaults.mjs" + "./autoinit_env": { + "require": "./dist/autoinit_env.js", + "import": "./dist/autoinit_env.mjs", + "default": "./dist/autoinit_env.mjs" }, "./package.json": "./package.json" }, "files": [ - "dist", - "defaults.*js", - "defaults.d.ts", - "postinstall.mjs" + "dist" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", @@ -48,7 +44,7 @@ "trusted-type-check": "tsec -p tsconfig.json --noEmit", "api-report": "api-extractor run --local --verbose", "typings:public": "node ../../scripts/build/use_typings.js ./dist/util-public.d.ts", - "postinstall": "node ./postinstall.mjs" + "postinstall": "node ./dist/postinstall.js" }, "license": "Apache-2.0", "dependencies": { diff --git a/packages/util/postinstall.mjs b/packages/util/postinstall.mjs deleted file mode 100644 index c69a4a4bb72..00000000000 --- a/packages/util/postinstall.mjs +++ /dev/null @@ -1,98 +0,0 @@ -import { writeFile, readFile } from "node:fs/promises"; -import { pathToFileURL } from "node:url"; -import { isAbsolute, join } from "node:path"; - -async function getWebConfig() { - let configFromEnvironment = undefined; - // $FIREBASE_WEBAPP_CONFIG can be either a JSON representation of FirebaseOptions or the path - // to a filename - if (process.env.FIREBASE_WEBAPP_CONFIG) { - if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{\"")) { - try { - configFromEnvironment = JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); - } catch(e) { - console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.", e); - } - } if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{")) { - // TODO temporary - configFromEnvironment = Object.fromEntries( - process.env.FIREBASE_WEBAPP_CONFIG - .match(/^{(.+)}$/)[1] - .split(',') - .map(it => it.match("([^\:]+)\:(.+)")?.slice(1)) - .filter(it => it) - ); - } else { - const fileName = process.env.FIREBASE_WEBAPP_CONFIG; - const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); - const fileContents = await readFile(fileURL, "utf-8").catch((err) => { - console.error(err); - return undefined; - }); - if (fileContents) { - try { - configFromEnvironment = JSON.parse(fileContents); - } catch(e) { - console.error(`Contents of ${fileName} could not be parsed.`, e); - } - } - } - } - - // In Firebase App Hosting the config provided to the environment variable is up-to-date and - // "complete" we should not reach out to the webConfig endpoint to freshen it - if (process.env.X_GOOGLE_TARGET_PLATFORM === "fah") { - return configFromEnvironment; - } - - if (!configFromEnvironment) { - return undefined; - } - const projectId = configFromEnvironment.projectId || "-"; - const appId = configFromEnvironment.appId; - const apiKey = configFromEnvironment.apiKey; - if (!appId || !apiKey) { - console.error("appId and apiKey are needed"); - return undefined; - } - const response = await fetch( - `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, - { headers: { "x-goog-api-key": apiKey } } - ).catch((e) => { - // TODO add sensible error - console.error(e); - return configFromEnvironment; - }); - if (!response.ok) { - // TODO add sensible error - console.error("yikes."); - return configFromEnvironment; - } - const json = await response.json().catch(() => { - // TODO add sensible error - console.error("also yikes."); - return configFromEnvironment; - }); - return { ...json, apiKey }; -} - -const config = await getWebConfig(); - -const emulatorHosts = { - // TODO: remote config, functions, and data connect emulators? - firestore: process.env.FIRESTORE_EMULATOR_HOST, - database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, - storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, - auth: process.env.FIREBASE_AUTH_EMULATOR_HOST, -}; - -const anyEmulatorHosts = Object.values(emulatorHosts).filter(it => it).length > 0; - -// getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's -// truthy—if we've done nothing here, make it falsy. -const defaults = (config || anyEmulatorHosts) ? { config, emulatorHosts } : undefined; - -await Promise.all([ - writeFile(join(import.meta.dirname, "defaults.js"), `module.exports = ${JSON.stringify(defaults)}`), - writeFile(join(import.meta.dirname, "defaults.mjs"), `export default ${JSON.stringify(defaults)}`), -]); diff --git a/packages/util/postinstall.ts b/packages/util/postinstall.ts new file mode 100644 index 00000000000..49fd28f498a --- /dev/null +++ b/packages/util/postinstall.ts @@ -0,0 +1,90 @@ +import { writeFile, readFile } from "node:fs/promises"; +import { pathToFileURL } from "node:url"; +import { isAbsolute, join } from "node:path"; +import type { FirebaseOptions } from "@firebase/app"; + +async function getWebConfig(): Promise { + // $FIREBASE_WEBAPP_CONFIG can be either a JSON representation of FirebaseOptions or the path + // to a filename + if (!process.env.FIREBASE_WEBAPP_CONFIG) { + return undefined; + } + + let configFromEnvironment: Partial|undefined = undefined; + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{\"")) { + try { + configFromEnvironment = JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); + } catch(e) { + console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.", e); + } + } else { + const fileName = process.env.FIREBASE_WEBAPP_CONFIG; + const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); + const fileContents = await readFile(fileURL, "utf-8").catch((err) => { + console.error(err); + }); + if (fileContents) { + try { + configFromEnvironment = JSON.parse(fileContents); + } catch(e) { + console.error(`Contents of ${fileName} could not be parsed.`, e); + } + } + } + + // In Firebase App Hosting the config provided to the environment variable is up-to-date and + // "complete" we should not reach out to the webConfig endpoint to freshen it + if (process.env.X_GOOGLE_TARGET_PLATFORM === "fah") { + return configFromEnvironment as FirebaseOptions; + } + + if (!configFromEnvironment) { + return undefined; + } + const projectId = configFromEnvironment.projectId || "-"; + const appId = configFromEnvironment.appId; + const apiKey = configFromEnvironment.apiKey; + if (!appId || !apiKey) { + return undefined; + } + + try { + const response = await fetch( + `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, + { headers: { "x-goog-api-key": apiKey } } + ); + if (!response.ok) { + return undefined; + } + const json = await response.json(); + return { ...json, apiKey }; + } catch(e) { + return undefined; + } +} + +getWebConfig().then(async (config) => { + + const emulatorHosts = Object.entries({ + firestore: process.env.FIRESTORE_EMULATOR_HOST, + database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, + auth: process.env.FIREBASE_AUTH_EMULATOR_HOST, + }).reduce( + // We want a falsy value if none of the above are defined + (current, [key, value]) => value ? { ...current, [key]: value } : current, + undefined as Record|undefined + ); + + // getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's + // truthy—if we've done nothing here, make it falsy. + const defaults = (config || emulatorHosts) ? { config, emulatorHosts } : undefined; + + await Promise.all([ + writeFile(join(__dirname, "autoinit_env.js"), `module.exports = ${JSON.stringify(defaults)}`), + writeFile(join(__dirname, "autoinit_env.mjs"), `export default ${JSON.stringify(defaults)}`), + ]); + + process.exit(0); + +}); diff --git a/packages/util/rollup.config.js b/packages/util/rollup.config.js index d428092d8ee..9e671267863 100644 --- a/packages/util/rollup.config.js +++ b/packages/util/rollup.config.js @@ -72,4 +72,33 @@ const nodeBuilds = [ } ]; -export default [...browserBuilds, ...nodeBuilds]; +const autoinitBuild = [ + { + input: './src/autoinit_env.ts', + output: { + file: './dist/autoinit_env.js', + format: 'cjs', + exports: 'default', + }, + plugins: buildPlugins, + }, + { + input: './src/autoinit_env.ts', + output: { + file: './dist/autoinit_env.mjs', + format: 'es', + }, + plugins: buildPlugins, + }, + { + input: './postinstall.ts', + output: { + file: './dist/postinstall.js', + format: 'cjs', + }, + plugins: buildPlugins, + external: () => true, + } +] + +export default [...browserBuilds, ...nodeBuilds, ...autoinitBuild]; diff --git a/packages/util/src/autoinit_env.ts b/packages/util/src/autoinit_env.ts new file mode 100644 index 00000000000..6844fb852d6 --- /dev/null +++ b/packages/util/src/autoinit_env.ts @@ -0,0 +1,6 @@ +import type { FirebaseDefaults } from "./defaults"; + +// This value is retrieved and hardcoded by the NPM postinstall script +const firebaseDefaultConfig: FirebaseDefaults|undefined = undefined; + +export default firebaseDefaultConfig; diff --git a/packages/util/src/defaults.ts b/packages/util/src/defaults.ts index a168efadec6..cecfc3b9896 100644 --- a/packages/util/src/defaults.ts +++ b/packages/util/src/defaults.ts @@ -17,8 +17,8 @@ import { base64Decode } from './crypt'; import { getGlobal } from './global'; -// @ts-ignore -import postinstallDefaults from "@firebase/util/postinstall"; +// @ts-expect-error +import postinstallDefaults from "@firebase/util/autoinit_env"; /** * Keys for experimental properties on the `FirebaseDefaults` object. From d0ce18ec3827a85a0e408c1d9b75adac14be61c1 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 13:25:02 -0500 Subject: [PATCH 07/22] Move to .js file --- packages/util/autoinit_env.js | 1 + packages/util/autoinit_env.mjs | 1 + packages/util/package.json | 5 +- packages/util/postinstall.js | 90 ++++++++++++++++++++++++++++++++++ packages/util/postinstall.ts | 90 ---------------------------------- packages/util/rollup.config.js | 9 ---- 6 files changed, 95 insertions(+), 101 deletions(-) create mode 100644 packages/util/autoinit_env.js create mode 100644 packages/util/autoinit_env.mjs create mode 100644 packages/util/postinstall.js delete mode 100644 packages/util/postinstall.ts diff --git a/packages/util/autoinit_env.js b/packages/util/autoinit_env.js new file mode 100644 index 00000000000..e70d460aa86 --- /dev/null +++ b/packages/util/autoinit_env.js @@ -0,0 +1 @@ +module.exports = {"config":{"projectId":"web-frameworks-e2e","appId":"1:1087080420931:web:a3151cc6bccd2e1aa1a6f6","databaseURL":"https://web-frameworks-e2e-default-rtdb.firebaseio.com","storageBucket":"web-frameworks-e2e.firebasestorage.app","authDomain":"web-frameworks-e2e.firebaseapp.com","messagingSenderId":"1087080420931","apiKey":"AIzaSyCgZEulMefCoYike_SESqhPW0dTwo5nWxs"}} \ No newline at end of file diff --git a/packages/util/autoinit_env.mjs b/packages/util/autoinit_env.mjs new file mode 100644 index 00000000000..cee16596840 --- /dev/null +++ b/packages/util/autoinit_env.mjs @@ -0,0 +1 @@ +export default {"config":{"projectId":"web-frameworks-e2e","appId":"1:1087080420931:web:a3151cc6bccd2e1aa1a6f6","databaseURL":"https://web-frameworks-e2e-default-rtdb.firebaseio.com","storageBucket":"web-frameworks-e2e.firebasestorage.app","authDomain":"web-frameworks-e2e.firebaseapp.com","messagingSenderId":"1087080420931","apiKey":"AIzaSyCgZEulMefCoYike_SESqhPW0dTwo5nWxs"}} \ No newline at end of file diff --git a/packages/util/package.json b/packages/util/package.json index 22edf52e830..8004e1053bb 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -27,7 +27,8 @@ "./package.json": "./package.json" }, "files": [ - "dist" + "dist", + "postinstall.js" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", @@ -44,7 +45,7 @@ "trusted-type-check": "tsec -p tsconfig.json --noEmit", "api-report": "api-extractor run --local --verbose", "typings:public": "node ../../scripts/build/use_typings.js ./dist/util-public.d.ts", - "postinstall": "node ./dist/postinstall.js" + "postinstall": "node ./postinstall.js" }, "license": "Apache-2.0", "dependencies": { diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js new file mode 100644 index 00000000000..3aa815b14ff --- /dev/null +++ b/packages/util/postinstall.js @@ -0,0 +1,90 @@ +const { writeFile, readFile } = require("node:fs/promises"); +const { pathToFileURL } = require("node:url"); +const { isAbsolute, join } = require("node:path"); + +function getConfigFromEnv() { + if (!process.env.FIREBASE_WEBAPP_CONFIG) { + return Promise.resolve(undefined); + } + + // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be + // either a JSON representation of FirebaseOptions or the path to a filename + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{\"")) { + try { + return Promise.resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); + } catch(e) { + console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.\n", e); + return Promise.resolve(undefined); + } + } + + const fileName = process.env.FIREBASE_WEBAPP_CONFIG; + const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); + return readFile(fileURL, "utf-8").then((fileContents) => { + try { + return JSON.parse(fileContents); + } catch(e) { + console.error(`Contents of "${fileName}" could not be parsed.\n`, e); + return undefined; + } + }, (e) => { + console.error(`Contents of "${fileName}" could not be parsed.\n`, e); + return undefined; + }); +} + +getConfigFromEnv().then((partialConfig) => { + + if (!partialConfig) { + return undefined; + } + // In Firebase App Hosting the config provided to the environment variable is up-to-date and + // "complete" we should not reach out to the webConfig endpoint to freshen it + if (process.env.X_GOOGLE_TARGET_PLATFORM === "fah") { + return partialConfig; + } + const projectId = partialConfig.projectId || "-"; + const appId = partialConfig.appId; + const apiKey = partialConfig.apiKey; + if (!appId || !apiKey) { + console.error(`Unable to fetch Firebase config, appId and apiKey are required.`); + return undefined; + } + + return fetch( + `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, + { headers: { "x-goog-api-key": apiKey } } + ).then((response) => { + if (!response.ok) { + console.error(`Unable to fetch Firebase config, API returned ${response.statusText} (${response.status})`); + return undefined; + } + return response.json().then((json) => ({ ...json, apiKey })); + }); + +}).then((config) => { + + const emulatorHosts = Object.entries({ + firestore: process.env.FIRESTORE_EMULATOR_HOST, + database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, + auth: process.env.FIREBASE_AUTH_EMULATOR_HOST, + }).reduce( + // We want a falsy value if none of the above are defined + (current, [key, value]) => value ? { ...current, [key]: value } : current, + undefined + ); + + // getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's + // truthy—if we've done nothing here, make it falsy. + const defaults = (config || emulatorHosts) ? { config, emulatorHosts } : undefined; + + return Promise.all([ + writeFile(join(__dirname, "autoinit_env.js"), `module.exports = ${JSON.stringify(defaults)}`), + writeFile(join(__dirname, "autoinit_env.mjs"), `export default ${JSON.stringify(defaults)}`), + ]); + +}).then( + () => process.exit(0), + () => process.exit(0), +); diff --git a/packages/util/postinstall.ts b/packages/util/postinstall.ts deleted file mode 100644 index 49fd28f498a..00000000000 --- a/packages/util/postinstall.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { writeFile, readFile } from "node:fs/promises"; -import { pathToFileURL } from "node:url"; -import { isAbsolute, join } from "node:path"; -import type { FirebaseOptions } from "@firebase/app"; - -async function getWebConfig(): Promise { - // $FIREBASE_WEBAPP_CONFIG can be either a JSON representation of FirebaseOptions or the path - // to a filename - if (!process.env.FIREBASE_WEBAPP_CONFIG) { - return undefined; - } - - let configFromEnvironment: Partial|undefined = undefined; - if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{\"")) { - try { - configFromEnvironment = JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); - } catch(e) { - console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.", e); - } - } else { - const fileName = process.env.FIREBASE_WEBAPP_CONFIG; - const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); - const fileContents = await readFile(fileURL, "utf-8").catch((err) => { - console.error(err); - }); - if (fileContents) { - try { - configFromEnvironment = JSON.parse(fileContents); - } catch(e) { - console.error(`Contents of ${fileName} could not be parsed.`, e); - } - } - } - - // In Firebase App Hosting the config provided to the environment variable is up-to-date and - // "complete" we should not reach out to the webConfig endpoint to freshen it - if (process.env.X_GOOGLE_TARGET_PLATFORM === "fah") { - return configFromEnvironment as FirebaseOptions; - } - - if (!configFromEnvironment) { - return undefined; - } - const projectId = configFromEnvironment.projectId || "-"; - const appId = configFromEnvironment.appId; - const apiKey = configFromEnvironment.apiKey; - if (!appId || !apiKey) { - return undefined; - } - - try { - const response = await fetch( - `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, - { headers: { "x-goog-api-key": apiKey } } - ); - if (!response.ok) { - return undefined; - } - const json = await response.json(); - return { ...json, apiKey }; - } catch(e) { - return undefined; - } -} - -getWebConfig().then(async (config) => { - - const emulatorHosts = Object.entries({ - firestore: process.env.FIRESTORE_EMULATOR_HOST, - database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, - storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, - auth: process.env.FIREBASE_AUTH_EMULATOR_HOST, - }).reduce( - // We want a falsy value if none of the above are defined - (current, [key, value]) => value ? { ...current, [key]: value } : current, - undefined as Record|undefined - ); - - // getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's - // truthy—if we've done nothing here, make it falsy. - const defaults = (config || emulatorHosts) ? { config, emulatorHosts } : undefined; - - await Promise.all([ - writeFile(join(__dirname, "autoinit_env.js"), `module.exports = ${JSON.stringify(defaults)}`), - writeFile(join(__dirname, "autoinit_env.mjs"), `export default ${JSON.stringify(defaults)}`), - ]); - - process.exit(0); - -}); diff --git a/packages/util/rollup.config.js b/packages/util/rollup.config.js index 9e671267863..9ea28d36298 100644 --- a/packages/util/rollup.config.js +++ b/packages/util/rollup.config.js @@ -89,15 +89,6 @@ const autoinitBuild = [ format: 'es', }, plugins: buildPlugins, - }, - { - input: './postinstall.ts', - output: { - file: './dist/postinstall.js', - format: 'cjs', - }, - plugins: buildPlugins, - external: () => true, } ] From 861e5b01ed671bf364836edf69b2f28aa16c4090 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 13:56:57 -0500 Subject: [PATCH 08/22] Use rollup plugin replace --- packages/util/autoinit_env.js | 1 - packages/util/autoinit_env.mjs | 1 - packages/util/package.json | 1 + packages/util/postinstall.js | 4 ++-- packages/util/rollup.config.js | 15 +++++++++++---- packages/util/src/defaults.ts | 3 +-- yarn.lock | 10 +++++++++- 7 files changed, 24 insertions(+), 11 deletions(-) delete mode 100644 packages/util/autoinit_env.js delete mode 100644 packages/util/autoinit_env.mjs diff --git a/packages/util/autoinit_env.js b/packages/util/autoinit_env.js deleted file mode 100644 index e70d460aa86..00000000000 --- a/packages/util/autoinit_env.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {"config":{"projectId":"web-frameworks-e2e","appId":"1:1087080420931:web:a3151cc6bccd2e1aa1a6f6","databaseURL":"https://web-frameworks-e2e-default-rtdb.firebaseio.com","storageBucket":"web-frameworks-e2e.firebasestorage.app","authDomain":"web-frameworks-e2e.firebaseapp.com","messagingSenderId":"1087080420931","apiKey":"AIzaSyCgZEulMefCoYike_SESqhPW0dTwo5nWxs"}} \ No newline at end of file diff --git a/packages/util/autoinit_env.mjs b/packages/util/autoinit_env.mjs deleted file mode 100644 index cee16596840..00000000000 --- a/packages/util/autoinit_env.mjs +++ /dev/null @@ -1 +0,0 @@ -export default {"config":{"projectId":"web-frameworks-e2e","appId":"1:1087080420931:web:a3151cc6bccd2e1aa1a6f6","databaseURL":"https://web-frameworks-e2e-default-rtdb.firebaseio.com","storageBucket":"web-frameworks-e2e.firebasestorage.app","authDomain":"web-frameworks-e2e.firebaseapp.com","messagingSenderId":"1087080420931","apiKey":"AIzaSyCgZEulMefCoYike_SESqhPW0dTwo5nWxs"}} \ No newline at end of file diff --git a/packages/util/package.json b/packages/util/package.json index 8004e1053bb..ba81c6f9209 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -52,6 +52,7 @@ "tslib": "^2.1.0" }, "devDependencies": { + "@rollup/plugin-replace": "6.0.2", "rollup": "2.79.2", "rollup-plugin-typescript2": "0.36.0", "typescript": "5.5.4" diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 3aa815b14ff..9250ace5b94 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -80,8 +80,8 @@ getConfigFromEnv().then((partialConfig) => { const defaults = (config || emulatorHosts) ? { config, emulatorHosts } : undefined; return Promise.all([ - writeFile(join(__dirname, "autoinit_env.js"), `module.exports = ${JSON.stringify(defaults)}`), - writeFile(join(__dirname, "autoinit_env.mjs"), `export default ${JSON.stringify(defaults)}`), + writeFile(join(__dirname, "dist", "autoinit_env.js"), `module.exports = ${JSON.stringify(defaults)}`), + writeFile(join(__dirname, "dist", "autoinit_env.mjs"), `export default ${JSON.stringify(defaults)}`), ]); }).then( diff --git a/packages/util/rollup.config.js b/packages/util/rollup.config.js index 9ea28d36298..fb0aae820fa 100644 --- a/packages/util/rollup.config.js +++ b/packages/util/rollup.config.js @@ -16,15 +16,22 @@ */ import typescriptPlugin from 'rollup-plugin-typescript2'; +import replacePlugin from '@rollup/plugin-replace'; import typescript from 'typescript'; import pkg from './package.json'; import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_package_file'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); +const deps = [ + ...Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) + ), + './autoinit_env' +]; -const buildPlugins = [typescriptPlugin({ typescript })]; +const buildPlugins = [ + typescriptPlugin({ typescript }), + replacePlugin({ './src/autoinit_env': '"@firebase/util/autoinit_env"', delimiters: ["'", "'"], preventAssignment: true }) +]; const browserBuilds = [ { diff --git a/packages/util/src/defaults.ts b/packages/util/src/defaults.ts index cecfc3b9896..1ff50bb3f8e 100644 --- a/packages/util/src/defaults.ts +++ b/packages/util/src/defaults.ts @@ -17,8 +17,7 @@ import { base64Decode } from './crypt'; import { getGlobal } from './global'; -// @ts-expect-error -import postinstallDefaults from "@firebase/util/autoinit_env"; +import postinstallDefaults from "./autoinit_env"; /** * Keys for experimental properties on the `FirebaseDefaults` object. diff --git a/yarn.lock b/yarn.lock index 28171de3f6d..bffdd9c7a25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2633,6 +2633,14 @@ is-module "^1.0.0" resolve "^1.22.1" +"@rollup/plugin-replace@6.0.2": + version "6.0.2" + resolved "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz#2f565d312d681e4570ff376c55c5c08eb6f1908d" + integrity sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + magic-string "^0.30.3" + "@rollup/plugin-strip@2.1.0": version "2.1.0" resolved "https://registry.npmjs.org/@rollup/plugin-strip/-/plugin-strip-2.1.0.tgz#04c2d2ccfb2c6b192bb70447fbf26e336379a333" @@ -11233,7 +11241,7 @@ magic-string@^0.25.2, magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.8" -magic-string@^0.30.2, magic-string@~0.30.0: +magic-string@^0.30.2, magic-string@^0.30.3, magic-string@~0.30.0: version "0.30.17" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== From 63b449e7a8af66dec207feaa3ca26da22c62b354 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 14:08:31 -0500 Subject: [PATCH 09/22] Dont use default export --- packages/util/postinstall.js | 7 +++++-- packages/util/rollup.config.js | 1 - packages/util/src/autoinit_env.ts | 4 +--- packages/util/src/defaults.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 9250ace5b94..097e2803294 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -80,8 +80,11 @@ getConfigFromEnv().then((partialConfig) => { const defaults = (config || emulatorHosts) ? { config, emulatorHosts } : undefined; return Promise.all([ - writeFile(join(__dirname, "dist", "autoinit_env.js"), `module.exports = ${JSON.stringify(defaults)}`), - writeFile(join(__dirname, "dist", "autoinit_env.mjs"), `export default ${JSON.stringify(defaults)}`), + writeFile(join(__dirname, "dist", "autoinit_env.js"), `'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.postinstallDefaults = ${JSON.stringify(defaults)};`), + writeFile(join(__dirname, "dist", "autoinit_env.mjs"), `const postinstallDefaults = ${JSON.stringify(defaults)}; +export { postinstallDefaults };`), ]); }).then( diff --git a/packages/util/rollup.config.js b/packages/util/rollup.config.js index fb0aae820fa..1de6ee3665d 100644 --- a/packages/util/rollup.config.js +++ b/packages/util/rollup.config.js @@ -85,7 +85,6 @@ const autoinitBuild = [ output: { file: './dist/autoinit_env.js', format: 'cjs', - exports: 'default', }, plugins: buildPlugins, }, diff --git a/packages/util/src/autoinit_env.ts b/packages/util/src/autoinit_env.ts index 6844fb852d6..d2e03ba0348 100644 --- a/packages/util/src/autoinit_env.ts +++ b/packages/util/src/autoinit_env.ts @@ -1,6 +1,4 @@ import type { FirebaseDefaults } from "./defaults"; // This value is retrieved and hardcoded by the NPM postinstall script -const firebaseDefaultConfig: FirebaseDefaults|undefined = undefined; - -export default firebaseDefaultConfig; +export const postinstallDefaults: FirebaseDefaults|undefined = undefined; diff --git a/packages/util/src/defaults.ts b/packages/util/src/defaults.ts index 1ff50bb3f8e..1039aec788f 100644 --- a/packages/util/src/defaults.ts +++ b/packages/util/src/defaults.ts @@ -17,7 +17,7 @@ import { base64Decode } from './crypt'; import { getGlobal } from './global'; -import postinstallDefaults from "./autoinit_env"; +import { postinstallDefaults } from "./autoinit_env"; /** * Keys for experimental properties on the `FirebaseDefaults` object. From 85b89920ac80b6161440f0be722da7ddd8fae62e Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 14:18:20 -0500 Subject: [PATCH 10/22] Format --- packages/util/postinstall.js | 161 ++++++++++++++++++------------ packages/util/rollup.config.js | 24 +++-- packages/util/src/autoinit_env.ts | 21 +++- packages/util/src/defaults.ts | 2 +- 4 files changed, 130 insertions(+), 78 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 097e2803294..43715d9c691 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -1,93 +1,126 @@ -const { writeFile, readFile } = require("node:fs/promises"); -const { pathToFileURL } = require("node:url"); -const { isAbsolute, join } = require("node:path"); +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { writeFile, readFile } = require('node:fs/promises'); +const { pathToFileURL } = require('node:url'); +const { isAbsolute, join } = require('node:path'); function getConfigFromEnv() { - if (!process.env.FIREBASE_WEBAPP_CONFIG) { - return Promise.resolve(undefined); - } - - // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be - // either a JSON representation of FirebaseOptions or the path to a filename - if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith("{\"")) { - try { - return Promise.resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); - } catch(e) { - console.error("FIREBASE_WEBAPP_CONFIG could not be parsed.\n", e); - return Promise.resolve(undefined); - } + if (!process.env.FIREBASE_WEBAPP_CONFIG) { + return Promise.resolve(undefined); + } + + // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be + // either a JSON representation of FirebaseOptions or the path to a filename + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) { + try { + return Promise.resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); + } catch (e) { + console.error('FIREBASE_WEBAPP_CONFIG could not be parsed.\n', e); + return Promise.resolve(undefined); } + } - const fileName = process.env.FIREBASE_WEBAPP_CONFIG; - const fileURL = pathToFileURL(isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)); - return readFile(fileURL, "utf-8").then((fileContents) => { - try { - return JSON.parse(fileContents); - } catch(e) { - console.error(`Contents of "${fileName}" could not be parsed.\n`, e); - return undefined; - } - }, (e) => { + const fileName = process.env.FIREBASE_WEBAPP_CONFIG; + const fileURL = pathToFileURL( + isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) + ); + return readFile(fileURL, 'utf-8').then( + fileContents => { + try { + return JSON.parse(fileContents); + } catch (e) { console.error(`Contents of "${fileName}" could not be parsed.\n`, e); return undefined; - }); + } + }, + e => { + console.error(`Contents of "${fileName}" could not be parsed.\n`, e); + return undefined; + } + ); } -getConfigFromEnv().then((partialConfig) => { - +getConfigFromEnv() + .then(partialConfig => { if (!partialConfig) { - return undefined; + return undefined; } // In Firebase App Hosting the config provided to the environment variable is up-to-date and // "complete" we should not reach out to the webConfig endpoint to freshen it - if (process.env.X_GOOGLE_TARGET_PLATFORM === "fah") { - return partialConfig; + if (process.env.X_GOOGLE_TARGET_PLATFORM === 'fah') { + return partialConfig; } - const projectId = partialConfig.projectId || "-"; + const projectId = partialConfig.projectId || '-'; const appId = partialConfig.appId; const apiKey = partialConfig.apiKey; if (!appId || !apiKey) { - console.error(`Unable to fetch Firebase config, appId and apiKey are required.`); - return undefined; + console.error( + `Unable to fetch Firebase config, appId and apiKey are required.` + ); + return undefined; } - + return fetch( - `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, - { headers: { "x-goog-api-key": apiKey } } - ).then((response) => { - if (!response.ok) { - console.error(`Unable to fetch Firebase config, API returned ${response.statusText} (${response.status})`); - return undefined; - } - return response.json().then((json) => ({ ...json, apiKey })); + `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, + { headers: { 'x-goog-api-key': apiKey } } + ).then(response => { + if (!response.ok) { + console.error( + `Unable to fetch Firebase config, API returned ${response.statusText} (${response.status})` + ); + return undefined; + } + return response.json().then(json => ({ ...json, apiKey })); }); - -}).then((config) => { - + }) + .then(config => { const emulatorHosts = Object.entries({ - firestore: process.env.FIRESTORE_EMULATOR_HOST, - database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, - storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, - auth: process.env.FIREBASE_AUTH_EMULATOR_HOST, + firestore: process.env.FIRESTORE_EMULATOR_HOST, + database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, + auth: process.env.FIREBASE_AUTH_EMULATOR_HOST }).reduce( - // We want a falsy value if none of the above are defined - (current, [key, value]) => value ? { ...current, [key]: value } : current, - undefined + // We want a falsy value if none of the above are defined + (current, [key, value]) => + value ? { ...current, [key]: value } : current, + undefined ); - + // getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's // truthy—if we've done nothing here, make it falsy. - const defaults = (config || emulatorHosts) ? { config, emulatorHosts } : undefined; + const defaults = + config || emulatorHosts ? { config, emulatorHosts } : undefined; return Promise.all([ - writeFile(join(__dirname, "dist", "autoinit_env.js"), `'use strict'; + writeFile( + join(__dirname, 'dist', 'autoinit_env.js'), + `'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.postinstallDefaults = ${JSON.stringify(defaults)};`), - writeFile(join(__dirname, "dist", "autoinit_env.mjs"), `const postinstallDefaults = ${JSON.stringify(defaults)}; -export { postinstallDefaults };`), +exports.postinstallDefaults = ${JSON.stringify(defaults)};` + ), + writeFile( + join(__dirname, 'dist', 'autoinit_env.mjs'), + `const postinstallDefaults = ${JSON.stringify(defaults)}; +export { postinstallDefaults };` + ) ]); - -}).then( - () => process.exit(0), + }) + .then( () => process.exit(0), -); + () => process.exit(0) + ); diff --git a/packages/util/rollup.config.js b/packages/util/rollup.config.js index 1de6ee3665d..c080936a43b 100644 --- a/packages/util/rollup.config.js +++ b/packages/util/rollup.config.js @@ -22,15 +22,17 @@ import pkg from './package.json'; import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_package_file'; const deps = [ - ...Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) - ), + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), './autoinit_env' ]; const buildPlugins = [ typescriptPlugin({ typescript }), - replacePlugin({ './src/autoinit_env': '"@firebase/util/autoinit_env"', delimiters: ["'", "'"], preventAssignment: true }) + replacePlugin({ + './src/autoinit_env': '"@firebase/util/autoinit_env"', + delimiters: ["'", "'"], + preventAssignment: true + }) ]; const browserBuilds = [ @@ -80,22 +82,22 @@ const nodeBuilds = [ ]; const autoinitBuild = [ - { + { input: './src/autoinit_env.ts', output: { file: './dist/autoinit_env.js', - format: 'cjs', + format: 'cjs' }, - plugins: buildPlugins, + plugins: buildPlugins }, - { + { input: './src/autoinit_env.ts', output: { file: './dist/autoinit_env.mjs', - format: 'es', + format: 'es' }, - plugins: buildPlugins, + plugins: buildPlugins } -] +]; export default [...browserBuilds, ...nodeBuilds, ...autoinitBuild]; diff --git a/packages/util/src/autoinit_env.ts b/packages/util/src/autoinit_env.ts index d2e03ba0348..8b7b9e1fa3c 100644 --- a/packages/util/src/autoinit_env.ts +++ b/packages/util/src/autoinit_env.ts @@ -1,4 +1,21 @@ -import type { FirebaseDefaults } from "./defaults"; +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { FirebaseDefaults } from './defaults'; // This value is retrieved and hardcoded by the NPM postinstall script -export const postinstallDefaults: FirebaseDefaults|undefined = undefined; +export const postinstallDefaults: FirebaseDefaults | undefined = undefined; diff --git a/packages/util/src/defaults.ts b/packages/util/src/defaults.ts index 1039aec788f..4b9fb6373de 100644 --- a/packages/util/src/defaults.ts +++ b/packages/util/src/defaults.ts @@ -17,7 +17,7 @@ import { base64Decode } from './crypt'; import { getGlobal } from './global'; -import { postinstallDefaults } from "./autoinit_env"; +import { postinstallDefaults } from './autoinit_env'; /** * Keys for experimental properties on the `FirebaseDefaults` object. From 9a38e30b40484ea3b7bacc1682cf5be1652016d5 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 14:32:56 -0500 Subject: [PATCH 11/22] Add changeset --- .changeset/tame-parrots-tie.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tame-parrots-tie.md diff --git a/.changeset/tame-parrots-tie.md b/.changeset/tame-parrots-tie.md new file mode 100644 index 00000000000..9e4be1e14b2 --- /dev/null +++ b/.changeset/tame-parrots-tie.md @@ -0,0 +1,6 @@ +--- +'@firebase/util': minor +'firebase': minor +--- + +Add support for the FIREBASE_WEBAPP_CONFIG environment variable at install time. From f247beafb795a5f68f2bb6e09aaa637eb48e8b34 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 14:36:38 -0500 Subject: [PATCH 12/22] Code format env var --- .changeset/tame-parrots-tie.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tame-parrots-tie.md b/.changeset/tame-parrots-tie.md index 9e4be1e14b2..a6b60f83952 100644 --- a/.changeset/tame-parrots-tie.md +++ b/.changeset/tame-parrots-tie.md @@ -3,4 +3,4 @@ 'firebase': minor --- -Add support for the FIREBASE_WEBAPP_CONFIG environment variable at install time. +Add support for the `FIREBASE_WEBAPP_CONFIG` environment variable at install time. From f52116a105f5cd61ed614f13436d978208c635da Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 14:43:19 -0500 Subject: [PATCH 13/22] Error on network failure --- packages/util/postinstall.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 43715d9c691..530df9a2447 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -86,6 +86,9 @@ getConfigFromEnv() return undefined; } return response.json().then(json => ({ ...json, apiKey })); + }, (e) => { + console.error(`Unable to fetch Firebase config\n${e.cause}`); + return undefined; }); }) .then(config => { From f83c02bb5852451f9ecfc8a4ee7b7e0f4a50ff44 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 14:46:51 -0500 Subject: [PATCH 14/22] cleanup postinstall --- packages/util/postinstall.js | 57 +++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 530df9a2447..01648efdd30 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -19,19 +19,19 @@ const { writeFile, readFile } = require('node:fs/promises'); const { pathToFileURL } = require('node:url'); const { isAbsolute, join } = require('node:path'); -function getConfigFromEnv() { +new Promise(resolve => { if (!process.env.FIREBASE_WEBAPP_CONFIG) { - return Promise.resolve(undefined); + return resolve(undefined); } // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be // either a JSON representation of FirebaseOptions or the path to a filename if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) { try { - return Promise.resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); + return resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); } catch (e) { console.error('FIREBASE_WEBAPP_CONFIG could not be parsed.\n', e); - return Promise.resolve(undefined); + return resolve(undefined); } } @@ -39,23 +39,23 @@ function getConfigFromEnv() { const fileURL = pathToFileURL( isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) ); - return readFile(fileURL, 'utf-8').then( - fileContents => { - try { - return JSON.parse(fileContents); - } catch (e) { + resolve( + readFile(fileURL, 'utf-8').then( + fileContents => { + try { + return JSON.parse(fileContents); + } catch (e) { + console.error(`Contents of "${fileName}" could not be parsed.\n`, e); + return undefined; + } + }, + e => { console.error(`Contents of "${fileName}" could not be parsed.\n`, e); return undefined; } - }, - e => { - console.error(`Contents of "${fileName}" could not be parsed.\n`, e); - return undefined; - } + ) ); -} - -getConfigFromEnv() +}) .then(partialConfig => { if (!partialConfig) { return undefined; @@ -78,18 +78,21 @@ getConfigFromEnv() return fetch( `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, { headers: { 'x-goog-api-key': apiKey } } - ).then(response => { - if (!response.ok) { - console.error( - `Unable to fetch Firebase config, API returned ${response.statusText} (${response.status})` - ); + ).then( + response => { + if (!response.ok) { + console.error( + `Unable to fetch Firebase config, API returned ${response.statusText} (${response.status})` + ); + return undefined; + } + return response.json().then(json => ({ ...json, apiKey })); + }, + e => { + console.error(`Unable to fetch Firebase config\n${e.cause}`); return undefined; } - return response.json().then(json => ({ ...json, apiKey })); - }, (e) => { - console.error(`Unable to fetch Firebase config\n${e.cause}`); - return undefined; - }); + ); }) .then(config => { const emulatorHosts = Object.entries({ From a4a09cff8bd23e68df13ff7af8dbbe77c5435cf2 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 15:34:28 -0500 Subject: [PATCH 15/22] Fix subtle break, more guardrails to ensure fail open --- packages/util/postinstall.js | 215 +++++++++++++++++++---------------- 1 file changed, 116 insertions(+), 99 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 01648efdd30..136f3f27c7a 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -15,118 +15,135 @@ * limitations under the License. */ -const { writeFile, readFile } = require('node:fs/promises'); -const { pathToFileURL } = require('node:url'); -const { isAbsolute, join } = require('node:path'); +try { + const { writeFile, readFile } = require('node:fs/promises'); + const { pathToFileURL } = require('node:url'); + const { isAbsolute, join } = require('node:path'); -new Promise(resolve => { - if (!process.env.FIREBASE_WEBAPP_CONFIG) { - return resolve(undefined); - } - - // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be - // either a JSON representation of FirebaseOptions or the path to a filename - if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) { - try { - return resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); - } catch (e) { - console.error('FIREBASE_WEBAPP_CONFIG could not be parsed.\n', e); + new Promise(resolve => { + if (!process.env.FIREBASE_WEBAPP_CONFIG) { return resolve(undefined); } - } - const fileName = process.env.FIREBASE_WEBAPP_CONFIG; - const fileURL = pathToFileURL( - isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) - ); - resolve( - readFile(fileURL, 'utf-8').then( - fileContents => { - try { - return JSON.parse(fileContents); - } catch (e) { - console.error(`Contents of "${fileName}" could not be parsed.\n`, e); + // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be + // either a JSON representation of FirebaseOptions or the path to a filename + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) { + try { + return resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); + } catch (e) { + console.warn( + 'FIREBASE_WEBAPP_CONFIG could not be parsed, ignoring.\n', + e + ); + return resolve(undefined); + } + } + + const fileName = process.env.FIREBASE_WEBAPP_CONFIG; + const fileURL = pathToFileURL( + isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) + ); + resolve( + readFile(fileURL, 'utf-8').then( + fileContents => { + try { + return JSON.parse(fileContents); + } catch (e) { + console.warn( + `Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`, + e + ); + return undefined; + } + }, + e => { + console.warn( + `Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`, + e + ); return undefined; } - }, - e => { - console.error(`Contents of "${fileName}" could not be parsed.\n`, e); + ) + ); + }) + .then(partialConfig => { + if (!partialConfig) { + return undefined; + } + // In Firebase App Hosting the config provided to the environment variable is up-to-date and + // "complete" we should not reach out to the webConfig endpoint to freshen it + if (process.env.X_GOOGLE_TARGET_PLATFORM === 'fah') { + return partialConfig; + } + const projectId = partialConfig.projectId || '-'; + const appId = partialConfig.appId; + const apiKey = partialConfig.apiKey; + if (!appId || !apiKey) { + console.warn( + `Unable to fetch Firebase config, appId and apiKey are required, ignoring FIREBASE_WEBAPP_CONFIG.` + ); return undefined; } - ) - ); -}) - .then(partialConfig => { - if (!partialConfig) { - return undefined; - } - // In Firebase App Hosting the config provided to the environment variable is up-to-date and - // "complete" we should not reach out to the webConfig endpoint to freshen it - if (process.env.X_GOOGLE_TARGET_PLATFORM === 'fah') { - return partialConfig; - } - const projectId = partialConfig.projectId || '-'; - const appId = partialConfig.appId; - const apiKey = partialConfig.apiKey; - if (!appId || !apiKey) { - console.error( - `Unable to fetch Firebase config, appId and apiKey are required.` - ); - return undefined; - } - return fetch( - `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`, - { headers: { 'x-goog-api-key': apiKey } } - ).then( - response => { - if (!response.ok) { - console.error( - `Unable to fetch Firebase config, API returned ${response.statusText} (${response.status})` + const url = `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`; + return fetch(url, { headers: { 'x-goog-api-key': apiKey } }).then( + response => { + if (!response.ok) { + console.warn( + `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.` + ); + console.warn( + `${url} returned ${response.statusText} (${response.status})` + ); + return undefined; + } + return response.json().then(json => ({ ...json, apiKey })); + }, + e => { + console.warn( + `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.\n${e.cause}` ); return undefined; } - return response.json().then(json => ({ ...json, apiKey })); - }, + ); + }) + .then(config => { + const emulatorHosts = { + firestore: process.env.FIRESTORE_EMULATOR_HOST, + database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, + auth: process.env.FIREBASE_AUTH_EMULATOR_HOST + }; + + const defaults = config && { config, emulatorHosts }; + + return Promise.all([ + writeFile( + join(__dirname, 'dist', 'autoinit_env.js'), + `'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.postinstallDefaults = ${JSON.stringify(defaults)};` + ), + writeFile( + join(__dirname, 'dist', 'autoinit_env.mjs'), + `const postinstallDefaults = ${JSON.stringify(defaults)}; + export { postinstallDefaults };` + ) + ]); + }) + .then( + () => process.exit(0), e => { - console.error(`Unable to fetch Firebase config\n${e.cause}`); - return undefined; + console.warn( + 'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG.\n', + e + ); + process.exit(0); } ); - }) - .then(config => { - const emulatorHosts = Object.entries({ - firestore: process.env.FIRESTORE_EMULATOR_HOST, - database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, - storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, - auth: process.env.FIREBASE_AUTH_EMULATOR_HOST - }).reduce( - // We want a falsy value if none of the above are defined - (current, [key, value]) => - value ? { ...current, [key]: value } : current, - undefined - ); - - // getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's - // truthy—if we've done nothing here, make it falsy. - const defaults = - config || emulatorHosts ? { config, emulatorHosts } : undefined; - - return Promise.all([ - writeFile( - join(__dirname, 'dist', 'autoinit_env.js'), - `'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -exports.postinstallDefaults = ${JSON.stringify(defaults)};` - ), - writeFile( - join(__dirname, 'dist', 'autoinit_env.mjs'), - `const postinstallDefaults = ${JSON.stringify(defaults)}; -export { postinstallDefaults };` - ) - ]); - }) - .then( - () => process.exit(0), - () => process.exit(0) +} catch (e) { + console.warn( + 'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG\n', + e ); +} From 2c6344c54f13c312fb92b0199b786b0c372c71ce Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 15:43:42 -0500 Subject: [PATCH 16/22] Handle demo- projects --- packages/util/postinstall.js | 194 +++++++++++++++++------------------ 1 file changed, 94 insertions(+), 100 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 136f3f27c7a..97d6641a6ef 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -15,99 +15,100 @@ * limitations under the License. */ -try { - const { writeFile, readFile } = require('node:fs/promises'); - const { pathToFileURL } = require('node:url'); - const { isAbsolute, join } = require('node:path'); +const { writeFile, readFile } = require('node:fs/promises'); +const { pathToFileURL } = require('node:url'); +const { isAbsolute, join } = require('node:path'); - new Promise(resolve => { - if (!process.env.FIREBASE_WEBAPP_CONFIG) { - return resolve(undefined); - } +async function getPartialConfig() { + if (!process.env.FIREBASE_WEBAPP_CONFIG) { + return undefined; + } - // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be - // either a JSON representation of FirebaseOptions or the path to a filename - if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) { - try { - return resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG)); - } catch (e) { - console.warn( - 'FIREBASE_WEBAPP_CONFIG could not be parsed, ignoring.\n', - e - ); - return resolve(undefined); - } + // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be + // either a JSON representation of FirebaseOptions or the path to a filename + if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) { + try { + return JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); + } catch (e) { + console.warn( + 'FIREBASE_WEBAPP_CONFIG could not be parsed, ignoring.\n', + e + ); + return undefined; } + } - const fileName = process.env.FIREBASE_WEBAPP_CONFIG; - const fileURL = pathToFileURL( - isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) + const fileName = process.env.FIREBASE_WEBAPP_CONFIG; + const fileURL = pathToFileURL( + isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) + ); + try { + const fileContents = await readFile(fileURL, 'utf-8'); + return JSON.parse(fileContents); + } catch (e) { + console.warn( + `Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`, + e ); - resolve( - readFile(fileURL, 'utf-8').then( - fileContents => { - try { - return JSON.parse(fileContents); - } catch (e) { - console.warn( - `Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`, - e - ); - return undefined; - } - }, - e => { - console.warn( - `Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`, - e - ); - return undefined; - } - ) + return undefined; + } +} + +async function getFullConfig(partialConfig) { + if (!partialConfig) { + return undefined; + } + // In Firebase App Hosting the config provided to the environment variable is up-to-date and + // "complete" we should not reach out to the webConfig endpoint to freshen it + if (process.env.X_GOOGLE_TARGET_PLATFORM === 'fah') { + return partialConfig; + } + const projectId = partialConfig.projectId || '-'; + // If the projectId starts with demo- this is an demo project from the firebase emulators + // treat the config as whole + if (projectId.startsWith('demo-')) { + return partialConfig; + } + const appId = partialConfig.appId; + const apiKey = partialConfig.apiKey; + if (!appId || !apiKey) { + console.warn( + `Unable to fetch Firebase config, appId and apiKey are required, ignoring FIREBASE_WEBAPP_CONFIG.` ); - }) - .then(partialConfig => { - if (!partialConfig) { - return undefined; - } - // In Firebase App Hosting the config provided to the environment variable is up-to-date and - // "complete" we should not reach out to the webConfig endpoint to freshen it - if (process.env.X_GOOGLE_TARGET_PLATFORM === 'fah') { - return partialConfig; - } - const projectId = partialConfig.projectId || '-'; - const appId = partialConfig.appId; - const apiKey = partialConfig.apiKey; - if (!appId || !apiKey) { - console.warn( - `Unable to fetch Firebase config, appId and apiKey are required, ignoring FIREBASE_WEBAPP_CONFIG.` - ); - return undefined; - } + return undefined; + } - const url = `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`; - return fetch(url, { headers: { 'x-goog-api-key': apiKey } }).then( - response => { - if (!response.ok) { - console.warn( - `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.` - ); - console.warn( - `${url} returned ${response.statusText} (${response.status})` - ); - return undefined; - } - return response.json().then(json => ({ ...json, apiKey })); - }, - e => { - console.warn( - `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.\n${e.cause}` - ); - return undefined; - } + const url = `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`; + try { + const response = await fetch(url, { + headers: { 'x-goog-api-key': apiKey } + }); + if (!response.ok) { + console.warn( + `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.` + ); + console.warn( + `${url} returned ${response.statusText} (${response.status})` ); - }) - .then(config => { + try { + console.warn((await response.json()).error.message); + } catch (e) {} + return undefined; + } + const json = await response.json(); + return { ...json, apiKey }; + } catch (e) { + console.warn( + `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.\n`, + e + ); + } +} + +try { + getPartialConfig() + .then(getFullConfig) + .then(async config => { const emulatorHosts = { firestore: process.env.FIRESTORE_EMULATOR_HOST, database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, @@ -117,33 +118,26 @@ try { const defaults = config && { config, emulatorHosts }; - return Promise.all([ + await Promise.all([ writeFile( join(__dirname, 'dist', 'autoinit_env.js'), `'use strict'; - Object.defineProperty(exports, '__esModule', { value: true }); - exports.postinstallDefaults = ${JSON.stringify(defaults)};` +Object.defineProperty(exports, '__esModule', { value: true }); +exports.postinstallDefaults = ${JSON.stringify(defaults)};` ), writeFile( join(__dirname, 'dist', 'autoinit_env.mjs'), `const postinstallDefaults = ${JSON.stringify(defaults)}; - export { postinstallDefaults };` +export { postinstallDefaults };` ) ]); - }) - .then( - () => process.exit(0), - e => { - console.warn( - 'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG.\n', - e - ); - process.exit(0); - } - ); + + process.exit(0); + }); } catch (e) { console.warn( 'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG\n', e ); + process.exit(0); } From 641203d8c81c44f65d46c3982810bf494f5e7af2 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 16:16:28 -0500 Subject: [PATCH 17/22] Return undefined --- packages/util/postinstall.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 97d6641a6ef..19d725be708 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -102,6 +102,7 @@ async function getFullConfig(partialConfig) { `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.\n`, e ); + return undefined; } } From df72b14afd27199ff23f1dcfd69ff22e8cea9976 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 16:27:57 -0500 Subject: [PATCH 18/22] Add catch to file saves --- packages/util/postinstall.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 19d725be708..208d45513ff 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -125,12 +125,16 @@ try { `'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.postinstallDefaults = ${JSON.stringify(defaults)};` - ), + ).catch(() => { + console.warn('Unable to save FIREBASE_WEBAPP_CONFIG to CJS entry.'); + }), writeFile( join(__dirname, 'dist', 'autoinit_env.mjs'), `const postinstallDefaults = ${JSON.stringify(defaults)}; export { postinstallDefaults };` - ) + ).catch(() => { + console.warn('Unable to save FIREBASE_WEBAPP_CONFIG to ESM entry.'); + }) ]); process.exit(0); From 10f959bd08e0744c7d70f4feff26c5ec4c36c476 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 16:35:51 -0500 Subject: [PATCH 19/22] Dry up the catches --- packages/util/postinstall.js | 69 ++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index 208d45513ff..f30a52b2e21 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -42,6 +42,7 @@ async function getPartialConfig() { const fileURL = pathToFileURL( isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) ); + try { const fileContents = await readFile(fileURL, 'utf-8'); return JSON.parse(fileContents); @@ -79,6 +80,7 @@ async function getFullConfig(partialConfig) { } const url = `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`; + try { const response = await fetch(url, { headers: { 'x-goog-api-key': apiKey } @@ -106,43 +108,42 @@ async function getFullConfig(partialConfig) { } } -try { - getPartialConfig() - .then(getFullConfig) - .then(async config => { - const emulatorHosts = { - firestore: process.env.FIRESTORE_EMULATOR_HOST, - database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, - storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, - auth: process.env.FIREBASE_AUTH_EMULATOR_HOST - }; +function handleUnexpectedError(e) { + console.warn( + 'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG.' + ); + console.warn(e); + process.exit(0); +} + +getPartialConfig() + .catch(handleUnexpectedError) + .then(getFullConfig) + .catch(handleUnexpectedError) + .then(async config => { + const emulatorHosts = { + firestore: process.env.FIRESTORE_EMULATOR_HOST, + database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, + auth: process.env.FIREBASE_AUTH_EMULATOR_HOST + }; - const defaults = config && { config, emulatorHosts }; + const defaults = config && { config, emulatorHosts }; - await Promise.all([ - writeFile( - join(__dirname, 'dist', 'autoinit_env.js'), - `'use strict'; + await Promise.all([ + writeFile( + join(__dirname, 'dist', 'autoinit_env.js'), + `'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.postinstallDefaults = ${JSON.stringify(defaults)};` - ).catch(() => { - console.warn('Unable to save FIREBASE_WEBAPP_CONFIG to CJS entry.'); - }), - writeFile( - join(__dirname, 'dist', 'autoinit_env.mjs'), - `const postinstallDefaults = ${JSON.stringify(defaults)}; + ), + writeFile( + join(__dirname, 'dist', 'autoinit_env.mjs'), + `const postinstallDefaults = ${JSON.stringify(defaults)}; export { postinstallDefaults };` - ).catch(() => { - console.warn('Unable to save FIREBASE_WEBAPP_CONFIG to ESM entry.'); - }) - ]); + ) + ]); - process.exit(0); - }); -} catch (e) { - console.warn( - 'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG\n', - e - ); - process.exit(0); -} + process.exit(0); + }) + .catch(handleUnexpectedError); From 7955cc8ff11a0da8dfa0206187eaea3fa6c0d70b Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 21:23:17 -0500 Subject: [PATCH 20/22] No seperate export, better naming --- packages/util/package.json | 5 --- packages/util/postinstall.js | 10 +++--- packages/util/rollup.config.js | 35 +++++++++++-------- packages/util/src/defaults.ts | 4 +-- .../src/{autoinit_env.ts => postinstall.ts} | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) rename packages/util/src/{autoinit_env.ts => postinstall.ts} (88%) diff --git a/packages/util/package.json b/packages/util/package.json index ba81c6f9209..c2852958d80 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -19,11 +19,6 @@ }, "default": "./dist/index.esm2017.js" }, - "./autoinit_env": { - "require": "./dist/autoinit_env.js", - "import": "./dist/autoinit_env.mjs", - "default": "./dist/autoinit_env.mjs" - }, "./package.json": "./package.json" }, "files": [ diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index f30a52b2e21..eed5f229f32 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -132,15 +132,15 @@ getPartialConfig() await Promise.all([ writeFile( - join(__dirname, 'dist', 'autoinit_env.js'), + join(__dirname, 'dist', 'postinstall.js'), `'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.postinstallDefaults = ${JSON.stringify(defaults)};` +exports.getDefaultsFromPostinstall = () => (${JSON.stringify(defaults)});` ), writeFile( - join(__dirname, 'dist', 'autoinit_env.mjs'), - `const postinstallDefaults = ${JSON.stringify(defaults)}; -export { postinstallDefaults };` + join(__dirname, 'dist', 'postinstall.mjs'), + `const getDefaultsFromPostinstall = () => (${JSON.stringify(defaults)}); +export { getDefaultsFromPostinstall };` ) ]); diff --git a/packages/util/rollup.config.js b/packages/util/rollup.config.js index c080936a43b..57750f3d518 100644 --- a/packages/util/rollup.config.js +++ b/packages/util/rollup.config.js @@ -23,17 +23,18 @@ import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_pa const deps = [ ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - './autoinit_env' + './postinstall' ]; -const buildPlugins = [ - typescriptPlugin({ typescript }), - replacePlugin({ - './src/autoinit_env': '"@firebase/util/autoinit_env"', +const buildPlugins = [typescriptPlugin({ typescript })]; + +function replaceSrcPostinstallWith(path) { + return replacePlugin({ + './src/postinstall': `'${path}'`, delimiters: ["'", "'"], preventAssignment: true - }) -]; + }); +} const browserBuilds = [ { @@ -43,7 +44,7 @@ const browserBuilds = [ format: 'es', sourcemap: true }, - plugins: buildPlugins, + plugins: [...buildPlugins, replaceSrcPostinstallWith('./postinstall.mjs')], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) }, { @@ -53,7 +54,7 @@ const browserBuilds = [ format: 'cjs', sourcemap: true }, - plugins: buildPlugins, + plugins: [...buildPlugins, replaceSrcPostinstallWith('./postinstall.js')], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } ]; @@ -66,7 +67,7 @@ const nodeBuilds = [ format: 'cjs', sourcemap: true }, - plugins: buildPlugins, + plugins: [...buildPlugins, replaceSrcPostinstallWith('./postinstall.js')], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) }, { @@ -76,24 +77,28 @@ const nodeBuilds = [ format: 'es', sourcemap: true }, - plugins: [...buildPlugins, emitModulePackageFile()], + plugins: [ + ...buildPlugins, + emitModulePackageFile(), + replaceSrcPostinstallWith('../postinstall.mjs') + ], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } ]; const autoinitBuild = [ { - input: './src/autoinit_env.ts', + input: './src/postinstall.ts', output: { - file: './dist/autoinit_env.js', + file: './dist/postinstall.js', format: 'cjs' }, plugins: buildPlugins }, { - input: './src/autoinit_env.ts', + input: './src/postinstall.ts', output: { - file: './dist/autoinit_env.mjs', + file: './dist/postinstall.mjs', format: 'es' }, plugins: buildPlugins diff --git a/packages/util/src/defaults.ts b/packages/util/src/defaults.ts index 4b9fb6373de..d7882e152dd 100644 --- a/packages/util/src/defaults.ts +++ b/packages/util/src/defaults.ts @@ -17,7 +17,7 @@ import { base64Decode } from './crypt'; import { getGlobal } from './global'; -import { postinstallDefaults } from './autoinit_env'; +import { getDefaultsFromPostinstall } from './postinstall'; /** * Keys for experimental properties on the `FirebaseDefaults` object. @@ -101,7 +101,7 @@ const getDefaultsFromCookie = (): FirebaseDefaults | undefined => { export const getDefaults = (): FirebaseDefaults | undefined => { try { return ( - postinstallDefaults || + getDefaultsFromPostinstall() || getDefaultsFromGlobal() || getDefaultsFromEnvVariable() || getDefaultsFromCookie() diff --git a/packages/util/src/autoinit_env.ts b/packages/util/src/postinstall.ts similarity index 88% rename from packages/util/src/autoinit_env.ts rename to packages/util/src/postinstall.ts index 8b7b9e1fa3c..4e58f03df52 100644 --- a/packages/util/src/autoinit_env.ts +++ b/packages/util/src/postinstall.ts @@ -18,4 +18,4 @@ import type { FirebaseDefaults } from './defaults'; // This value is retrieved and hardcoded by the NPM postinstall script -export const postinstallDefaults: FirebaseDefaults | undefined = undefined; +export const getDefaultsFromPostinstall: () => FirebaseDefaults | undefined = () => undefined; From 939af36c2facbfdb7d9c0219a05bae3e99ee926e Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 25 Feb 2025 21:33:09 -0500 Subject: [PATCH 21/22] Formattting --- packages/util/src/postinstall.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/util/src/postinstall.ts b/packages/util/src/postinstall.ts index 4e58f03df52..f16f60d5b49 100644 --- a/packages/util/src/postinstall.ts +++ b/packages/util/src/postinstall.ts @@ -18,4 +18,6 @@ import type { FirebaseDefaults } from './defaults'; // This value is retrieved and hardcoded by the NPM postinstall script -export const getDefaultsFromPostinstall: () => FirebaseDefaults | undefined = () => undefined; +export const getDefaultsFromPostinstall: () => + | FirebaseDefaults + | undefined = () => undefined; From 15a35e931a1b9dd8d502db21d8dd0ffd8e2fb982 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Wed, 26 Feb 2025 10:35:57 -0500 Subject: [PATCH 22/22] Cleanup error messages, address feedback --- packages/util/postinstall.js | 46 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/util/postinstall.js b/packages/util/postinstall.js index eed5f229f32..6987d1316da 100644 --- a/packages/util/postinstall.js +++ b/packages/util/postinstall.js @@ -19,28 +19,31 @@ const { writeFile, readFile } = require('node:fs/promises'); const { pathToFileURL } = require('node:url'); const { isAbsolute, join } = require('node:path'); +const ENV_VARIABLE = 'FIREBASE_WEBAPP_CONFIG'; + async function getPartialConfig() { - if (!process.env.FIREBASE_WEBAPP_CONFIG) { + const envVariable = process.env[ENV_VARIABLE]?.trim(); + + if (!envVariable) { return undefined; } // Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be // either a JSON representation of FirebaseOptions or the path to a filename - if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) { + if (envVariable.startsWith('{"')) { try { - return JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG); + return JSON.parse(envVariable); } catch (e) { console.warn( - 'FIREBASE_WEBAPP_CONFIG could not be parsed, ignoring.\n', + `JSON payload in \$${ENV_VARIABLE} could not be parsed, ignoring.\n`, e ); return undefined; } } - const fileName = process.env.FIREBASE_WEBAPP_CONFIG; const fileURL = pathToFileURL( - isAbsolute(fileName) ? fileName : join(process.cwd(), fileName) + isAbsolute(envVariable) ? envVariable : join(process.cwd(), envVariable) ); try { @@ -48,14 +51,14 @@ async function getPartialConfig() { return JSON.parse(fileContents); } catch (e) { console.warn( - `Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`, + `Contents of "${envVariable}" could not be parsed, ignoring \$${ENV_VARIABLE}.\n`, e ); return undefined; } } -async function getFullConfig(partialConfig) { +async function getFinalConfig(partialConfig) { if (!partialConfig) { return undefined; } @@ -74,7 +77,7 @@ async function getFullConfig(partialConfig) { const apiKey = partialConfig.apiKey; if (!appId || !apiKey) { console.warn( - `Unable to fetch Firebase config, appId and apiKey are required, ignoring FIREBASE_WEBAPP_CONFIG.` + `Unable to fetch Firebase config, appId and apiKey are required, ignoring \$${ENV_VARIABLE}.` ); return undefined; } @@ -87,7 +90,7 @@ async function getFullConfig(partialConfig) { }); if (!response.ok) { console.warn( - `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.` + `Unable to fetch Firebase config, ignoring \$${ENV_VARIABLE}.` ); console.warn( `${url} returned ${response.statusText} (${response.status})` @@ -101,7 +104,7 @@ async function getFullConfig(partialConfig) { return { ...json, apiKey }; } catch (e) { console.warn( - `Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.\n`, + `Unable to fetch Firebase config, ignoring \$${ENV_VARIABLE}.\n`, e ); return undefined; @@ -110,7 +113,7 @@ async function getFullConfig(partialConfig) { function handleUnexpectedError(e) { console.warn( - 'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG.' + `Unexpected error encountered in @firebase/util postinstall script, ignoring \$${ENV_VARIABLE}.` ); console.warn(e); process.exit(0); @@ -118,18 +121,19 @@ function handleUnexpectedError(e) { getPartialConfig() .catch(handleUnexpectedError) - .then(getFullConfig) + .then(getFinalConfig) .catch(handleUnexpectedError) - .then(async config => { - const emulatorHosts = { - firestore: process.env.FIRESTORE_EMULATOR_HOST, - database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, - storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, - auth: process.env.FIREBASE_AUTH_EMULATOR_HOST + .then(async finalConfig => { + const defaults = finalConfig && { + config: finalConfig, + emulatorHosts: { + firestore: process.env.FIRESTORE_EMULATOR_HOST, + database: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST, + auth: process.env.FIREBASE_AUTH_EMULATOR_HOST + } }; - const defaults = config && { config, emulatorHosts }; - await Promise.all([ writeFile( join(__dirname, 'dist', 'postinstall.js'),