Skip to content

Commit 76a17f0

Browse files
authored
Firebase App Auto Init (#164)
Add code to read default options from the FIREBASE_CONFIG environment variable. (and make the options param to initializeApp optional)
1 parent c6dfea5 commit 76a17f0

11 files changed

+249
-17
lines changed

src/firebase-app.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import {InstanceId} from './instance-id/instance-id';
3636
*/
3737
export type AppHook = (event: string, app: FirebaseApp) => void;
3838

39-
4039
/**
4140
* Type representing the options object passed into initializeApp().
4241
*/
@@ -260,7 +259,6 @@ export class FirebaseApp {
260259
if (!hasCredential) {
261260
errorMessage = 'Options must be an object containing at least a "credential" property.';
262261
}
263-
264262
const credential = this.options_.credential;
265263
if (typeof credential !== 'object' || credential === null || typeof credential.getAccessToken !== 'function') {
266264
errorMessage = 'The "credential" property must be an object which implements the Credential interface.';

src/firebase-namespace.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
import fs = require('fs');
1718
import {deepExtend} from './utils/deep-copy';
1819
import {AppErrorCodes, FirebaseAppError} from './utils/error';
1920
import {AppHook, FirebaseApp, FirebaseAppOptions} from './firebase-app';
@@ -32,8 +33,18 @@ import {Database} from '@firebase/database';
3233
import {Firestore} from '@google-cloud/firestore';
3334
import {InstanceId} from './instance-id/instance-id';
3435

36+
import * as validator from './utils/validator';
37+
3538
const DEFAULT_APP_NAME = '[DEFAULT]';
3639

40+
/**
41+
* Constant holding the environment variable name with the default config.
42+
* If the environmet variable contains a string that starts with '{' it will be parsed as JSON,
43+
* otherwise it will be assumed to be pointing to a file.
44+
*/
45+
export const FIREBASE_CONFIG_VAR: string = 'FIREBASE_CONFIG';
46+
47+
3748
let globalAppDefaultCred: ApplicationDefaultCredential;
3849
let globalCertCreds: { [key: string]: CertCredential } = {};
3950
let globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {};
@@ -59,12 +70,20 @@ export class FirebaseNamespaceInternals {
5970
/**
6071
* Initializes the FirebaseApp instance.
6172
*
62-
* @param {FirebaseAppOptions} options Options for the FirebaseApp instance.
73+
* @param {FirebaseAppOptions} options Optional options for the FirebaseApp instance. If none present
74+
* will try to initialize from the FIREBASE_CONFIG environment variable.
75+
* If the environmet variable contains a string that starts with '{'
76+
* it will be parsed as JSON,
77+
* otherwise it will be assumed to be pointing to a file.
6378
* @param {string} [appName] Optional name of the FirebaseApp instance.
6479
*
6580
* @return {FirebaseApp} A new FirebaseApp instance.
6681
*/
67-
public initializeApp(options: FirebaseAppOptions, appName = DEFAULT_APP_NAME): FirebaseApp {
82+
public initializeApp(options?: FirebaseAppOptions, appName = DEFAULT_APP_NAME): FirebaseApp {
83+
if (typeof options === 'undefined') {
84+
options = this.loadOptionsFromEnvVar();
85+
options.credential = new ApplicationDefaultCredential();
86+
}
6887
if (typeof appName !== 'string' || appName === '') {
6988
throw new FirebaseAppError(
7089
AppErrorCodes.INVALID_APP_NAME,
@@ -227,6 +246,36 @@ export class FirebaseNamespaceInternals {
227246
}
228247
});
229248
}
249+
250+
/**
251+
* Parse the file pointed to by the FIREBASE_CONFIG_VAR, if it exists.
252+
* Or if the FIREBASE_CONFIG_ENV contains a valid JSON object, parse it directly.
253+
* If the environmet variable contains a string that starts with '{' it will be parsed as JSON,
254+
* otherwise it will be assumed to be pointing to a file.
255+
*/
256+
private loadOptionsFromEnvVar(): FirebaseAppOptions {
257+
let config = process.env[FIREBASE_CONFIG_VAR];
258+
if (!validator.isNonEmptyString(config)) {
259+
return {};
260+
}
261+
try {
262+
let contents;
263+
if (config.startsWith('{')) {
264+
// Assume json object.
265+
contents = config;
266+
} else {
267+
// Assume filename.
268+
contents = fs.readFileSync(config, 'utf8');
269+
}
270+
return JSON.parse(contents) as FirebaseAppOptions;
271+
} catch (error) {
272+
// Throw a nicely formed error message if the file contents cannot be parsed
273+
throw new FirebaseAppError(
274+
AppErrorCodes.INVALID_APP_OPTIONS,
275+
'Failed to parse app options file: ' + error,
276+
);
277+
}
278+
}
230279
}
231280

232281

@@ -354,12 +403,15 @@ export class FirebaseNamespace {
354403
/**
355404
* Initializes the FirebaseApp instance.
356405
*
357-
* @param {FirebaseAppOptions} options Options for the FirebaseApp instance.
406+
* @param {FirebaseAppOptions} [options] Optional options for the FirebaseApp instance.
407+
* If none present will try to initialize from the FIREBASE_CONFIG environment variable.
408+
* If the environmet variable contains a string that starts with '{' it will be parsed as JSON,
409+
* otherwise it will be assumed to be pointing to a file.
358410
* @param {string} [appName] Optional name of the FirebaseApp instance.
359411
*
360412
* @return {FirebaseApp} A new FirebaseApp instance.
361413
*/
362-
public initializeApp(options: FirebaseAppOptions, appName?: string): FirebaseApp {
414+
public initializeApp(options?: FirebaseAppOptions, appName?: string): FirebaseApp {
363415
return this.INTERNAL.initializeApp(options, appName);
364416
}
365417

src/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ declare namespace admin {
6060
function storage(app?: admin.app.App): admin.storage.Storage;
6161
function firestore(app?: admin.app.App): admin.firestore.Firestore;
6262
function instanceId(app?: admin.app.App): admin.instanceId.InstanceId;
63-
function initializeApp(options: admin.AppOptions, name?: string): admin.app.App;
63+
function initializeApp(options?: admin.AppOptions, name?: string): admin.app.App;
6464
}
6565

6666
declare namespace admin.app {

test/resources/firebase_config.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"databaseAuthVariableOverride": { "some#key": "some#val" },
3+
"databaseURL": "https://hipster-chat.firebaseio.mock",
4+
"projectId": "hipster-chat-mock",
5+
"storageBucket": "hipster-chat.appspot.mock"
6+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
baaaaad
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"notAValidKeyValue": "The key value here is not valid.",
3+
"projectId": "hipster-chat-mock"
4+
}

test/resources/firebase_config_empty.json

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"databaseURL": "https://hipster-chat.firebaseio.mock",
3+
"projectId": "hipster-chat-mock"
4+
}

test/resources/mocks.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export let serviceName = 'mock-service-name';
4545

4646
export let databaseURL = 'https://databaseName.firebaseio.com';
4747

48+
export let databaseAuthVariableOverride = { 'some#string': 'some#val' };
49+
4850
export let storageBucket = 'bucketName.appspot.com';
4951

5052
export let credential = new CertCredential(path.resolve(__dirname, './mock.key.json'));
@@ -55,6 +57,14 @@ export let appOptions: FirebaseAppOptions = {
5557
storageBucket,
5658
};
5759

60+
export let appOptionsWithOverride: FirebaseAppOptions = {
61+
credential,
62+
databaseAuthVariableOverride,
63+
databaseURL,
64+
storageBucket,
65+
projectId,
66+
};
67+
5868
export let appOptionsNoAuth: FirebaseAppOptions = {
5969
databaseURL,
6070
};
@@ -63,6 +73,11 @@ export let appOptionsNoDatabaseUrl: FirebaseAppOptions = {
6373
credential,
6474
};
6575

76+
export let appOptionsAuthDB: FirebaseAppOptions = {
77+
credential,
78+
databaseURL,
79+
};
80+
6681
export class MockCredential implements Credential {
6782
public getAccessToken(): Promise<GoogleOAuthAccessToken> {
6883
return Promise.resolve({

0 commit comments

Comments
 (0)