Skip to content

Commit a4a09cf

Browse files
committed
Fix subtle break, more guardrails to ensure fail open
1 parent f83c02b commit a4a09cf

File tree

1 file changed

+116
-99
lines changed

1 file changed

+116
-99
lines changed

packages/util/postinstall.js

Lines changed: 116 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -15,118 +15,135 @@
1515
* limitations under the License.
1616
*/
1717

18-
const { writeFile, readFile } = require('node:fs/promises');
19-
const { pathToFileURL } = require('node:url');
20-
const { isAbsolute, join } = require('node:path');
18+
try {
19+
const { writeFile, readFile } = require('node:fs/promises');
20+
const { pathToFileURL } = require('node:url');
21+
const { isAbsolute, join } = require('node:path');
2122

22-
new Promise(resolve => {
23-
if (!process.env.FIREBASE_WEBAPP_CONFIG) {
24-
return resolve(undefined);
25-
}
26-
27-
// Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be
28-
// either a JSON representation of FirebaseOptions or the path to a filename
29-
if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) {
30-
try {
31-
return resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG));
32-
} catch (e) {
33-
console.error('FIREBASE_WEBAPP_CONFIG could not be parsed.\n', e);
23+
new Promise(resolve => {
24+
if (!process.env.FIREBASE_WEBAPP_CONFIG) {
3425
return resolve(undefined);
3526
}
36-
}
3727

38-
const fileName = process.env.FIREBASE_WEBAPP_CONFIG;
39-
const fileURL = pathToFileURL(
40-
isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)
41-
);
42-
resolve(
43-
readFile(fileURL, 'utf-8').then(
44-
fileContents => {
45-
try {
46-
return JSON.parse(fileContents);
47-
} catch (e) {
48-
console.error(`Contents of "${fileName}" could not be parsed.\n`, e);
28+
// Like FIREBASE_CONFIG (admin autoinit) FIREBASE_WEBAPP_CONFIG can be
29+
// either a JSON representation of FirebaseOptions or the path to a filename
30+
if (process.env.FIREBASE_WEBAPP_CONFIG.startsWith('{"')) {
31+
try {
32+
return resolve(JSON.parse(process.env.FIREBASE_WEBAPP_CONFIG));
33+
} catch (e) {
34+
console.warn(
35+
'FIREBASE_WEBAPP_CONFIG could not be parsed, ignoring.\n',
36+
e
37+
);
38+
return resolve(undefined);
39+
}
40+
}
41+
42+
const fileName = process.env.FIREBASE_WEBAPP_CONFIG;
43+
const fileURL = pathToFileURL(
44+
isAbsolute(fileName) ? fileName : join(process.cwd(), fileName)
45+
);
46+
resolve(
47+
readFile(fileURL, 'utf-8').then(
48+
fileContents => {
49+
try {
50+
return JSON.parse(fileContents);
51+
} catch (e) {
52+
console.warn(
53+
`Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`,
54+
e
55+
);
56+
return undefined;
57+
}
58+
},
59+
e => {
60+
console.warn(
61+
`Contents of "${fileName}" could not be parsed, ignoring FIREBASE_WEBAPP_CONFIG.\n`,
62+
e
63+
);
4964
return undefined;
5065
}
51-
},
52-
e => {
53-
console.error(`Contents of "${fileName}" could not be parsed.\n`, e);
66+
)
67+
);
68+
})
69+
.then(partialConfig => {
70+
if (!partialConfig) {
71+
return undefined;
72+
}
73+
// In Firebase App Hosting the config provided to the environment variable is up-to-date and
74+
// "complete" we should not reach out to the webConfig endpoint to freshen it
75+
if (process.env.X_GOOGLE_TARGET_PLATFORM === 'fah') {
76+
return partialConfig;
77+
}
78+
const projectId = partialConfig.projectId || '-';
79+
const appId = partialConfig.appId;
80+
const apiKey = partialConfig.apiKey;
81+
if (!appId || !apiKey) {
82+
console.warn(
83+
`Unable to fetch Firebase config, appId and apiKey are required, ignoring FIREBASE_WEBAPP_CONFIG.`
84+
);
5485
return undefined;
5586
}
56-
)
57-
);
58-
})
59-
.then(partialConfig => {
60-
if (!partialConfig) {
61-
return undefined;
62-
}
63-
// In Firebase App Hosting the config provided to the environment variable is up-to-date and
64-
// "complete" we should not reach out to the webConfig endpoint to freshen it
65-
if (process.env.X_GOOGLE_TARGET_PLATFORM === 'fah') {
66-
return partialConfig;
67-
}
68-
const projectId = partialConfig.projectId || '-';
69-
const appId = partialConfig.appId;
70-
const apiKey = partialConfig.apiKey;
71-
if (!appId || !apiKey) {
72-
console.error(
73-
`Unable to fetch Firebase config, appId and apiKey are required.`
74-
);
75-
return undefined;
76-
}
7787

78-
return fetch(
79-
`https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`,
80-
{ headers: { 'x-goog-api-key': apiKey } }
81-
).then(
82-
response => {
83-
if (!response.ok) {
84-
console.error(
85-
`Unable to fetch Firebase config, API returned ${response.statusText} (${response.status})`
88+
const url = `https://firebase.googleapis.com/v1alpha/projects/${projectId}/apps/${appId}/webConfig`;
89+
return fetch(url, { headers: { 'x-goog-api-key': apiKey } }).then(
90+
response => {
91+
if (!response.ok) {
92+
console.warn(
93+
`Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.`
94+
);
95+
console.warn(
96+
`${url} returned ${response.statusText} (${response.status})`
97+
);
98+
return undefined;
99+
}
100+
return response.json().then(json => ({ ...json, apiKey }));
101+
},
102+
e => {
103+
console.warn(
104+
`Unable to fetch Firebase config, ignoring FIREBASE_WEBAPP_CONFIG.\n${e.cause}`
86105
);
87106
return undefined;
88107
}
89-
return response.json().then(json => ({ ...json, apiKey }));
90-
},
108+
);
109+
})
110+
.then(config => {
111+
const emulatorHosts = {
112+
firestore: process.env.FIRESTORE_EMULATOR_HOST,
113+
database: process.env.FIREBASE_DATABASE_EMULATOR_HOST,
114+
storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST,
115+
auth: process.env.FIREBASE_AUTH_EMULATOR_HOST
116+
};
117+
118+
const defaults = config && { config, emulatorHosts };
119+
120+
return Promise.all([
121+
writeFile(
122+
join(__dirname, 'dist', 'autoinit_env.js'),
123+
`'use strict';
124+
Object.defineProperty(exports, '__esModule', { value: true });
125+
exports.postinstallDefaults = ${JSON.stringify(defaults)};`
126+
),
127+
writeFile(
128+
join(__dirname, 'dist', 'autoinit_env.mjs'),
129+
`const postinstallDefaults = ${JSON.stringify(defaults)};
130+
export { postinstallDefaults };`
131+
)
132+
]);
133+
})
134+
.then(
135+
() => process.exit(0),
91136
e => {
92-
console.error(`Unable to fetch Firebase config\n${e.cause}`);
93-
return undefined;
137+
console.warn(
138+
'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG.\n',
139+
e
140+
);
141+
process.exit(0);
94142
}
95143
);
96-
})
97-
.then(config => {
98-
const emulatorHosts = Object.entries({
99-
firestore: process.env.FIRESTORE_EMULATOR_HOST,
100-
database: process.env.FIREBASE_DATABASE_EMULATOR_HOST,
101-
storage: process.env.FIREBASE_STORAGE_EMULATOR_HOST,
102-
auth: process.env.FIREBASE_AUTH_EMULATOR_HOST
103-
}).reduce(
104-
// We want a falsy value if none of the above are defined
105-
(current, [key, value]) =>
106-
value ? { ...current, [key]: value } : current,
107-
undefined
108-
);
109-
110-
// getDefaults() will use this object, rather than fallback to other autoinit suppliers, if it's
111-
// truthy—if we've done nothing here, make it falsy.
112-
const defaults =
113-
config || emulatorHosts ? { config, emulatorHosts } : undefined;
114-
115-
return Promise.all([
116-
writeFile(
117-
join(__dirname, 'dist', 'autoinit_env.js'),
118-
`'use strict';
119-
Object.defineProperty(exports, '__esModule', { value: true });
120-
exports.postinstallDefaults = ${JSON.stringify(defaults)};`
121-
),
122-
writeFile(
123-
join(__dirname, 'dist', 'autoinit_env.mjs'),
124-
`const postinstallDefaults = ${JSON.stringify(defaults)};
125-
export { postinstallDefaults };`
126-
)
127-
]);
128-
})
129-
.then(
130-
() => process.exit(0),
131-
() => process.exit(0)
144+
} catch (e) {
145+
console.warn(
146+
'Unexpected error encountered in @firebase/util postinstall script, ignoring FIREBASE_WEBAPP_CONFIG\n',
147+
e
132148
);
149+
}

0 commit comments

Comments
 (0)