mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
App Hosting JS SDK autoinit (#8483)
Adds a `postinstall` step to `@firebase/util` which hardcodes autoinit defaults into `@firebase/util/dist/postinstall.(m)js` for both CJS and ESM variants [go/firestack-autoinit](http://goto.google.com/firestack-autoinit). Hardcoding is important since environment variable substitution doesn't dive into a developer's `node_modules` folder. This gives us a framework agnostic way to inject configuration and is a more robust solution than the _experimental_ autoinit methods provided by Web Frameworks [go/firebase-api-client-autoinit](https://goto.google.com/firebase-api-client-autoinit). Once this lands, we'll backport to Hosting and Functions and aim to deprecate the other autoinit methods.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@firebase/util': minor
|
||||
'firebase': minor
|
||||
---
|
||||
|
||||
Add support for the `FIREBASE_WEBAPP_CONFIG` environment variable at install time.
|
||||
@@ -22,7 +22,8 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"postinstall.js"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
|
||||
@@ -38,13 +39,15 @@
|
||||
"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.js"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"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"
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @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');
|
||||
|
||||
const ENV_VARIABLE = 'FIREBASE_WEBAPP_CONFIG';
|
||||
|
||||
async function getPartialConfig() {
|
||||
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 (envVariable.startsWith('{"')) {
|
||||
try {
|
||||
return JSON.parse(envVariable);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`JSON payload in \$${ENV_VARIABLE} could not be parsed, ignoring.\n`,
|
||||
e
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const fileURL = pathToFileURL(
|
||||
isAbsolute(envVariable) ? envVariable : join(process.cwd(), envVariable)
|
||||
);
|
||||
|
||||
try {
|
||||
const fileContents = await readFile(fileURL, 'utf-8');
|
||||
return JSON.parse(fileContents);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Contents of "${envVariable}" could not be parsed, ignoring \$${ENV_VARIABLE}.\n`,
|
||||
e
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function getFinalConfig(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 \$${ENV_VARIABLE}.`
|
||||
);
|
||||
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 \$${ENV_VARIABLE}.`
|
||||
);
|
||||
console.warn(
|
||||
`${url} returned ${response.statusText} (${response.status})`
|
||||
);
|
||||
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 \$${ENV_VARIABLE}.\n`,
|
||||
e
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function handleUnexpectedError(e) {
|
||||
console.warn(
|
||||
`Unexpected error encountered in @firebase/util postinstall script, ignoring \$${ENV_VARIABLE}.`
|
||||
);
|
||||
console.warn(e);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
getPartialConfig()
|
||||
.catch(handleUnexpectedError)
|
||||
.then(getFinalConfig)
|
||||
.catch(handleUnexpectedError)
|
||||
.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
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
join(__dirname, 'dist', 'postinstall.js'),
|
||||
`'use strict';
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.getDefaultsFromPostinstall = () => (${JSON.stringify(defaults)});`
|
||||
),
|
||||
writeFile(
|
||||
join(__dirname, 'dist', 'postinstall.mjs'),
|
||||
`const getDefaultsFromPostinstall = () => (${JSON.stringify(defaults)});
|
||||
export { getDefaultsFromPostinstall };`
|
||||
)
|
||||
]);
|
||||
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(handleUnexpectedError);
|
||||
@@ -16,16 +16,26 @@
|
||||
*/
|
||||
|
||||
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)),
|
||||
'./postinstall'
|
||||
];
|
||||
|
||||
const buildPlugins = [typescriptPlugin({ typescript })];
|
||||
|
||||
function replaceSrcPostinstallWith(path) {
|
||||
return replacePlugin({
|
||||
'./src/postinstall': `'${path}'`,
|
||||
delimiters: ["'", "'"],
|
||||
preventAssignment: true
|
||||
});
|
||||
}
|
||||
|
||||
const browserBuilds = [
|
||||
{
|
||||
input: 'index.ts',
|
||||
@@ -34,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}/`))
|
||||
},
|
||||
{
|
||||
@@ -44,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}/`))
|
||||
}
|
||||
];
|
||||
@@ -57,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}/`))
|
||||
},
|
||||
{
|
||||
@@ -67,9 +77,32 @@ 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}/`))
|
||||
}
|
||||
];
|
||||
|
||||
export default [...browserBuilds, ...nodeBuilds];
|
||||
const autoinitBuild = [
|
||||
{
|
||||
input: './src/postinstall.ts',
|
||||
output: {
|
||||
file: './dist/postinstall.js',
|
||||
format: 'cjs'
|
||||
},
|
||||
plugins: buildPlugins
|
||||
},
|
||||
{
|
||||
input: './src/postinstall.ts',
|
||||
output: {
|
||||
file: './dist/postinstall.mjs',
|
||||
format: 'es'
|
||||
},
|
||||
plugins: buildPlugins
|
||||
}
|
||||
];
|
||||
|
||||
export default [...browserBuilds, ...nodeBuilds, ...autoinitBuild];
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { base64Decode } from './crypt';
|
||||
import { getGlobal } from './global';
|
||||
import { getDefaultsFromPostinstall } from './postinstall';
|
||||
|
||||
/**
|
||||
* Keys for experimental properties on the `FirebaseDefaults` object.
|
||||
@@ -100,6 +101,7 @@ const getDefaultsFromCookie = (): FirebaseDefaults | undefined => {
|
||||
export const getDefaults = (): FirebaseDefaults | undefined => {
|
||||
try {
|
||||
return (
|
||||
getDefaultsFromPostinstall() ||
|
||||
getDefaultsFromGlobal() ||
|
||||
getDefaultsFromEnvVariable() ||
|
||||
getDefaultsFromCookie()
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @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 getDefaultsFromPostinstall: () =>
|
||||
| FirebaseDefaults
|
||||
| undefined = () => undefined;
|
||||
@@ -2633,6 +2633,14 @@
|
||||
is-module "^1.0.0"
|
||||
resolve "^1.22.1"
|
||||
|
||||
"@rollup/[email protected]":
|
||||
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/[email protected]":
|
||||
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==
|
||||
|
||||
Reference in New Issue
Block a user