From 1216622f54eebb4e5f3b6e91121bc6b8a0f58e3a Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 19 Sep 2023 11:31:06 -0400 Subject: [PATCH 01/41] initial pass of additions --- common/api-review/app.api.md | 7 ++ packages/app-types/index.d.ts | 54 +++++++++++ packages/app/src/api.ts | 115 ++++++++++++++++++++++++ packages/app/src/firebaseApp.ts | 12 +-- packages/app/src/firebaseServerApp.ts | 125 ++++++++++++++++++++++++++ packages/app/src/public-types.ts | 34 +++++++ 6 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 packages/app/src/firebaseServerApp.ts diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 52f134dac16..00143e52120 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -96,6 +96,13 @@ export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSett // @public export function initializeApp(): FirebaseApp; +// @public +export function initializeServerAppInstance(options: FirebaseOptions, config?: + FirebaseAppSettings): FirebaseServerApp; + +// @public +export function initializeServerAppInstance(): FirebaseServerApp; + // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 72b8fa68f9d..242f5211966 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -32,6 +32,11 @@ export interface FirebaseAppConfig { automaticDataCollectionEnabled?: boolean; } +export interface FirebaseServerAppConfig extends FirebaseAppConfig { + headers: object; + setCookieCallback?: (name: string, value: string) => void): FirebaseServerApp; +} + export class FirebaseApp { /** * The (read-only) name (identifier) for this App. '[DEFAULT]' is the default @@ -55,6 +60,19 @@ export class FirebaseApp { delete(): Promise; } +export class FirebaseServerApp extends FirebaseApp { + /** + * The (read-only) name (identifier) for this App. '[DEFAULT]' is the default + * App. + */ + headers: object; + + /** + * The (read-only) configuration options from the app initialization. + */ + setCookieCallback?: (name: string, value: string) => void): FirebaseServerApp; +} + export interface FirebaseNamespace { /** * Create (and initialize) a FirebaseApp. @@ -66,6 +84,7 @@ export interface FirebaseNamespace { options: FirebaseOptions, config?: FirebaseAppConfig ): FirebaseApp; + /** * Create (and initialize) a FirebaseApp. * @@ -75,6 +94,17 @@ export interface FirebaseNamespace { */ initializeApp(options: FirebaseOptions, name?: string): FirebaseApp; + /** + * Create (and initialize) a FirebaseServerApp. + * + * @param options Options to configure the services used in the App. + * @param config The optional config for your firebase server app + */ + initializeServerAppInstance( + options: FirebaseOptions, + config?: FirebaseServerAppConfig + ): FirebaseServerApp; + app: { /** * Retrieve an instance of a FirebaseApp. @@ -94,11 +124,35 @@ export interface FirebaseNamespace { App: typeof FirebaseApp; }; + serverApp: { + /** + * Retrieve an instance of a FirebaseServerApp. + * + * Usage: firebase.serverApp() + * + * @param name The optional name of the server app to return ('[DEFAULT]' if omitted) + */ + (name?: string): FirebaseServerApp; + + /** + * For testing FirebaseApp instances: + * app() instanceof firebase.app.App + * + * DO NOT call this constuctor directly (use firebase.app() instead). + */ + serverApp: typeof FirebaseServerApp; + }; + /** * A (read-only) array of all the initialized Apps. */ apps: FirebaseApp[]; + /** + * A (read-only) array of all the initialized Server Apps. + */ + serverApps: FirebaseServerApp[]; + /** * Registers a library's name and version for platform logging purposes. * @param library Name of 1p or 3p library (e.g. firestore, angularfire) diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index d43c4ac65c2..8ad1c5065aa 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -171,6 +171,121 @@ export function initializeApp( return newApp; } +/** + * Creates and initializes a {@link @firebase/app#FirebaseServerApp} instance. + * + * The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in + * server environments only. + * + * See + * {@link +* https://firebase.google.com/docs/web/setup#add_firebase_to_your_app +* | Add Firebase to your app} and +* {@link +* https://firebase.google.com/docs/web/setup#multiple-projects +* | Initialize multiple projects} for detailed documentation. +* +* @example +* ```javascript +* +* // Initialize default app +* // Retrieve your own options values by adding a web app on +* // https://console.firebase.google.com +* initializeServerAppInstance({ +* apiKey: "AIza....", // Auth / General Use +* authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect +* databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database +* storageBucket: "YOUR_APP.appspot.com", // Storage +* messagingSenderId: "123456789" // Cloud Messaging +* }, +* { +* headers: requestHeaders +* }); +* ``` +* +* @example +* ```javascript +* +* // Initialize another server app +* const otherApp = initializeServerAppInstance({ +* databaseURL: "https://.firebaseio.com", +* storageBucket: ".appspot.com" +* }, +* { +* name: "otherApp", +* headers: requestHeaders +* }); +* ``` +* +* @param options - Options to configure the app's services. +* @param config - FirebaseServerApp configuration. +* +* @returns The initialized FirebaseServerApp. +* +* @public +*/ +export function initializeServerAppInstance( + options: FirebaseOptions, + config: FirebaseServerAppSettings): FirebaseServerApp; + + +/** +* Creates and initializes a FirebaseServerApp instance. +* +* @public +*/ +export function initializeServerAppInstance(config: FirebaseServerAppSettings): + FirebaseServerApp; +export function initializeServerAppInstance( + _serverAppSettings: FirebaseServerAppSettings, + _options?: FirebaseOptions) +): FirebaseServerApp { + + let options = _options; + + const serverAppSettings: Required = { + name: DEFAULT_ENTRY_NAME, + ..._serverAppSettings + }; + const name = config.name; + + if (typeof name !== 'string' || !name) { + throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, { + appName: String(name) + }); + } + + options ||= getDefaultAppConfig(); + + if (!options) { + throw ERROR_FACTORY.create(AppError.NO_OPTIONS); + } + + const existingApp = _serverApps.get(name) as FirebaseAppImpl; + if (existingApp) { + // return the existing app if options and config deep equal the ones in the existing app. + if ( + deepEqual(options, existingApp.options) && + deepEqual(serverAppSettings, existingApp.serverAppSettings) + ) { + return existingApp; + } else { + throw ERROR_FACTORY.create(AppError.DUPLICATE_APP, { appName: name }); + } + } + + const container = new ComponentContainer(name); + for (const component of _components.values()) { + container.addComponent(component); + } + + const newApp = new FirebaseAppImpl(options, config, container); + + _apps.set(name, newApp); + + return newApp; +} + /** * Retrieves a {@link @firebase/app#FirebaseApp} instance. * diff --git a/packages/app/src/firebaseApp.ts b/packages/app/src/firebaseApp.ts index 60d3b29a3ea..70c8c8ce5b2 100644 --- a/packages/app/src/firebaseApp.ts +++ b/packages/app/src/firebaseApp.ts @@ -28,8 +28,8 @@ import { import { ERROR_FACTORY, AppError } from './errors'; export class FirebaseAppImpl implements FirebaseApp { - private readonly _options: FirebaseOptions; - private readonly _name: string; + protected readonly _options: FirebaseOptions; + protected readonly _name: string; /** * Original config values passed in as a constructor parameter. * It is only used to compare with another config object to support idempotent initializeApp(). @@ -37,9 +37,9 @@ export class FirebaseAppImpl implements FirebaseApp { * Updating automaticDataCollectionEnabled on the App instance will not change its value in _config. */ private readonly _config: Required; - private _automaticDataCollectionEnabled: boolean; - private _isDeleted = false; - private readonly _container: ComponentContainer; + protected _automaticDataCollectionEnabled: boolean; + protected _isDeleted = false; + protected readonly _container: ComponentContainer; constructor( options: FirebaseOptions, @@ -98,7 +98,7 @@ export class FirebaseAppImpl implements FirebaseApp { * This function will throw an Error if the App has already been deleted - * use before performing API actions on the App. */ - private checkDestroyed(): void { + protected checkDestroyed(): void { if (this.isDeleted) { throw ERROR_FACTORY.create(AppError.APP_DELETED, { appName: this._name }); } diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts new file mode 100644 index 00000000000..8a3d46286c9 --- /dev/null +++ b/packages/app/src/firebaseServerApp.ts @@ -0,0 +1,125 @@ +/** + * @license + * Copyright 2023 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 { + FirebaseAppImpl, +} from './firebaseApp'; +import { + FirebaseServerApp, + FirebaseOptions, + FirebaseServerAppSettings +} from './public-types'; +import { + ComponentContainer, + Component, + ComponentType +} from '@firebase/component'; +import { ERROR_FACTORY, AppError } from './errors'; + +export class FirebaseServerAppImpl implements FirebaseServerApp { + /** + * Original config values passed in as a constructor parameter. + * It is only used to compare with another config object to support idempotent + * initializeServerAppInstance(). + * + * Updating automaticDataCollectionEnabled on the App instance will not change its value in + * _config. + */ + private readonly _config: Required; + private readonly _options: FirebaseOptions; + private readonly _name: string; + private _automaticDataCollectionEnabled: boolean; + private readonly _headers: object; + private readonly _setCookieCallback: (name: string, value: string) => void): FirebaseServerApp; + private _isDeleted = false; + private readonly _container: ComponentContainer; + + constructor( + options: FirebaseOptions, + config: Required, + container: ComponentContainer + ) { + this._options = { ...options }; + this._config = { ...config }; + this._name = config.name; + this._automaticDataCollectionEnabled = + config.automaticDataCollectionEnabled; + this._headers = config.headers; + this._setCookieCallback = config.setCookieCallback; + this._container = container; + this.container.addComponent( + new Component('app', () => this, ComponentType.PUBLIC) + ); + } + + get automaticDataCollectionEnabled(): boolean { + this.checkDestroyed(); + return this._automaticDataCollectionEnabled; + } + + set automaticDataCollectionEnabled(val: boolean) { + this.checkDestroyed(); + this._automaticDataCollectionEnabled = val; + } + + get name(): string { + this.checkDestroyed(); + return this._name; + } + + get options(): FirebaseOptions { + this.checkDestroyed(); + return this._options; + } + + get config(): Required { + this.checkDestroyed(); + return this._config; + } + + get container(): ComponentContainer { + return this._container; + } + + get isDeleted(): boolean { + return this._isDeleted; + } + + set isDeleted(val: boolean) { + this._isDeleted = val; + } + + get headers(): object { + return this._headers; + } + + callSetCookieCallback(cookieName: string, cookieValue: string) : void { + if(this._setCookieCallback !== undefined) { + this._setCookieCallback(cookieName, cookieValue); + } + } + + /** + * This function will throw an Error if the App has already been deleted - + * use before performing API actions on the App. + */ + private checkDestroyed(): void { + if (this.isDeleted) { + throw ERROR_FACTORY.create(AppError.APP_DELETED, { appName: this._name }); + } + } +} diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 3ca76c47889..b8e428755f4 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -71,6 +71,22 @@ export interface FirebaseApp { automaticDataCollectionEnabled: boolean; } +/** + * A {@link @firebase/app#FirebaseServerApp} holds the initialization information + * for a collection of services running in server enviornments. + * + * Do not call this constructor directly. Instead, use + * {@link (initializeServerAppInstance:1) | initializeServerAppInstance()} to create + * an app. + * + * @public + */ +export interface FirebaseServerApp extends FirebaseApp { + // An object originating from the API endpoint or Server Side Rendering + // request which contains the browser cookies of the HTTP request. + readonly headers: object; + } + /** * @public * @@ -139,6 +155,24 @@ export interface FirebaseAppSettings { automaticDataCollectionEnabled?: boolean; } +/** + * @public + * + * Configuration options given to {@link (initializeServerApp:1) | initializeServerApp()} + */ +export interface FirebaseServerAppSettings extends FirebaseAppSettings { + /** + * An object containing the client's HTTP request headers. + */ + headers: object; + + /** + * An optional function callback the Firebase SDK may call to request that a cookie be + * added to the server response cookie object. + */ + setCookieCallback?: (name: string, value: string) => void): FirebaseServerApp; +} + /** * @internal */ From 2efe04367c70fe2e8401597b4ff9555cd8dd39c4 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 20 Sep 2023 16:57:00 -0400 Subject: [PATCH 02/41] another pass, optional callback failures --- common/api-review/app.api.md | 21 +++- packages/app-types/index.d.ts | 4 +- packages/app/src/api.ts | 137 +++++++++++--------------- packages/app/src/firebaseServerApp.ts | 10 +- packages/app/src/internal.ts | 3 +- packages/app/src/public-types.ts | 2 +- 6 files changed, 85 insertions(+), 92 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 00143e52120..151e4a3dcb6 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -71,6 +71,18 @@ export interface FirebaseOptions { storageBucket?: string; } +// @public +export interface FirebaseServerApp extends FirebaseApp { + // (undocumented) + readonly headers: object; +} + +// @public +export interface FirebaseServerAppSettings extends FirebaseAppSettings { + headers: object; + setCookieCallback?: (name: string, value: string) => void; +} + // @internal (undocumented) export interface _FirebaseService { // (undocumented) @@ -97,11 +109,7 @@ export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSett export function initializeApp(): FirebaseApp; // @public -export function initializeServerAppInstance(options: FirebaseOptions, config?: - FirebaseAppSettings): FirebaseServerApp; - -// @public -export function initializeServerAppInstance(): FirebaseServerApp; +export function initializeServerAppInstance(_options: FirebaseOptions, _config: FirebaseServerAppSettings): FirebaseServerApp; // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; @@ -118,6 +126,9 @@ export function _removeServiceInstance(app: FirebaseApp, name: T // @public export const SDK_VERSION: string; +// @public (undocumented) +export const _serverApps: Map; + // @public export function setLogLevel(logLevel: LogLevelString): void; diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 242f5211966..1e0a924b696 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -34,7 +34,7 @@ export interface FirebaseAppConfig { export interface FirebaseServerAppConfig extends FirebaseAppConfig { headers: object; - setCookieCallback?: (name: string, value: string) => void): FirebaseServerApp; + setCookieCallback?: (name: string, value: string) => void; } export class FirebaseApp { @@ -70,7 +70,7 @@ export class FirebaseServerApp extends FirebaseApp { /** * The (read-only) configuration options from the app initialization. */ - setCookieCallback?: (name: string, value: string) => void): FirebaseServerApp; + setCookieCallback?: (name: string, value: string) => void; } export interface FirebaseNamespace { diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 8ad1c5065aa..6a26bc6bc6e 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -17,8 +17,10 @@ import { FirebaseApp, + FirebaseServerApp, FirebaseOptions, - FirebaseAppSettings + FirebaseAppSettings, + FirebaseServerAppSettings } from './public-types'; import { DEFAULT_ENTRY_NAME, PLATFORM_LOG_STRING } from './constants'; import { ERROR_FACTORY, AppError } from './errors'; @@ -30,7 +32,8 @@ import { } from '@firebase/component'; import { version } from '../../firebase/package.json'; import { FirebaseAppImpl } from './firebaseApp'; -import { _apps, _components, _registerComponent } from './internal'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; +import { _apps, _components, _registerComponent, _serverApps } from './internal'; import { logger } from './logger'; import { LogLevelString, @@ -179,94 +182,74 @@ export function initializeApp( * * See * {@link -* https://firebase.google.com/docs/web/setup#add_firebase_to_your_app -* | Add Firebase to your app} and -* {@link -* https://firebase.google.com/docs/web/setup#multiple-projects -* | Initialize multiple projects} for detailed documentation. -* -* @example -* ```javascript -* -* // Initialize default app -* // Retrieve your own options values by adding a web app on -* // https://console.firebase.google.com -* initializeServerAppInstance({ -* apiKey: "AIza....", // Auth / General Use -* authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect -* databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database -* storageBucket: "YOUR_APP.appspot.com", // Storage -* messagingSenderId: "123456789" // Cloud Messaging -* }, -* { -* headers: requestHeaders -* }); -* ``` -* -* @example -* ```javascript -* -* // Initialize another server app -* const otherApp = initializeServerAppInstance({ -* databaseURL: "https://.firebaseio.com", -* storageBucket: ".appspot.com" -* }, -* { -* name: "otherApp", -* headers: requestHeaders -* }); -* ``` -* -* @param options - Options to configure the app's services. -* @param config - FirebaseServerApp configuration. -* -* @returns The initialized FirebaseServerApp. -* -* @public -*/ -export function initializeServerAppInstance( - options: FirebaseOptions, - config: FirebaseServerAppSettings): FirebaseServerApp; - - -/** -* Creates and initializes a FirebaseServerApp instance. -* -* @public -*/ -export function initializeServerAppInstance(config: FirebaseServerAppSettings): - FirebaseServerApp; + * https://firebase.google.com/docs/web/setup#add_firebase_to_your_app + * | Add Firebase to your app} and + * {@link + * https://firebase.google.com/docs/web/setup#multiple-projects + * | Initialize multiple projects} for detailed documentation. + * + * @example + * ```javascript + * + * // Initialize default app + * // Retrieve your own options values by adding a web app on + * // https://console.firebase.google.com + * initializeServerAppInstance({ + * apiKey: "AIza....", // Auth / General Use + * authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect + * databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database + * storageBucket: "YOUR_APP.appspot.com", // Storage + * messagingSenderId: "123456789" // Cloud Messaging + * }, + * { + * headers: requestHeaders + * }); + * ``` + * + * @example + * ```javascript + * + * // Initialize another server app + * const otherApp = initializeServerAppInstance({ + * databaseURL: "https://.firebaseio.com", + * storageBucket: ".appspot.com" + * }, + * { + * name: "otherApp", + * headers: requestHeaders + * }); + * ``` + * + * @param options - Options to configure the app's services. + * @param config - FirebaseServerApp configuration. + * + * @returns The initialized FirebaseServerApp. + * + * @public + */ export function initializeServerAppInstance( - _serverAppSettings: FirebaseServerAppSettings, - _options?: FirebaseOptions) -): FirebaseServerApp { + _options: FirebaseOptions, + _config: FirebaseServerAppSettings): FirebaseServerApp { - let options = _options; - - const serverAppSettings: Required = { + const serverAppSettings: FirebaseServerAppSettings = { name: DEFAULT_ENTRY_NAME, - ..._serverAppSettings + automaticDataCollectionEnabled: false, + ..._config }; - const name = config.name; + const name = _config.name; if (typeof name !== 'string' || !name) { throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, { appName: String(name) }); } - options ||= getDefaultAppConfig(); - - if (!options) { - throw ERROR_FACTORY.create(AppError.NO_OPTIONS); - } - - const existingApp = _serverApps.get(name) as FirebaseAppImpl; + const existingApp = _serverApps.get(name) as FirebaseServerAppImpl; if (existingApp) { // return the existing app if options and config deep equal the ones in the existing app. if ( - deepEqual(options, existingApp.options) && - deepEqual(serverAppSettings, existingApp.serverAppSettings) + deepEqual(_options, existingApp.options) && + deepEqual(serverAppSettings, existingApp.config) ) { return existingApp; } else { @@ -279,7 +262,7 @@ export function initializeServerAppInstance( container.addComponent(component); } - const newApp = new FirebaseAppImpl(options, config, container); + const newApp = new FirebaseServerAppImpl(_options, serverAppSettings, container); _apps.set(name, newApp); diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 8a3d46286c9..96ae20e2ecb 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -15,9 +15,6 @@ * limitations under the License. */ -import { - FirebaseAppImpl, -} from './firebaseApp'; import { FirebaseServerApp, FirebaseOptions, @@ -39,18 +36,18 @@ export class FirebaseServerAppImpl implements FirebaseServerApp { * Updating automaticDataCollectionEnabled on the App instance will not change its value in * _config. */ - private readonly _config: Required; + private readonly _config: Required; private readonly _options: FirebaseOptions; private readonly _name: string; private _automaticDataCollectionEnabled: boolean; private readonly _headers: object; - private readonly _setCookieCallback: (name: string, value: string) => void): FirebaseServerApp; + private readonly _setCookieCallback: (name: string, value: string) => void; private _isDeleted = false; private readonly _container: ComponentContainer; constructor( options: FirebaseOptions, - config: Required, + config: FirebaseServerAppSettings, container: ComponentContainer ) { this._options = { ...options }; @@ -60,6 +57,7 @@ export class FirebaseServerAppImpl implements FirebaseServerApp { config.automaticDataCollectionEnabled; this._headers = config.headers; this._setCookieCallback = config.setCookieCallback; + this._container = container; this.container.addComponent( new Component('app', () => this, ComponentType.PUBLIC) diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 9026a36b26a..2bec868d10b 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from './public-types'; +import { FirebaseApp, FirebaseServerApp } from './public-types'; import { Component, Provider, Name } from '@firebase/component'; import { logger } from './logger'; import { DEFAULT_ENTRY_NAME } from './constants'; @@ -25,6 +25,7 @@ import { FirebaseAppImpl } from './firebaseApp'; * @internal */ export const _apps = new Map(); +export const _serverApps = new Map(); /** * Registered components. diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index b8e428755f4..29f641b1385 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -170,7 +170,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * An optional function callback the Firebase SDK may call to request that a cookie be * added to the server response cookie object. */ - setCookieCallback?: (name: string, value: string) => void): FirebaseServerApp; + setCookieCallback?: (name: string, value: string) => void; } /** From fda3060e3607087ca2d9ef41320a6dff4b5c044f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 21 Sep 2023 11:26:54 -0400 Subject: [PATCH 03/41] compilation fixes --- packages/app/src/firebaseServerApp.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 96ae20e2ecb..6d875aa35f4 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -26,6 +26,7 @@ import { ComponentType } from '@firebase/component'; import { ERROR_FACTORY, AppError } from './errors'; +import { DEFAULT_ENTRY_NAME } from './constants'; export class FirebaseServerAppImpl implements FirebaseServerApp { /** @@ -51,18 +52,30 @@ export class FirebaseServerAppImpl implements FirebaseServerApp { container: ComponentContainer ) { this._options = { ...options }; - this._config = { ...config }; - this._name = config.name; + this._config = { + + name: DEFAULT_ENTRY_NAME, + automaticDataCollectionEnabled: false, + setCookieCallback: this.defaultSetCookieCallback, + ...config + }; + this._name = this._config.name; this._automaticDataCollectionEnabled = - config.automaticDataCollectionEnabled; - this._headers = config.headers; - this._setCookieCallback = config.setCookieCallback; + this._config.automaticDataCollectionEnabled; + this._headers = this._config.headers; + this._setCookieCallback = this._config.setCookieCallback; + this._container = container; this.container.addComponent( new Component('app', () => this, ComponentType.PUBLIC) ); } + + private defaultSetCookieCallback(name: string, value: string): void { + console.log(name); + console.log(value); + } get automaticDataCollectionEnabled(): boolean { this.checkDestroyed(); From 5fa9c21d00cc330ed16b34cba710b5d80dd1fd51 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 21 Sep 2023 15:00:52 -0400 Subject: [PATCH 04/41] FirebaseServerAppImpl now extends FirebaseAppImpl --- packages/app/src/firebaseServerApp.ts | 84 +++++++++++---------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 6d875aa35f4..8389fd88188 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -16,60 +16,52 @@ */ import { + FirebaseAppSettings, FirebaseServerApp, - FirebaseOptions, - FirebaseServerAppSettings + FirebaseServerAppSettings, + FirebaseOptions } from './public-types'; import { - ComponentContainer, - Component, - ComponentType + ComponentContainer } from '@firebase/component'; -import { ERROR_FACTORY, AppError } from './errors'; +import {FirebaseAppImpl} from './firebaseApp'; import { DEFAULT_ENTRY_NAME } from './constants'; -export class FirebaseServerAppImpl implements FirebaseServerApp { - /** - * Original config values passed in as a constructor parameter. - * It is only used to compare with another config object to support idempotent - * initializeServerAppInstance(). - * - * Updating automaticDataCollectionEnabled on the App instance will not change its value in - * _config. - */ - private readonly _config: Required; - private readonly _options: FirebaseOptions; - private readonly _name: string; - private _automaticDataCollectionEnabled: boolean; +export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseServerApp { + private readonly _serverConfig: Required; private readonly _headers: object; private readonly _setCookieCallback: (name: string, value: string) => void; - private _isDeleted = false; - private readonly _container: ComponentContainer; - + constructor( options: FirebaseOptions, - config: FirebaseServerAppSettings, + serverConfig: FirebaseServerAppSettings, container: ComponentContainer ) { - this._options = { ...options }; - this._config = { - - name: DEFAULT_ENTRY_NAME, - automaticDataCollectionEnabled: false, - setCookieCallback: this.defaultSetCookieCallback, - ...config + + let name:string = DEFAULT_ENTRY_NAME; + if(serverConfig.name !== undefined) { + name = serverConfig.name; + } + + let automaticDataCollectionEnabled = false; + if(serverConfig.automaticDataCollectionEnabled !== undefined) { + automaticDataCollectionEnabled = serverConfig.automaticDataCollectionEnabled; + } + + const config:Required = { + name, + automaticDataCollectionEnabled }; - this._name = this._config.name; - this._automaticDataCollectionEnabled = - this._config.automaticDataCollectionEnabled; - this._headers = this._config.headers; - this._setCookieCallback = this._config.setCookieCallback; - - this._container = container; - this.container.addComponent( - new Component('app', () => this, ComponentType.PUBLIC) - ); + super(options, config, container); + this._serverConfig = { + name, + automaticDataCollectionEnabled, + setCookieCallback: this.defaultSetCookieCallback, + ...serverConfig + }; + this._headers = this._serverConfig.headers; + this._setCookieCallback = this._serverConfig.setCookieCallback; } private defaultSetCookieCallback(name: string, value: string): void { @@ -99,7 +91,7 @@ export class FirebaseServerAppImpl implements FirebaseServerApp { get config(): Required { this.checkDestroyed(); - return this._config; + return this._serverConfig; } get container(): ComponentContainer { @@ -123,14 +115,4 @@ export class FirebaseServerAppImpl implements FirebaseServerApp { this._setCookieCallback(cookieName, cookieValue); } } - - /** - * This function will throw an Error if the App has already been deleted - - * use before performing API actions on the App. - */ - private checkDestroyed(): void { - if (this.isDeleted) { - throw ERROR_FACTORY.create(AppError.APP_DELETED, { appName: this._name }); - } - } } From 0ef3bb22cfa73f9a065a9bcc6b1cd8158b8f20fe Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 2 Oct 2023 11:39:48 -0400 Subject: [PATCH 05/41] removed Headers object, added three callbacks instead --- common/api-review/app.api.md | 8 +++--- packages/app-types/index.d.ts | 17 +++++++++---- packages/app/src/firebaseServerApp.ts | 29 +++++++++++++--------- packages/app/src/public-types.ts | 35 +++++++++++++++++++++------ 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 151e4a3dcb6..db2fb723574 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -73,13 +73,15 @@ export interface FirebaseOptions { // @public export interface FirebaseServerApp extends FirebaseApp { - // (undocumented) - readonly headers: object; + invokeGetCookieCallback: (name: string) => string | undefined; + invokeGetHeaderCallback: (name: string) => string | undefined; + invokeSetCookieCallback: (name: string, value: string) => void; } // @public export interface FirebaseServerAppSettings extends FirebaseAppSettings { - headers: object; + getCookieCallback: (name: string) => string | undefined; + getHeaderCallback: (name: string) => string | undefined; setCookieCallback?: (name: string, value: string) => void; } diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 1e0a924b696..3e5d1299bc4 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -33,8 +33,9 @@ export interface FirebaseAppConfig { } export interface FirebaseServerAppConfig extends FirebaseAppConfig { - headers: object; + getCookieCallback: (name: string) => string|undefined; setCookieCallback?: (name: string, value: string) => void; + getHeaderCallback: (name: string) => string|undefined; } export class FirebaseApp { @@ -62,15 +63,21 @@ export class FirebaseApp { export class FirebaseServerApp extends FirebaseApp { /** - * The (read-only) name (identifier) for this App. '[DEFAULT]' is the default - * App. + * A callback that may be invoked by the Firebase SDKs to retrieve cookie data from the server + * request object. */ - headers: object; + getCookieCallback: (name: string) => string|undefined; /** - * The (read-only) configuration options from the app initialization. + * A callback that may be invoked by the Firebase SDKs to set a cookie in the SSR response object. */ setCookieCallback?: (name: string, value: string) => void; + + /** + * A callback that may be invoked by the Firebase SDKs to query a header value from the server + * request object. + */ + getHeaderCallback: (name: string) => string|undefined; } export interface FirebaseNamespace { diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 8389fd88188..12f1d3a38e1 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -29,8 +29,9 @@ import { DEFAULT_ENTRY_NAME } from './constants'; export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseServerApp { private readonly _serverConfig: Required; - private readonly _headers: object; private readonly _setCookieCallback: (name: string, value: string) => void; + private readonly _getCookieCallback: (name: string) => string|undefined; + private readonly _getHeaderCallback: (name: string) => string|undefined; constructor( options: FirebaseOptions, @@ -60,14 +61,13 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe setCookieCallback: this.defaultSetCookieCallback, ...serverConfig }; - this._headers = this._serverConfig.headers; + this._setCookieCallback = this._serverConfig.setCookieCallback; + this._getCookieCallback = this._serverConfig.getCookieCallback; + this._getHeaderCallback = this._serverConfig.getHeaderCallback; } - private defaultSetCookieCallback(name: string, value: string): void { - console.log(name); - console.log(value); - } + private defaultSetCookieCallback(name: string, value: string): void { } get automaticDataCollectionEnabled(): boolean { this.checkDestroyed(); @@ -106,13 +106,20 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe this._isDeleted = val; } - get headers(): object { - return this._headers; - } - - callSetCookieCallback(cookieName: string, cookieValue: string) : void { + invokeSetCookieCallback(cookieName: string, cookieValue: string) : void { + this.checkDestroyed(); if(this._setCookieCallback !== undefined) { this._setCookieCallback(cookieName, cookieValue); } } + + invokeGetCookieCallback(cookieName: string) : string|undefined { + this.checkDestroyed(); + return this._getCookieCallback(cookieName); + } + + invokeGetHeaderCallback(headerName: string) : string|undefined { + this.checkDestroyed(); + return this._getHeaderCallback(headerName); + } } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 29f641b1385..2ad3f6f37a4 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -82,9 +82,22 @@ export interface FirebaseApp { * @public */ export interface FirebaseServerApp extends FirebaseApp { - // An object originating from the API endpoint or Server Side Rendering - // request which contains the browser cookies of the HTTP request. - readonly headers: object; + /** + * Invokes a callback into the App to retrieve cookie data from the server request object. + */ + invokeGetCookieCallback: (name: string) => string|undefined; + + /** + * Invokes a callback into the App to set cookie data within the server response object. If + * no callback was configured when the FirebaseServerApp was created then this operation is a + * no-op. + */ + invokeSetCookieCallback: (name: string, value: string) => void; + + /** + * Invokes a callback into the App to retrieve header data from the server request object. + */ + invokeGetHeaderCallback: (name: string) => string|undefined; } /** @@ -162,15 +175,23 @@ export interface FirebaseAppSettings { */ export interface FirebaseServerAppSettings extends FirebaseAppSettings { /** - * An object containing the client's HTTP request headers. + * A function callback the Firebase SDK may call to query a cookie value from the server request + * object. */ - headers: object; + getCookieCallback: (name: string) => string|undefined; /** - * An optional function callback the Firebase SDK may call to request that a cookie be - * added to the server response cookie object. + * An optional function callback the Firebase SDK may call to request that a cookie be added to + * the server response cookie object. If this callback is not provided then there will be no + * automatic data propagation to the Firebase SDK running on sthe client. */ setCookieCallback?: (name: string, value: string) => void; + + /** + * A function callback the Firebase SDK may call to query a header value from the server request + * object. + */ + getHeaderCallback: (name: string) => string|undefined; } /** From 8cd1072677d17a47d85f1e52d5141701af710e2b Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 2 Oct 2023 15:33:38 -0400 Subject: [PATCH 06/41] added deleteOnDeref support --- common/api-review/app.api.md | 6 +- packages/app/src/api.ts | 26 +++++-- packages/app/src/firebaseServerApp.ts | 104 +++++++++++--------------- packages/app/src/internal.ts | 5 ++ packages/app/src/public-types.ts | 11 +++ packages/app/tsconfig.json | 3 +- 6 files changed, 86 insertions(+), 69 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index db2fb723574..746fcb017f8 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -80,6 +80,7 @@ export interface FirebaseServerApp extends FirebaseApp { // @public export interface FirebaseServerAppSettings extends FirebaseAppSettings { + deleteOnDeref?: WeakRef; getCookieCallback: (name: string) => string | undefined; getHeaderCallback: (name: string) => string | undefined; setCookieCallback?: (name: string, value: string) => void; @@ -101,6 +102,9 @@ export function getApps(): FirebaseApp[]; // @internal (undocumented) export function _getProvider(app: FirebaseApp, name: T): Provider; +// @public +export function getServerApps(): FirebaseServerApp[]; + // @public export function initializeApp(options: FirebaseOptions, name?: string): FirebaseApp; @@ -111,7 +115,7 @@ export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSett export function initializeApp(): FirebaseApp; // @public -export function initializeServerAppInstance(_options: FirebaseOptions, _config: FirebaseServerAppSettings): FirebaseServerApp; +export function initializeServerAppInstance(_options: FirebaseOptions, _serverAppConfig: FirebaseServerAppSettings): FirebaseServerApp; // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 6a26bc6bc6e..4319f49423b 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -229,15 +229,15 @@ export function initializeApp( */ export function initializeServerAppInstance( _options: FirebaseOptions, - _config: FirebaseServerAppSettings): FirebaseServerApp { + _serverAppConfig: FirebaseServerAppSettings): FirebaseServerApp { const serverAppSettings: FirebaseServerAppSettings = { name: DEFAULT_ENTRY_NAME, automaticDataCollectionEnabled: false, - ..._config + ..._serverAppConfig }; - const name = _config.name; + const name = _serverAppConfig.name; if (typeof name !== 'string' || !name) { throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, { appName: String(name) @@ -249,7 +249,7 @@ export function initializeServerAppInstance( // return the existing app if options and config deep equal the ones in the existing app. if ( deepEqual(_options, existingApp.options) && - deepEqual(serverAppSettings, existingApp.config) + deepEqual(serverAppSettings, existingApp.serverAppConfig) ) { return existingApp; } else { @@ -263,8 +263,7 @@ export function initializeServerAppInstance( } const newApp = new FirebaseServerAppImpl(_options, serverAppSettings, container); - - _apps.set(name, newApp); + _serverApps.set(name, newApp); return newApp; } @@ -318,6 +317,14 @@ export function getApps(): FirebaseApp[] { return Array.from(_apps.values()); } +/** + * A (read-only) array of all initialized server apps. + * @public + */ +export function getServerApps(): FirebaseServerApp[] { + return Array.from(_serverApps.values()); +} + /** * Renders this app unusable and frees the resources of all associated * services. @@ -336,9 +343,16 @@ export function getApps(): FirebaseApp[] { * @public */ export async function deleteApp(app: FirebaseApp): Promise { + var foundApp = false; const name = app.name; if (_apps.has(name)) { + foundApp = true; _apps.delete(name); + } else if (_serverApps.has(name)) { + foundApp = true; + _serverApps.delete(name); + } + if (foundApp) { await Promise.all( (app as FirebaseAppImpl).container .getProviders() diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 12f1d3a38e1..fd76d5a7e0b 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -21,105 +21,87 @@ import { FirebaseServerAppSettings, FirebaseOptions } from './public-types'; +import { + deleteApp +} from 'firebase/app'; import { ComponentContainer } from '@firebase/component'; -import {FirebaseAppImpl} from './firebaseApp'; +import { FirebaseAppImpl } from './firebaseApp'; import { DEFAULT_ENTRY_NAME } from './constants'; export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseServerApp { - private readonly _serverConfig: Required; - private readonly _setCookieCallback: (name: string, value: string) => void; - private readonly _getCookieCallback: (name: string) => string|undefined; - private readonly _getHeaderCallback: (name: string) => string|undefined; - + private readonly _serverConfig: FirebaseServerAppSettings; + private readonly _setCookieCallback?: (name: string, value: string) => void; + private readonly _getCookieCallback?: (name: string) => string | undefined; + private readonly _getHeaderCallback?: (name: string) => string | undefined; + private readonly _deleteOnDeref?: WeakRef; + private _finalizationRegistry: FinalizationRegistry; + constructor( options: FirebaseOptions, serverConfig: FirebaseServerAppSettings, container: ComponentContainer ) { - - let name:string = DEFAULT_ENTRY_NAME; - if(serverConfig.name !== undefined) { - name = serverConfig.name; - } - - let automaticDataCollectionEnabled = false; - if(serverConfig.automaticDataCollectionEnabled !== undefined) { - automaticDataCollectionEnabled = serverConfig.automaticDataCollectionEnabled; - } - - const config:Required = { - name, - automaticDataCollectionEnabled + // Build configuration parameters for the FirebaseAppImpl base class. + const name: string = (serverConfig.name !== undefined) ? serverConfig.name : DEFAULT_ENTRY_NAME; + const automaticDataCollectionEnabled = + (serverConfig.automaticDataCollectionEnabled !== undefined) ? serverConfig.automaticDataCollectionEnabled : false; + + // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. + const config: Required = { + name, automaticDataCollectionEnabled }; - + + // Construct the parent FirebaseAppImp object. super(options, config, container); + + // Now construct the data for the FirebaseServerAppImpl. this._serverConfig = { - name, - automaticDataCollectionEnabled, - setCookieCallback: this.defaultSetCookieCallback, + name, automaticDataCollectionEnabled, ...serverConfig }; this._setCookieCallback = this._serverConfig.setCookieCallback; this._getCookieCallback = this._serverConfig.getCookieCallback; this._getHeaderCallback = this._serverConfig.getHeaderCallback; - } - - private defaultSetCookieCallback(name: string, value: string): void { } - - get automaticDataCollectionEnabled(): boolean { - this.checkDestroyed(); - return this._automaticDataCollectionEnabled; - } + this._deleteOnDeref = this._serverConfig.deleteOnDeref; - set automaticDataCollectionEnabled(val: boolean) { - this.checkDestroyed(); - this._automaticDataCollectionEnabled = val; - } + this._finalizationRegistry = + new FinalizationRegistry(this.automaticCleanup); - get name(): string { - this.checkDestroyed(); - return this._name; + if (this._deleteOnDeref !== undefined) { + this._finalizationRegistry.register(this._deleteOnDeref.deref, this) + } } - get options(): FirebaseOptions { - this.checkDestroyed(); - return this._options; + private automaticCleanup(serverApp: FirebaseServerAppImpl): void { + deleteApp(serverApp); } - get config(): Required { + get serverAppConfig(): FirebaseServerAppSettings { this.checkDestroyed(); return this._serverConfig; } - get container(): ComponentContainer { - return this._container; - } - - get isDeleted(): boolean { - return this._isDeleted; - } - - set isDeleted(val: boolean) { - this._isDeleted = val; - } - - invokeSetCookieCallback(cookieName: string, cookieValue: string) : void { + invokeSetCookieCallback(cookieName: string, cookieValue: string): void { this.checkDestroyed(); - if(this._setCookieCallback !== undefined) { + if (this._setCookieCallback !== undefined) { this._setCookieCallback(cookieName, cookieValue); } } - invokeGetCookieCallback(cookieName: string) : string|undefined { + invokeGetCookieCallback(cookieName: string): string | undefined { this.checkDestroyed(); - return this._getCookieCallback(cookieName); + if (this._getCookieCallback !== undefined) { + return this._getCookieCallback(cookieName); + } } - invokeGetHeaderCallback(headerName: string) : string|undefined { + invokeGetHeaderCallback(headerName: string): string | undefined { this.checkDestroyed(); - return this._getHeaderCallback(headerName); + if (this._getHeaderCallback !== undefined) { + return this._getHeaderCallback(headerName); + } } } diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 2bec868d10b..4d7a82a238c 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -20,6 +20,7 @@ import { Component, Provider, Name } from '@firebase/component'; import { logger } from './logger'; import { DEFAULT_ENTRY_NAME } from './constants'; import { FirebaseAppImpl } from './firebaseApp'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; /** * @internal @@ -91,6 +92,10 @@ export function _registerComponent( _addComponent(app as FirebaseAppImpl, component); } + for (const serverApp of _serverApps.values()) { + _addComponent(serverApp as FirebaseServerAppImpl, component); + } + return true; } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 2ad3f6f37a4..38e0cc6c52e 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -192,6 +192,17 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * object. */ getHeaderCallback: (name: string) => string|undefined; + + /** + * An optional WeakRef. If provided, the Firebase SDK will cleanup and destroy + * itself when the object pointed to by the WeakRef is destroyed. This field is + * used to help reduce memory overhead for long-running cloud functions executing SSR + * fulfillment. + * + * If a WeakRef is not provided then the application must clean up the + * FirebaseServerApp instance through it's own standard mechanisms. + */ + deleteOnDeref?: WeakRef; } /** diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 735ea623511..79d5153105c 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../config/tsconfig.base.json", "compilerOptions": { "outDir": "dist", - "downlevelIteration": true + "downlevelIteration": true, + "lib": ["ESNext"], }, "exclude": [ "dist/**/*" From d42f6763d759563334e507d5e63814dab6252764 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 2 Oct 2023 19:39:26 -0400 Subject: [PATCH 07/41] lint fixes --- packages/app/src/firebaseServerApp.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index fd76d5a7e0b..9e88bc0bdf5 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -23,7 +23,7 @@ import { } from './public-types'; import { deleteApp -} from 'firebase/app'; +} from './api'; import { ComponentContainer } from '@firebase/component'; @@ -71,12 +71,12 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe new FinalizationRegistry(this.automaticCleanup); if (this._deleteOnDeref !== undefined) { - this._finalizationRegistry.register(this._deleteOnDeref.deref, this) + this._finalizationRegistry.register(this._deleteOnDeref.deref, this); } } private automaticCleanup(serverApp: FirebaseServerAppImpl): void { - deleteApp(serverApp); + void deleteApp(serverApp); } get serverAppConfig(): FirebaseServerAppSettings { From 612f990deecfcf18a7e46ac4d7de2648928c40a5 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 3 Oct 2023 10:58:11 -0400 Subject: [PATCH 08/41] deleteOnDeref type converted from WeakRef to object. --- common/api-review/app.api.md | 2 +- config/tsconfig.base.json | 1 + packages/app/package.json | 3 +++ packages/app/src/api.ts | 10 +++++++++- packages/app/src/errors.ts | 10 +++++++--- packages/app/src/firebaseServerApp.ts | 7 +++---- packages/app/src/public-types.ts | 2 +- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 746fcb017f8..1bd923e7a13 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -80,7 +80,7 @@ export interface FirebaseServerApp extends FirebaseApp { // @public export interface FirebaseServerAppSettings extends FirebaseAppSettings { - deleteOnDeref?: WeakRef; + deleteOnDeref?: object; getCookieCallback: (name: string) => string | undefined; getHeaderCallback: (name: string) => string | undefined; setCookieCallback?: (name: string, value: string) => void; diff --git a/config/tsconfig.base.json b/config/tsconfig.base.json index cf88a789aa2..0b585359a36 100644 --- a/config/tsconfig.base.json +++ b/config/tsconfig.base.json @@ -15,6 +15,7 @@ "es2015.core", "es2017.object", "es2017.string", + "ESNext.WeakRef", ], "module": "ES2015", "moduleResolution": "node", diff --git a/packages/app/package.json b/packages/app/package.json index 80c602e4a82..a72e1ae094f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -7,6 +7,9 @@ "browser": "dist/esm/index.esm2017.js", "module": "dist/esm/index.esm2017.js", "esm5": "dist/esm/index.esm5.js", + "engines": { + "node": ">=14.6.0" + }, "exports": { ".": { "types": "./dist/app-public.d.ts", diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 4319f49423b..10ca565d910 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -257,6 +257,14 @@ export function initializeServerAppInstance( } } + if(serverAppSettings.deleteOnDeref !== undefined) { + if(FinalizationRegistry === undefined) { + throw ERROR_FACTORY.create(AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, { + appName: String(name) + }); + } + } + const container = new ComponentContainer(name); for (const component of _components.values()) { container.addComponent(component); @@ -343,7 +351,7 @@ export function getServerApps(): FirebaseServerApp[] { * @public */ export async function deleteApp(app: FirebaseApp): Promise { - var foundApp = false; + let foundApp = false; const name = app.name; if (_apps.has(name)) { foundApp = true; diff --git a/packages/app/src/errors.ts b/packages/app/src/errors.ts index 25582398663..41099364dcb 100644 --- a/packages/app/src/errors.ts +++ b/packages/app/src/errors.ts @@ -28,14 +28,15 @@ export const enum AppError { IDB_OPEN = 'idb-open', IDB_GET = 'idb-get', IDB_WRITE = 'idb-set', - IDB_DELETE = 'idb-delete' + IDB_DELETE = 'idb-delete', + FINALIZATION_REGISTRY_NOT_SUPPORTED = 'finalization-registry-not-supported' } const ERRORS: ErrorMap = { [AppError.NO_APP]: "No Firebase App '{$appName}' has been created - " + 'call initializeApp() first', - [AppError.BAD_APP_NAME]: "Illegal App name: '{$appName}", + [AppError.BAD_APP_NAME]: "Illegal App name: '{$appName}'", [AppError.DUPLICATE_APP]: "Firebase App named '{$appName}' already exists with different options or config", [AppError.APP_DELETED]: "Firebase App named '{$appName}' already deleted", @@ -53,7 +54,9 @@ const ERRORS: ErrorMap = { [AppError.IDB_WRITE]: 'Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.', [AppError.IDB_DELETE]: - 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.' + 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.', + [AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED]: + "FirebaseServerApp '{$appName}' deleteOnDeref field defined but the runtime does not support the FinalizationRegistry." }; interface ErrorParams { @@ -66,6 +69,7 @@ interface ErrorParams { [AppError.IDB_GET]: { originalErrorMessage?: string }; [AppError.IDB_WRITE]: { originalErrorMessage?: string }; [AppError.IDB_DELETE]: { originalErrorMessage?: string }; + [AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED]: { appName?: string }; } export const ERROR_FACTORY = new ErrorFactory( diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 9e88bc0bdf5..118377f87c8 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -35,7 +35,6 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe private readonly _setCookieCallback?: (name: string, value: string) => void; private readonly _getCookieCallback?: (name: string) => string | undefined; private readonly _getHeaderCallback?: (name: string) => string | undefined; - private readonly _deleteOnDeref?: WeakRef; private _finalizationRegistry: FinalizationRegistry; constructor( @@ -65,13 +64,13 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe this._setCookieCallback = this._serverConfig.setCookieCallback; this._getCookieCallback = this._serverConfig.getCookieCallback; this._getHeaderCallback = this._serverConfig.getHeaderCallback; - this._deleteOnDeref = this._serverConfig.deleteOnDeref; this._finalizationRegistry = new FinalizationRegistry(this.automaticCleanup); - if (this._deleteOnDeref !== undefined) { - this._finalizationRegistry.register(this._deleteOnDeref.deref, this); + if (this._serverConfig.deleteOnDeref !== undefined) { + this._finalizationRegistry.register(this._serverConfig.deleteOnDeref, this); + this._serverConfig.deleteOnDeref = undefined; // Don't keep a strong reference to the object. } } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 38e0cc6c52e..481bd2fb61e 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -202,7 +202,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * If a WeakRef is not provided then the application must clean up the * FirebaseServerApp instance through it's own standard mechanisms. */ - deleteOnDeref?: WeakRef; + deleteOnDeref?: object; } /** From 76f2fe158a0e084e750d1ae37b732a082b087f5b Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 4 Oct 2023 14:42:22 -0400 Subject: [PATCH 09/41] update cookie func signatures. Check for existence of FinalizationRegistry --- common/api-review/app.api.md | 12 +++++------ packages/app-types/index.d.ts | 12 +++++------ packages/app/src/api.ts | 2 +- packages/app/src/errors.ts | 2 +- packages/app/src/firebaseServerApp.ts | 30 ++++++++++++--------------- 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 1bd923e7a13..aa8495de7fb 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -73,17 +73,17 @@ export interface FirebaseOptions { // @public export interface FirebaseServerApp extends FirebaseApp { - invokeGetCookieCallback: (name: string) => string | undefined; - invokeGetHeaderCallback: (name: string) => string | undefined; - invokeSetCookieCallback: (name: string, value: string) => void; + invokeGetCookie: (name: string) => string | undefined; + invokeGetHeader: (name: string) => string | undefined; + invokeSetCookie: (name: string, value: string | undefined, options: object) => void; } // @public export interface FirebaseServerAppSettings extends FirebaseAppSettings { deleteOnDeref?: object; - getCookieCallback: (name: string) => string | undefined; - getHeaderCallback: (name: string) => string | undefined; - setCookieCallback?: (name: string, value: string) => void; + getCookie: (name: string) => string | undefined; + getHeader: (name: string) => string | undefined; + setCookie?: (name: string, value: string | undefined, options: object) => void; } // @internal (undocumented) diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 3e5d1299bc4..165a9e631ff 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -33,9 +33,9 @@ export interface FirebaseAppConfig { } export interface FirebaseServerAppConfig extends FirebaseAppConfig { - getCookieCallback: (name: string) => string|undefined; - setCookieCallback?: (name: string, value: string) => void; - getHeaderCallback: (name: string) => string|undefined; + getCookie: (name: string) => string|undefined; + setCookie?: (name: string, value: string) => void; + getHeader: (name: string) => string|undefined; } export class FirebaseApp { @@ -66,18 +66,18 @@ export class FirebaseServerApp extends FirebaseApp { * A callback that may be invoked by the Firebase SDKs to retrieve cookie data from the server * request object. */ - getCookieCallback: (name: string) => string|undefined; + getCookie: (name: string) => string|undefined; /** * A callback that may be invoked by the Firebase SDKs to set a cookie in the SSR response object. */ - setCookieCallback?: (name: string, value: string) => void; + setCookie?: (name: string, value: string, options: object) => void; /** * A callback that may be invoked by the Firebase SDKs to query a header value from the server * request object. */ - getHeaderCallback: (name: string) => string|undefined; + getHeader: (name: string) => string|undefined; } export interface FirebaseNamespace { diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 10ca565d910..417eefae59b 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -258,7 +258,7 @@ export function initializeServerAppInstance( } if(serverAppSettings.deleteOnDeref !== undefined) { - if(FinalizationRegistry === undefined) { + if (typeof FinalizationRegistry === "undefined") { throw ERROR_FACTORY.create(AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, { appName: String(name) }); diff --git a/packages/app/src/errors.ts b/packages/app/src/errors.ts index 41099364dcb..7876d3e379f 100644 --- a/packages/app/src/errors.ts +++ b/packages/app/src/errors.ts @@ -56,7 +56,7 @@ const ERRORS: ErrorMap = { [AppError.IDB_DELETE]: 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.', [AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED]: - "FirebaseServerApp '{$appName}' deleteOnDeref field defined but the runtime does not support the FinalizationRegistry." + "FirebaseServerApp '{$appName}' deleteOnDeref field defined but the JS runtime does not support FinalizationRegistry." }; interface ErrorParams { diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 118377f87c8..5fc9b3b021e 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -32,9 +32,9 @@ import { DEFAULT_ENTRY_NAME } from './constants'; export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseServerApp { private readonly _serverConfig: FirebaseServerAppSettings; - private readonly _setCookieCallback?: (name: string, value: string) => void; - private readonly _getCookieCallback?: (name: string) => string | undefined; - private readonly _getHeaderCallback?: (name: string) => string | undefined; + private readonly _setCookie?: (name: string, value: string|undefined, options: object) => void; + private readonly _getCookie: (name: string) => string | undefined; + private readonly _getHeader: (name: string) => string | undefined; private _finalizationRegistry: FinalizationRegistry; constructor( @@ -61,9 +61,9 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe ...serverConfig }; - this._setCookieCallback = this._serverConfig.setCookieCallback; - this._getCookieCallback = this._serverConfig.getCookieCallback; - this._getHeaderCallback = this._serverConfig.getHeaderCallback; + this._setCookie = this._serverConfig.setCookie; + this._getCookie = this._serverConfig.getCookie; + this._getHeader = this._serverConfig.getHeader; this._finalizationRegistry = new FinalizationRegistry(this.automaticCleanup); @@ -83,24 +83,20 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe return this._serverConfig; } - invokeSetCookieCallback(cookieName: string, cookieValue: string): void { + invokeSetCookie(cookieName: string, cookieValue: string|undefined, options: object): void { this.checkDestroyed(); - if (this._setCookieCallback !== undefined) { - this._setCookieCallback(cookieName, cookieValue); + if (this._setCookie !== undefined) { + this._setCookie(cookieName, cookieValue, options); } } - invokeGetCookieCallback(cookieName: string): string | undefined { + invokeGetCookie(cookieName: string): string | undefined { this.checkDestroyed(); - if (this._getCookieCallback !== undefined) { - return this._getCookieCallback(cookieName); - } + return this._getCookie(cookieName); } - invokeGetHeaderCallback(headerName: string): string | undefined { + invokeGetHeader(headerName: string): string | undefined { this.checkDestroyed(); - if (this._getHeaderCallback !== undefined) { - return this._getHeaderCallback(headerName); - } + return this._getHeader(headerName); } } From d68f986aeb200787d7f11bf2a85148c259f95257 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 5 Oct 2023 11:34:44 -0400 Subject: [PATCH 10/41] added baseline firebaseServerApp.test.ts --- packages/app/src/firebaseServerApp.test.ts | 145 +++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 packages/app/src/firebaseServerApp.test.ts diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts new file mode 100644 index 00000000000..ae34fba9faa --- /dev/null +++ b/packages/app/src/firebaseServerApp.test.ts @@ -0,0 +1,145 @@ +/** + * @license + * Copyright 2023 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 { expect } from 'chai'; +import '../test/setup'; +import { FirebaseAppImpl } from './firebaseApp'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; +import { ComponentContainer } from '@firebase/component'; + + +const setCookieCb = (name: string, value: string|undefined, options: object ) : void => { + return; +} + +const getCookieCb = (name: string) : string|undefined => { + return; +} + +const getHeaderCb = (name: string) : string|undefined => { + return; +} + +describe('FirebaseServerApp', () => { + it('has various accessors', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings = { + name: "test", + automaticDataCollectionEnabled: false, + setCookie: setCookieCb, + getCookie: getCookieCb, + getHeader: getHeaderCb, + deleteOnDeref: options, + }; + + const firebaseServerAppImpl = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.false; + expect(firebaseServerAppImpl.name).to.equal('test'); + expect(firebaseServerAppImpl.options).to.deep.equal(options); + }); + + it('deep-copies options', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings = { + name: "test", + automaticDataCollectionEnabled: false, + setCookie: setCookieCb, + getCookie: getCookieCb, + getHeader: getHeaderCb, + deleteOnDeref: options, + }; + + const firebaseServerAppImpl = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(firebaseServerAppImpl.options).to.not.equal(options); + expect(firebaseServerAppImpl.options).to.deep.equal(options); + }); + + it('sets automaticDataCollectionEnabled', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings = { + name: "test", + automaticDataCollectionEnabled: false, + setCookie: setCookieCb, + getCookie: getCookieCb, + getHeader: getHeaderCb, + deleteOnDeref: options, + }; + + const firebaseServerAppImpl = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.false; + firebaseServerAppImpl.automaticDataCollectionEnabled = true; + expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.true; + }); + + it('throws accessing any property after being deleted', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings = { + name: "test", + automaticDataCollectionEnabled: false, + setCookie: setCookieCb, + getCookie: getCookieCb, + getHeader: getHeaderCb, + deleteOnDeref: options, + }; + + const firebaseServerAppImpl = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(() => firebaseServerAppImpl.name).to.not.throw(); + (firebaseServerAppImpl as unknown as FirebaseServerAppImpl).isDeleted = true; + + expect(() => { + firebaseServerAppImpl.name; + }).throws("Firebase App named 'test' already deleted"); + expect(() => firebaseServerAppImpl.options).throws( + "Firebase App named 'test' already deleted" + ); + expect(() => firebaseServerAppImpl.automaticDataCollectionEnabled).throws( + "Firebase App named 'test' already deleted" + ); + }); +}); From 09e31571e3f7052d96efe0468165d4d02e7958be Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 5 Oct 2023 11:35:33 -0400 Subject: [PATCH 11/41] update callback method siganatures with shorter names and alt undef params --- packages/app/src/public-types.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 481bd2fb61e..9a299a7bf75 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -85,19 +85,19 @@ export interface FirebaseServerApp extends FirebaseApp { /** * Invokes a callback into the App to retrieve cookie data from the server request object. */ - invokeGetCookieCallback: (name: string) => string|undefined; + invokeGetCookie: (name: string) => string|undefined; /** * Invokes a callback into the App to set cookie data within the server response object. If * no callback was configured when the FirebaseServerApp was created then this operation is a * no-op. */ - invokeSetCookieCallback: (name: string, value: string) => void; + invokeSetCookie: (name: string, value: string|undefined, options: object) => void; /** * Invokes a callback into the App to retrieve header data from the server request object. */ - invokeGetHeaderCallback: (name: string) => string|undefined; + invokeGetHeader: (name: string) => string|undefined; } /** @@ -178,20 +178,24 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * A function callback the Firebase SDK may call to query a cookie value from the server request * object. */ - getCookieCallback: (name: string) => string|undefined; + getCookie: (name: string) => string|undefined; /** - * An optional function callback the Firebase SDK may call to request that a cookie be added to - * the server response cookie object. If this callback is not provided then there will be no - * automatic data propagation to the Firebase SDK running on sthe client. + * An optional function callback the Firebase SDK may call to request that a cookie be + * added to the server response cookie object. If this callback is not provided + * then there will be no automatic data propagation to the Firebase SDK running on + * the client. + * + * A undefined value parameter signifies that the corresponding cookie should be + * deleted. */ - setCookieCallback?: (name: string, value: string) => void; + setCookie?: (name: string, value: string|undefined, options: object) => void; /** * A function callback the Firebase SDK may call to query a header value from the server request * object. */ - getHeaderCallback: (name: string) => string|undefined; + getHeader: (name: string) => string|undefined; /** * An optional WeakRef. If provided, the Firebase SDK will cleanup and destroy From 3750aa43b12bd4522a2d4a82d76bafc5b92b983d Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 10 Oct 2023 11:44:24 -0400 Subject: [PATCH 12/41] added some firebaseServerApp tests --- packages/app/src/api.test.ts | 161 ++++++++++++++++++++++++++++++++++- packages/app/src/api.ts | 2 +- 2 files changed, 159 insertions(+), 4 deletions(-) diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index ab361df6ff1..f5dd398bae1 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -20,6 +20,7 @@ import { stub, spy } from 'sinon'; import '../test/setup'; import { initializeApp, + initializeServerAppInstance, getApps, deleteApp, getApp, @@ -28,7 +29,7 @@ import { onLog } from './api'; import { DEFAULT_ENTRY_NAME } from './constants'; -import { _FirebaseService } from './public-types'; +import { FirebaseServerAppSettings, _FirebaseService } from './public-types'; import { _clearComponents, _components, @@ -54,7 +55,7 @@ describe('API tests', () => { }); describe('initializeApp', () => { - it('creats DEFAULT App', () => { + it('creates DEFAULT App', () => { const app = initializeApp({}); expect(app.name).to.equal(DEFAULT_ENTRY_NAME); }); @@ -91,6 +92,160 @@ describe('API tests', () => { ).to.equal(app); }); + it('throws when creating duplicate DEFAULT Apps with different options', () => { + initializeApp({ + apiKey: 'test1' + }); + expect(() => + initializeApp({ + apiKey: 'test2' + }) + ).throws(/\[DEFAULT\].*exists/i); + }); + + it('throws when creating duplicate named Apps with different options', () => { + const appName = 'MyApp'; + initializeApp( + { + apiKey: 'test1' + }, + appName + ); + expect(() => + initializeApp( + { + apiKey: 'test2' + }, + appName + ) + ).throws(/'MyApp'.*exists/i); + }); + + it('throws when creating duplicate DEFAULT Apps with different config values', () => { + initializeApp( + { + apiKey: 'test1' + }, + { automaticDataCollectionEnabled: true } + ); + expect(() => + initializeApp( + { + apiKey: 'test1' + }, + { automaticDataCollectionEnabled: false } + ) + ).throws(/\[DEFAULT\].*exists/i); + }); + + it('throws when creating duplicate named Apps with different config values', () => { + const appName = 'MyApp'; + initializeApp( + { + apiKey: 'test1' + }, + { name: appName, automaticDataCollectionEnabled: true } + ); + expect(() => + initializeApp( + { + apiKey: 'test1' + }, + { name: appName, automaticDataCollectionEnabled: false } + ) + ).throws(/'MyApp'.*exists/i); + }); + + it('takes an object as the second parameter to create named App', () => { + const appName = 'MyApp'; + const app = initializeApp({}, { name: appName }); + expect(app.name).to.equal(appName); + }); + + it('takes an object as the second parameter to create named App', () => { + const appName = 'MyApp'; + const app = initializeApp({}, { name: appName }); + expect(app.name).to.equal(appName); + }); + + it('sets automaticDataCollectionEnabled', () => { + const app = initializeApp({}, { automaticDataCollectionEnabled: true }); + expect(app.automaticDataCollectionEnabled).to.be.true; + }); + + it('adds registered components to App', () => { + _clearComponents(); + const comp1 = createTestComponent('test1'); + const comp2 = createTestComponent('test2'); + _registerComponent(comp1); + _registerComponent(comp2); + + const app = initializeApp({}) as FirebaseAppImpl; + // -1 here to not count the FirebaseApp provider that's added during initializeApp + expect(app.container.getProviders().length - 1).to.equal( + _components.size + ); + }); + }); + + describe('initializeServerApp', () => { + const setCookieCb = (name: string, value: string|undefined, options: object ) : void => { + return; + } + + const getCookieCb = (name: string) : string|undefined => { + return; + } + + const getHeaderCb = (name: string) : string|undefined => { + return; + } + + it('creates named App', () => { + const options = { + apiKey: 'APIKEY' + }; + + const appName = 'MyApp'; + + const serverAppSettings = { + name: appName, + automaticDataCollectionEnabled: false, + setCookie: setCookieCb, + getCookie: getCookieCb, + getHeader: getHeaderCb, + deleteOnDeref: options, + }; + + const app = initializeServerAppInstance(options, serverAppSettings); + expect(app.name).to.equal(appName); + deleteApp(app); + }); + + it('creates named and DEFAULT App', () => { + const appName = 'MyApp'; + const options = { + apiKey: 'APIKEY' + }; + + let serverAppSettings : FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + setCookie: setCookieCb, + getCookie: getCookieCb, + getHeader: getHeaderCb, + deleteOnDeref: options, + }; + + const app1 = initializeServerAppInstance(options, serverAppSettings); + serverAppSettings.name = appName; + const app2 = initializeServerAppInstance(options, serverAppSettings); + + expect(app1.name).to.equal(DEFAULT_ENTRY_NAME); + expect(app2.name).to.equal(appName); + deleteApp(app1); + deleteApp(app2); + }); + it('throws when creating duplicate DEDAULT Apps with different options', () => { initializeApp({ apiKey: 'test1' @@ -120,7 +275,7 @@ describe('API tests', () => { ).throws(/'MyApp'.*exists/i); }); - it('throws when creating duplicate DEDAULT Apps with different config values', () => { + it('throws when creating duplicate DEFAULT Apps with different config values', () => { initializeApp( { apiKey: 'test1' diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 417eefae59b..3934c06a819 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -237,7 +237,7 @@ export function initializeServerAppInstance( ..._serverAppConfig }; - const name = _serverAppConfig.name; + const name = serverAppSettings.name; if (typeof name !== 'string' || !name) { throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, { appName: String(name) From 6cc428bb746ca6855b587cce65348b2fcaa6c4e4 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 4 Dec 2023 10:59:40 -0500 Subject: [PATCH 13/41] format --- packages/app-types/index.d.ts | 8 +- packages/app/src/api.test.ts | 22 +++--- packages/app/src/api.ts | 89 ++++++++++++---------- packages/app/src/firebaseServerApp.test.ts | 38 ++++----- packages/app/src/firebaseServerApp.ts | 48 ++++++++---- packages/app/src/public-types.ts | 22 ++++-- 6 files changed, 134 insertions(+), 93 deletions(-) diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 165a9e631ff..2a632a68bff 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -33,9 +33,9 @@ export interface FirebaseAppConfig { } export interface FirebaseServerAppConfig extends FirebaseAppConfig { - getCookie: (name: string) => string|undefined; + getCookie: (name: string) => string | undefined; setCookie?: (name: string, value: string) => void; - getHeader: (name: string) => string|undefined; + getHeader: (name: string) => string | undefined; } export class FirebaseApp { @@ -66,7 +66,7 @@ export class FirebaseServerApp extends FirebaseApp { * A callback that may be invoked by the Firebase SDKs to retrieve cookie data from the server * request object. */ - getCookie: (name: string) => string|undefined; + getCookie: (name: string) => string | undefined; /** * A callback that may be invoked by the Firebase SDKs to set a cookie in the SSR response object. @@ -77,7 +77,7 @@ export class FirebaseServerApp extends FirebaseApp { * A callback that may be invoked by the Firebase SDKs to query a header value from the server * request object. */ - getHeader: (name: string) => string|undefined; + getHeader: (name: string) => string | undefined; } export interface FirebaseNamespace { diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index f5dd398bae1..bdee67a620b 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -189,17 +189,21 @@ describe('API tests', () => { }); describe('initializeServerApp', () => { - const setCookieCb = (name: string, value: string|undefined, options: object ) : void => { + const setCookieCb = ( + name: string, + value: string | undefined, + options: object + ): void => { return; - } + }; - const getCookieCb = (name: string) : string|undefined => { + const getCookieCb = (name: string): string | undefined => { return; - } + }; - const getHeaderCb = (name: string) : string|undefined => { + const getHeaderCb = (name: string): string | undefined => { return; - } + }; it('creates named App', () => { const options = { @@ -214,7 +218,7 @@ describe('API tests', () => { setCookie: setCookieCb, getCookie: getCookieCb, getHeader: getHeaderCb, - deleteOnDeref: options, + deleteOnDeref: options }; const app = initializeServerAppInstance(options, serverAppSettings); @@ -228,12 +232,12 @@ describe('API tests', () => { apiKey: 'APIKEY' }; - let serverAppSettings : FirebaseServerAppSettings = { + let serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, setCookie: setCookieCb, getCookie: getCookieCb, getHeader: getHeaderCb, - deleteOnDeref: options, + deleteOnDeref: options }; const app1 = initializeServerAppInstance(options, serverAppSettings); diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 3934c06a819..f4f2e2ad3e2 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -33,7 +33,12 @@ import { import { version } from '../../firebase/package.json'; import { FirebaseAppImpl } from './firebaseApp'; import { FirebaseServerAppImpl } from './firebaseServerApp'; -import { _apps, _components, _registerComponent, _serverApps } from './internal'; +import { + _apps, + _components, + _registerComponent, + _serverApps +} from './internal'; import { logger } from './logger'; import { LogLevelString, @@ -228,52 +233,56 @@ export function initializeApp( * @public */ export function initializeServerAppInstance( - _options: FirebaseOptions, - _serverAppConfig: FirebaseServerAppSettings): FirebaseServerApp { - - const serverAppSettings: FirebaseServerAppSettings = { - name: DEFAULT_ENTRY_NAME, - automaticDataCollectionEnabled: false, - ..._serverAppConfig - }; - - const name = serverAppSettings.name; - if (typeof name !== 'string' || !name) { - throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, { - appName: String(name) - }); - } - - const existingApp = _serverApps.get(name) as FirebaseServerAppImpl; - if (existingApp) { - // return the existing app if options and config deep equal the ones in the existing app. - if ( - deepEqual(_options, existingApp.options) && - deepEqual(serverAppSettings, existingApp.serverAppConfig) - ) { - return existingApp; - } else { - throw ERROR_FACTORY.create(AppError.DUPLICATE_APP, { appName: name }); - } - } + _options: FirebaseOptions, + _serverAppConfig: FirebaseServerAppSettings +): FirebaseServerApp { + const serverAppSettings: FirebaseServerAppSettings = { + name: DEFAULT_ENTRY_NAME, + automaticDataCollectionEnabled: false, + ..._serverAppConfig + }; - if(serverAppSettings.deleteOnDeref !== undefined) { - if (typeof FinalizationRegistry === "undefined") { - throw ERROR_FACTORY.create(AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, { + const name = serverAppSettings.name; + if (typeof name !== 'string' || !name) { + throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, { appName: String(name) }); } - } - const container = new ComponentContainer(name); - for (const component of _components.values()) { - container.addComponent(component); - } + const existingApp = _serverApps.get(name) as FirebaseServerAppImpl; + if (existingApp) { + // return the existing app if options and config deep equal the ones in the existing app. + if ( + deepEqual(_options, existingApp.options) && + deepEqual(serverAppSettings, existingApp.serverAppConfig) + ) { + return existingApp; + } else { + throw ERROR_FACTORY.create(AppError.DUPLICATE_APP, { appName: name }); + } + } + + if (serverAppSettings.deleteOnDeref !== undefined) { + if (typeof FinalizationRegistry === 'undefined') { + throw ERROR_FACTORY.create(AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, { + appName: String(name) + }); + } + } + + const container = new ComponentContainer(name); + for (const component of _components.values()) { + container.addComponent(component); + } - const newApp = new FirebaseServerAppImpl(_options, serverAppSettings, container); - _serverApps.set(name, newApp); + const newApp = new FirebaseServerAppImpl( + _options, + serverAppSettings, + container + ); + _serverApps.set(name, newApp); - return newApp; + return newApp; } /** diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index ae34fba9faa..68108d66d81 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -21,18 +21,21 @@ import { FirebaseAppImpl } from './firebaseApp'; import { FirebaseServerAppImpl } from './firebaseServerApp'; import { ComponentContainer } from '@firebase/component'; - -const setCookieCb = (name: string, value: string|undefined, options: object ) : void => { +const setCookieCb = ( + name: string, + value: string | undefined, + options: object +): void => { return; -} +}; -const getCookieCb = (name: string) : string|undefined => { +const getCookieCb = (name: string): string | undefined => { return; -} +}; -const getHeaderCb = (name: string) : string|undefined => { +const getHeaderCb = (name: string): string | undefined => { return; -} +}; describe('FirebaseServerApp', () => { it('has various accessors', () => { @@ -41,12 +44,12 @@ describe('FirebaseServerApp', () => { }; const serverAppSettings = { - name: "test", + name: 'test', automaticDataCollectionEnabled: false, setCookie: setCookieCb, getCookie: getCookieCb, getHeader: getHeaderCb, - deleteOnDeref: options, + deleteOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( @@ -54,7 +57,7 @@ describe('FirebaseServerApp', () => { serverAppSettings, new ComponentContainer('test') ); - + expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.false; expect(firebaseServerAppImpl.name).to.equal('test'); expect(firebaseServerAppImpl.options).to.deep.equal(options); @@ -66,12 +69,12 @@ describe('FirebaseServerApp', () => { }; const serverAppSettings = { - name: "test", + name: 'test', automaticDataCollectionEnabled: false, setCookie: setCookieCb, getCookie: getCookieCb, getHeader: getHeaderCb, - deleteOnDeref: options, + deleteOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( @@ -90,12 +93,12 @@ describe('FirebaseServerApp', () => { }; const serverAppSettings = { - name: "test", + name: 'test', automaticDataCollectionEnabled: false, setCookie: setCookieCb, getCookie: getCookieCb, getHeader: getHeaderCb, - deleteOnDeref: options, + deleteOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( @@ -115,12 +118,12 @@ describe('FirebaseServerApp', () => { }; const serverAppSettings = { - name: "test", + name: 'test', automaticDataCollectionEnabled: false, setCookie: setCookieCb, getCookie: getCookieCb, getHeader: getHeaderCb, - deleteOnDeref: options, + deleteOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( @@ -130,7 +133,8 @@ describe('FirebaseServerApp', () => { ); expect(() => firebaseServerAppImpl.name).to.not.throw(); - (firebaseServerAppImpl as unknown as FirebaseServerAppImpl).isDeleted = true; + (firebaseServerAppImpl as unknown as FirebaseServerAppImpl).isDeleted = + true; expect(() => { firebaseServerAppImpl.name; diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 5fc9b3b021e..a6d5addcbfa 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -21,18 +21,21 @@ import { FirebaseServerAppSettings, FirebaseOptions } from './public-types'; -import { - deleteApp -} from './api'; -import { - ComponentContainer -} from '@firebase/component'; +import { deleteApp } from './api'; +import { ComponentContainer } from '@firebase/component'; import { FirebaseAppImpl } from './firebaseApp'; import { DEFAULT_ENTRY_NAME } from './constants'; -export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseServerApp { +export class FirebaseServerAppImpl + extends FirebaseAppImpl + implements FirebaseServerApp +{ private readonly _serverConfig: FirebaseServerAppSettings; - private readonly _setCookie?: (name: string, value: string|undefined, options: object) => void; + private readonly _setCookie?: ( + name: string, + value: string | undefined, + options: object + ) => void; private readonly _getCookie: (name: string) => string | undefined; private readonly _getHeader: (name: string) => string | undefined; private _finalizationRegistry: FinalizationRegistry; @@ -43,13 +46,17 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe container: ComponentContainer ) { // Build configuration parameters for the FirebaseAppImpl base class. - const name: string = (serverConfig.name !== undefined) ? serverConfig.name : DEFAULT_ENTRY_NAME; + const name: string = + serverConfig.name !== undefined ? serverConfig.name : DEFAULT_ENTRY_NAME; const automaticDataCollectionEnabled = - (serverConfig.automaticDataCollectionEnabled !== undefined) ? serverConfig.automaticDataCollectionEnabled : false; + serverConfig.automaticDataCollectionEnabled !== undefined + ? serverConfig.automaticDataCollectionEnabled + : false; // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. const config: Required = { - name, automaticDataCollectionEnabled + name, + automaticDataCollectionEnabled }; // Construct the parent FirebaseAppImp object. @@ -57,7 +64,8 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe // Now construct the data for the FirebaseServerAppImpl. this._serverConfig = { - name, automaticDataCollectionEnabled, + name, + automaticDataCollectionEnabled, ...serverConfig }; @@ -65,11 +73,15 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe this._getCookie = this._serverConfig.getCookie; this._getHeader = this._serverConfig.getHeader; - this._finalizationRegistry = - new FinalizationRegistry(this.automaticCleanup); + this._finalizationRegistry = new FinalizationRegistry( + this.automaticCleanup + ); if (this._serverConfig.deleteOnDeref !== undefined) { - this._finalizationRegistry.register(this._serverConfig.deleteOnDeref, this); + this._finalizationRegistry.register( + this._serverConfig.deleteOnDeref, + this + ); this._serverConfig.deleteOnDeref = undefined; // Don't keep a strong reference to the object. } } @@ -83,7 +95,11 @@ export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseSe return this._serverConfig; } - invokeSetCookie(cookieName: string, cookieValue: string|undefined, options: object): void { + invokeSetCookie( + cookieName: string, + cookieValue: string | undefined, + options: object + ): void { this.checkDestroyed(); if (this._setCookie !== undefined) { this._setCookie(cookieName, cookieValue, options); diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 9a299a7bf75..7a1c92042bb 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -85,20 +85,24 @@ export interface FirebaseServerApp extends FirebaseApp { /** * Invokes a callback into the App to retrieve cookie data from the server request object. */ - invokeGetCookie: (name: string) => string|undefined; + invokeGetCookie: (name: string) => string | undefined; /** * Invokes a callback into the App to set cookie data within the server response object. If * no callback was configured when the FirebaseServerApp was created then this operation is a * no-op. */ - invokeSetCookie: (name: string, value: string|undefined, options: object) => void; + invokeSetCookie: ( + name: string, + value: string | undefined, + options: object + ) => void; /** * Invokes a callback into the App to retrieve header data from the server request object. */ - invokeGetHeader: (name: string) => string|undefined; - } + invokeGetHeader: (name: string) => string | undefined; +} /** * @public @@ -178,7 +182,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * A function callback the Firebase SDK may call to query a cookie value from the server request * object. */ - getCookie: (name: string) => string|undefined; + getCookie: (name: string) => string | undefined; /** * An optional function callback the Firebase SDK may call to request that a cookie be @@ -189,13 +193,17 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * A undefined value parameter signifies that the corresponding cookie should be * deleted. */ - setCookie?: (name: string, value: string|undefined, options: object) => void; + setCookie?: ( + name: string, + value: string | undefined, + options: object + ) => void; /** * A function callback the Firebase SDK may call to query a header value from the server request * object. */ - getHeader: (name: string) => string|undefined; + getHeader: (name: string) => string | undefined; /** * An optional WeakRef. If provided, the Firebase SDK will cleanup and destroy From 22b9bd99e2c5b0ddedd1789d0f1c2f6734b8cb6d Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 7 Dec 2023 17:19:07 -0500 Subject: [PATCH 14/41] rework builds, tests need updating --- common/api-review/app.api.md | 2 +- packages/app-types/index.d.ts | 155 ++++++++++++++++++--- packages/app/src/api.test.ts | 8 +- packages/app/src/api.ts | 46 +++--- packages/app/src/firebaseServerApp.test.ts | 28 ---- 5 files changed, 162 insertions(+), 77 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index aa8495de7fb..009add6119c 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -115,7 +115,7 @@ export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSett export function initializeApp(): FirebaseApp; // @public -export function initializeServerAppInstance(_options: FirebaseOptions, _serverAppConfig: FirebaseServerAppSettings): FirebaseServerApp; +export function initializeServerApp(options: FirebaseOptions | FirebaseApp, config: FirebaseServerAppSettings): FirebaseServerApp; // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 2a632a68bff..433127a706c 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -33,9 +33,81 @@ export interface FirebaseAppConfig { } export interface FirebaseServerAppConfig extends FirebaseAppConfig { - getCookie: (name: string) => string | undefined; - setCookie?: (name: string, value: string) => void; - getHeader: (name: string) => string | undefined; + /** + * An optional Auth ID token used to resume a signed in user session from a client + * runtime environment. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be queried via by the application by invoking the + * FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by + * authIdTokenVerified is highly recommended if an Auth ID token is provided. + * + * Once the token has been properly verified then invoking getAuth() will attempt to + * automatically sign in a user with the provided Auth ID Token. + * + * If the token fails verification then a warning is logged and Auth SDK will not + * attempt to sign in a user upon its initalization. + */ + authIdToken?: string; + + /** + * An optional AppCheck token. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be monitored by invoking the + * FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by + * appCheckTokenVerified is highly recommended if an AppCheck token is provided. + * + * If the token has been properly verified then the AppCheck token will be + * automatically used by Firebase SDKs that support App Check. + * + * If the token fails verification then a warning is logged and the token will not + * be used. + */ + appCheckToken?: string; + + /** + * An optional Installation Auth token. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be monitored by invoking the + * FirebaseServerApp.installationAuthTokenVerified(). Awaiting the Promise returned by + * appCheckTokenVerified is highly recommended before initalization any other Firebase + * SDKs. + * + * If the token has been properly verified then the Installation Auth token will be + * automatically used by Firebase SDKs that support Firebase Installations. + * + * If the token fails verification then a warning is logged and the token will not + * be used. + */ + installationAuthToken?: string; + + /** + * An optional object. If provided, the Firebase SDK will use a FinalizationRegistry + * object to monitor its GC status. The Firebase SDK will cleanup and + * delete the corresponding FirebaseServerApp instance when the provided object is + * deleted by the garbage collector. + * + * The intent of this field is to help reduce memory overhead for long-running cloud + * functions executing SSR fulfillment without the customer's app needing to + * orchestrate FirebaseServerApp cleanup. + * + * For instance, in the case that asynchronous operations makes it difficult to + * determine the finality of the server side rendering pass. + * + * If the object is not provided then the application must clean up the + * FirebaseServerApp instance through it's own standard mechanisms by invoking + * deleteApp. + * + * If the app uses provides an object but uses a JavaScript engine that predates the + * support of FinalizationRegistry (introduced in node v14.6.0, for instance), then the + * Firebase SDK will not be able to automatically clean up the FirebaseServerApp + * instance and an error will be thrown. + */ + deleteOnDeref?: object; + + name: ''; } export class FirebaseApp { @@ -61,23 +133,65 @@ export class FirebaseApp { delete(): Promise; } +/** + * A {@link @firebase/app#FirebaseServerApp} holds the initialization information + * for a collection of services running in server enviornments. + * + * Do not call this constructor directly. Instead, use + * {@link (initializeServerAppInstance:1) | initializeServerAppInstance()} to create + * an app. + * + * @public + */ export class FirebaseServerApp extends FirebaseApp { /** - * A callback that may be invoked by the Firebase SDKs to retrieve cookie data from the server - * request object. + * Checks to see if the verification of the authIdToken provided to + * @initializeServerApp has completed. + * + * It is recommend that your application awaits this promise if an authIdToken was + * provided during FirebaseServerApp initialization before invoking getAuth(). If an + * instance of Auth is created before the Auth ID Token is validated, then the token + * will not be used by that instance of the Auth SDK. + * + * The returned Promise is completed immediately if the optional authIdToken parameter + * was omitted from FirebaseServerApp initialization. */ - getCookie: (name: string) => string | undefined; + authIdTokenVerified: () => Promise; /** - * A callback that may be invoked by the Firebase SDKs to set a cookie in the SSR response object. + * Checks to see if the verification of the appCheckToken provided to + * @initializeServerApp has completed. If the optional appCheckToken parameter was + * omitted then the returned Promise is completed immediately. + * + * It is recommend that your application awaits this promise before initializing + * any Firebase products that use AppCheck. The Firebase SDKs will not + * use App Check tokens that are determined to be invalid or those that have not yet + * completed validation. + * + * The returned Promise is completed immediately if the optional appCheckToken + * parameter was omitted from FirebaseServerApp initialization. */ - setCookie?: (name: string, value: string, options: object) => void; + appCheckTokenVerified: () => Promise; /** - * A callback that may be invoked by the Firebase SDKs to query a header value from the server - * request object. + * Checks to see if the verification of the installationAuthToken provided to + * @initializeServerApp has completed. + * + * It is recommend that your application awaits this promise before initializing + * any Firebase products that use Firebase Installations. The Firebase SDKs will not + * use Installation Auth tokens that are determined to be invalid or those that have + * not yet completed validation. + * + * The returned Promise is completed immediately if the optional appCheckToken + * parameter was omitted from FirebaseServerApp initialization. + */ + installationAuthTokenVerified: () => Promise; + + /** + * There is no get for FirebaseServerApp, name is not relevant—however it's always + * a blank string to conform to the FirebaseApp interface */ - getHeader: (name: string) => string | undefined; + name: undefined; } export interface FirebaseNamespace { @@ -104,12 +218,13 @@ export interface FirebaseNamespace { /** * Create (and initialize) a FirebaseServerApp. * - * @param options Options to configure the services used in the App. - * @param config The optional config for your firebase server app + * @param options - Firebase.AppOptions to configure the app's services, or a + * a FirebaseApp instance which contains the AppOptions within. + * @param config The config for your firebase server app. */ - initializeServerAppInstance( - options: FirebaseOptions, - config?: FirebaseServerAppConfig + initializeServerApp( + options: FirebaseOptions | FirebaseApp, + config: FirebaseServerAppConfig ): FirebaseServerApp; app: { @@ -135,17 +250,17 @@ export interface FirebaseNamespace { /** * Retrieve an instance of a FirebaseServerApp. * - * Usage: firebase.serverApp() + * Usage: firebase.serverApp(name) * * @param name The optional name of the server app to return ('[DEFAULT]' if omitted) */ (name?: string): FirebaseServerApp; /** - * For testing FirebaseApp instances: - * app() instanceof firebase.app.App + * For testing FirebaseServerApp instances: + * serverApp() instanceof firebase.app.FirebaseServerApp * - * DO NOT call this constuctor directly (use firebase.app() instead). + * DO NOT call this constuctor directly (use firebase.initializeServerApp() instead). */ serverApp: typeof FirebaseServerApp; }; diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index bdee67a620b..576fbdaff4c 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -20,7 +20,7 @@ import { stub, spy } from 'sinon'; import '../test/setup'; import { initializeApp, - initializeServerAppInstance, + initializeServerApp, getApps, deleteApp, getApp, @@ -221,7 +221,7 @@ describe('API tests', () => { deleteOnDeref: options }; - const app = initializeServerAppInstance(options, serverAppSettings); + const app = initializeServerApp(options, serverAppSettings); expect(app.name).to.equal(appName); deleteApp(app); }); @@ -240,9 +240,9 @@ describe('API tests', () => { deleteOnDeref: options }; - const app1 = initializeServerAppInstance(options, serverAppSettings); + const app1 = initializeServerApp(options, serverAppSettings); serverAppSettings.name = appName; - const app2 = initializeServerAppInstance(options, serverAppSettings); + const app2 = initializeServerApp(options, serverAppSettings); expect(app1.name).to.equal(DEFAULT_ENTRY_NAME); expect(app2.name).to.equal(appName); diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index f4f2e2ad3e2..a73053cdd5c 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -183,7 +183,7 @@ export function initializeApp( * Creates and initializes a {@link @firebase/app#FirebaseServerApp} instance. * * The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in - * server environments only. + * server side rendering environments only. * * See * {@link @@ -196,10 +196,10 @@ export function initializeApp( * @example * ```javascript * - * // Initialize default app + * // Initialize a FirebaseServerApp. * // Retrieve your own options values by adding a web app on * // https://console.firebase.google.com - * initializeServerAppInstance({ + * initializeServerApp({ * apiKey: "AIza....", // Auth / General Use * authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect * databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database @@ -207,33 +207,25 @@ export function initializeApp( * messagingSenderId: "123456789" // Cloud Messaging * }, * { - * headers: requestHeaders + * authIdToken: "Your Auth ID Token" * }); * ``` * - * @example - * ```javascript - * - * // Initialize another server app - * const otherApp = initializeServerAppInstance({ - * databaseURL: "https://.firebaseio.com", - * storageBucket: ".appspot.com" - * }, - * { - * name: "otherApp", - * headers: requestHeaders - * }); - * ``` - * - * @param options - Options to configure the app's services. + * @param options - Firebase.AppOptions to configure the app's services, or a + * a FirebaseApp instance which contains the AppOptions within. * @param config - FirebaseServerApp configuration. * * @returns The initialized FirebaseServerApp. * * @public */ -export function initializeServerAppInstance( - _options: FirebaseOptions, +export function initializeServerApp( + options: FirebaseOptions | FirebaseApp, + config: FirebaseServerAppSettings +): FirebaseServerApp; + +export function initializeServerApp( + _options: FirebaseOptions | FirebaseApp, _serverAppConfig: FirebaseServerAppSettings ): FirebaseServerApp { const serverAppSettings: FirebaseServerAppSettings = { @@ -249,11 +241,18 @@ export function initializeServerAppInstance( }); } + let appOptions: FirebaseOptions; + if ((_options as FirebaseApp).options !== undefined) { + appOptions = (_options as FirebaseApp).options; + } else { + appOptions = _options as FirebaseOptions; + } + const existingApp = _serverApps.get(name) as FirebaseServerAppImpl; if (existingApp) { // return the existing app if options and config deep equal the ones in the existing app. if ( - deepEqual(_options, existingApp.options) && + deepEqual(appOptions, existingApp.options) && deepEqual(serverAppSettings, existingApp.serverAppConfig) ) { return existingApp; @@ -276,11 +275,10 @@ export function initializeServerAppInstance( } const newApp = new FirebaseServerAppImpl( - _options, + appOptions, serverAppSettings, container ); - _serverApps.set(name, newApp); return newApp; } diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index 68108d66d81..052af50d369 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -21,22 +21,6 @@ import { FirebaseAppImpl } from './firebaseApp'; import { FirebaseServerAppImpl } from './firebaseServerApp'; import { ComponentContainer } from '@firebase/component'; -const setCookieCb = ( - name: string, - value: string | undefined, - options: object -): void => { - return; -}; - -const getCookieCb = (name: string): string | undefined => { - return; -}; - -const getHeaderCb = (name: string): string | undefined => { - return; -}; - describe('FirebaseServerApp', () => { it('has various accessors', () => { const options = { @@ -46,9 +30,6 @@ describe('FirebaseServerApp', () => { const serverAppSettings = { name: 'test', automaticDataCollectionEnabled: false, - setCookie: setCookieCb, - getCookie: getCookieCb, - getHeader: getHeaderCb, deleteOnDeref: options }; @@ -71,9 +52,6 @@ describe('FirebaseServerApp', () => { const serverAppSettings = { name: 'test', automaticDataCollectionEnabled: false, - setCookie: setCookieCb, - getCookie: getCookieCb, - getHeader: getHeaderCb, deleteOnDeref: options }; @@ -95,9 +73,6 @@ describe('FirebaseServerApp', () => { const serverAppSettings = { name: 'test', automaticDataCollectionEnabled: false, - setCookie: setCookieCb, - getCookie: getCookieCb, - getHeader: getHeaderCb, deleteOnDeref: options }; @@ -120,9 +95,6 @@ describe('FirebaseServerApp', () => { const serverAppSettings = { name: 'test', automaticDataCollectionEnabled: false, - setCookie: setCookieCb, - getCookie: getCookieCb, - getHeader: getHeaderCb, deleteOnDeref: options }; From 3c3986633c697b1bb87fb61198cdb5fff1129c99 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 14 Dec 2023 15:41:16 -0500 Subject: [PATCH 15/41] another API update and doc generation --- common/api-review/app.api.md | 16 +- docs-devsite/app.firebaseserverapp.md | 83 ++++++++++ docs-devsite/app.firebaseserverappsettings.md | 104 +++++++++++++ docs-devsite/app.md | 74 +++++++++ packages/app/src/api.ts | 15 +- packages/app/src/firebaseServerApp.ts | 60 +++----- packages/app/src/public-types.ts | 143 +++++++++++++----- 7 files changed, 402 insertions(+), 93 deletions(-) create mode 100644 docs-devsite/app.firebaseserverapp.md create mode 100644 docs-devsite/app.firebaseserverappsettings.md diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 009add6119c..e0b76154f26 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -73,17 +73,19 @@ export interface FirebaseOptions { // @public export interface FirebaseServerApp extends FirebaseApp { - invokeGetCookie: (name: string) => string | undefined; - invokeGetHeader: (name: string) => string | undefined; - invokeSetCookie: (name: string, value: string | undefined, options: object) => void; + appCheckTokenVerified: () => Promise; + authIdTokenVerified: () => Promise; + installationTokenVerified: () => Promise; + name: string; } // @public export interface FirebaseServerAppSettings extends FirebaseAppSettings { - deleteOnDeref?: object; - getCookie: (name: string) => string | undefined; - getHeader: (name: string) => string | undefined; - setCookie?: (name: string, value: string | undefined, options: object) => void; + appCheckToken?: string; + authIdToken?: string; + installationsAuthToken?: string; + name: ""; + releaseOnDeref?: object; } // @internal (undocumented) diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md new file mode 100644 index 00000000000..71d223867f6 --- /dev/null +++ b/docs-devsite/app.firebaseserverapp.md @@ -0,0 +1,83 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FirebaseServerApp interface +A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments. + +Do not call this constructor directly. Instead, use to create an app. + +Signature: + +```typescript +export interface FirebaseServerApp extends FirebaseApp +``` +Extends: [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appCheckTokenVerified](./app.firebaseserverapp.md#firebaseserverappappchecktokenverified) | () => Promise<void> | Checks to see if the verification of the appCheckToken provided to has completed. If the optional appCheckToken parameter was omitted then the returned Promise is completed immediately.It is recommend that your application awaits this promise before initializing any Firebase products that use AppCheck. The Firebase SDKs will not use App Check tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | +| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the verification of the authIdToken provided to has completed.It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | +| [installationTokenVerified](./app.firebaseserverapp.md#firebaseserverappinstallationtokenverified) | () => Promise<void> | Checks to see if the verification of the installationToken provided to has completed.It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | +| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no get for FirebaseServerApp, name is not relevant—however it's always a blank string to conform to the FirebaseApp interface | + +## FirebaseServerApp.appCheckTokenVerified + +Checks to see if the verification of the appCheckToken provided to has completed. If the optional appCheckToken parameter was omitted then the returned Promise is completed immediately. + +It is recommend that your application awaits this promise before initializing any Firebase products that use AppCheck. The Firebase SDKs will not use App Check tokens that are determined to be invalid or those that have not yet completed validation. + +The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. + +Signature: + +```typescript +appCheckTokenVerified: () => Promise; +``` + +## FirebaseServerApp.authIdTokenVerified + +Checks to see if the verification of the authIdToken provided to has completed. + +It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK. + +The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. + +Signature: + +```typescript +authIdTokenVerified: () => Promise; +``` + +## FirebaseServerApp.installationTokenVerified + +Checks to see if the verification of the installationToken provided to has completed. + +It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation. + +The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. + +Signature: + +```typescript +installationTokenVerified: () => Promise; +``` + +## FirebaseServerApp.name + +There is no get for FirebaseServerApp, name is not relevant—however it's always a blank string to conform to the FirebaseApp interface + +Signature: + +```typescript +name: string; +``` diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md new file mode 100644 index 00000000000..66726403030 --- /dev/null +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -0,0 +1,104 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FirebaseServerAppSettings interface +Configuration options given to [initializeServerApp()](./app.md#initializeserverapp) + +Signature: + +```typescript +export interface FirebaseServerAppSettings extends FirebaseAppSettings +``` +Extends: [FirebaseAppSettings](./app.firebaseappsettings.md#firebaseappsettings_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appCheckToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsappchecktoken) | string | An optional AppCheck token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided.If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check.If the token fails verification then a warning is logged and the token will not be used. | +| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided.Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token.If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. | +| [installationsAuthToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsinstallationsauthtoken) | string | An optional Installation Auth token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs.If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations.If the token fails verification then a warning is logged and the token will not be used. | +| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | "" | There is no get for FirebaseServerApp, and so the name cannot be provided as it can in parent interface FirebaseAppSettings. Force the name to blank string by default. | +| [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or.The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted.If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp.If the app provides an object in this parameter, but the application is executed in a JavaScript engine that predates the support of FinalizationRegistry (introduced in node v14.6.0, for instance), then the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. | + +## FirebaseServerAppSettings.appCheckToken + +An optional AppCheck token. + +If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided. + +If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check. + +If the token fails verification then a warning is logged and the token will not be used. + +Signature: + +```typescript +appCheckToken?: string; +``` + +## FirebaseServerAppSettings.authIdToken + +An optional Auth ID token used to resume a signed in user session from a client runtime environment. + +If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided. + +Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token. + +If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. + +Signature: + +```typescript +authIdToken?: string; +``` + +## FirebaseServerAppSettings.installationsAuthToken + +An optional Installation Auth token. + +If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs. + +If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations. + +If the token fails verification then a warning is logged and the token will not be used. + +Signature: + +```typescript +installationsAuthToken?: string; +``` + +## FirebaseServerAppSettings.name + +There is no get for FirebaseServerApp, and so the name cannot be provided as it can in parent interface FirebaseAppSettings. Force the name to blank string by default. + +Signature: + +```typescript +name: ""; +``` + +## FirebaseServerAppSettings.releaseOnDeref + +An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or. + +The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted. + +If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp. + +If the app provides an object in this parameter, but the application is executed in a JavaScript engine that predates the support of FinalizationRegistry (introduced in node v14.6.0, for instance), then the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. + +Signature: + +```typescript +releaseOnDeref?: object; +``` diff --git a/docs-devsite/app.md b/docs-devsite/app.md index babb7d7bd2b..c27a49ef91f 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -22,6 +22,7 @@ This package coordinates the communication between the different Firebase compon | [deleteApp(app)](./app.md#deleteapp) | Renders this app unusable and frees the resources of all associated services. | | function() | | [getApps()](./app.md#getapps) | A (read-only) array of all initialized apps. | +| [getServerApps()](./app.md#getserverapps) | A (read-only) array of all initialized server apps. | | [initializeApp()](./app.md#initializeapp) | Creates and initializes a FirebaseApp instance. | | function(libraryKeyOrName...) | | [registerVersion(libraryKeyOrName, version, variant)](./app.md#registerversion) | Registers a library's name and version for platform logging purposes. | @@ -34,6 +35,7 @@ This package coordinates the communication between the different Firebase compon | function(options...) | | [initializeApp(options, name)](./app.md#initializeapp) | Creates and initializes a [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) instance.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | | [initializeApp(options, config)](./app.md#initializeapp) | Creates and initializes a FirebaseApp instance. | +| [initializeServerApp(options, config)](./app.md#initializeserverapp) | Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance.The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | ## Interfaces @@ -42,11 +44,14 @@ This package coordinates the communication between the different Firebase compon | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | A [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) holds the initialization information for a collection of services.Do not call this constructor directly. Instead, use [initializeApp()](./app.md#initializeapp) to create an app. | | [FirebaseAppSettings](./app.firebaseappsettings.md#firebaseappsettings_interface) | Configuration options given to [initializeApp()](./app.md#initializeapp) | | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) | Firebase configuration object. Contains a set of parameters required by services in order to successfully communicate with Firebase server APIs and to associate client data with your Firebase project and Firebase application. Typically this object is populated by the Firebase console at project setup. See also: [Learn about the Firebase config object](https://firebase.google.com/docs/web/setup#config-object). | +| [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) | A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments.Do not call this constructor directly. Instead, use to create an app. | +| [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | Configuration options given to [initializeServerApp()](./app.md#initializeserverapp) | ## Variables | Variable | Description | | --- | --- | +| [\_serverApps](./app.md#_serverapps) | | | [SDK\_VERSION](./app.md#sdk_version) | The current SDK version. | ## deleteApp() @@ -96,6 +101,19 @@ export declare function getApps(): FirebaseApp[]; [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface)\[\] +## getServerApps() + +A (read-only) array of all initialized server apps. + +Signature: + +```typescript +export declare function getServerApps(): FirebaseServerApp[]; +``` +Returns: + +[FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface)\[\] + ## initializeApp() Creates and initializes a FirebaseApp instance. @@ -295,6 +313,62 @@ export declare function initializeApp(options: FirebaseOptions, config?: Firebas [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) +## initializeServerApp() + +Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance. + +The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. + +See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. + +Signature: + +```typescript +export declare function initializeServerApp(options: FirebaseOptions | FirebaseApp, config: FirebaseServerAppSettings): FirebaseServerApp; +``` + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) \| [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | Firebase.AppOptions to configure the app's services, or a a FirebaseApp instance which contains the AppOptions within. | +| config | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | FirebaseServerApp configuration. | + +Returns: + +[FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) + +The initialized FirebaseServerApp. + +### Example + + +```javascript + +// Initialize a FirebaseServerApp. +// Retrieve your own options values by adding a web app on +// https://console.firebase.google.com +initializeServerApp({ + apiKey: "AIza....", // Auth / General Use + authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect + databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database + storageBucket: "YOUR_APP.appspot.com", // Storage + messagingSenderId: "123456789" // Cloud Messaging + }, + { + authIdToken: "Your Auth ID Token" + }); + +``` + +## \_serverApps + +Signature: + +```typescript +_serverApps: Map +``` + ## SDK\_VERSION The current SDK version. diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index a73053cdd5c..23628224f75 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -229,7 +229,6 @@ export function initializeServerApp( _serverAppConfig: FirebaseServerAppSettings ): FirebaseServerApp { const serverAppSettings: FirebaseServerAppSettings = { - name: DEFAULT_ENTRY_NAME, automaticDataCollectionEnabled: false, ..._serverAppConfig }; @@ -261,11 +260,9 @@ export function initializeServerApp( } } - if (serverAppSettings.deleteOnDeref !== undefined) { + if (serverAppSettings.releaseOnDeref !== undefined) { if (typeof FinalizationRegistry === 'undefined') { - throw ERROR_FACTORY.create(AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, { - appName: String(name) - }); + throw ERROR_FACTORY.create(AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, { }); } } @@ -332,14 +329,6 @@ export function getApps(): FirebaseApp[] { return Array.from(_apps.values()); } -/** - * A (read-only) array of all initialized server apps. - * @public - */ -export function getServerApps(): FirebaseServerApp[] { - return Array.from(_serverApps.values()); -} - /** * Renders this app unusable and frees the resources of all associated * services. diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index a6d5addcbfa..c0a70be3763 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -24,30 +24,20 @@ import { import { deleteApp } from './api'; import { ComponentContainer } from '@firebase/component'; import { FirebaseAppImpl } from './firebaseApp'; -import { DEFAULT_ENTRY_NAME } from './constants'; export class FirebaseServerAppImpl extends FirebaseAppImpl implements FirebaseServerApp { private readonly _serverConfig: FirebaseServerAppSettings; - private readonly _setCookie?: ( - name: string, - value: string | undefined, - options: object - ) => void; - private readonly _getCookie: (name: string) => string | undefined; - private readonly _getHeader: (name: string) => string | undefined; private _finalizationRegistry: FinalizationRegistry; constructor( - options: FirebaseOptions, + options: FirebaseOptions | FirebaseAppImpl, serverConfig: FirebaseServerAppSettings, container: ComponentContainer ) { // Build configuration parameters for the FirebaseAppImpl base class. - const name: string = - serverConfig.name !== undefined ? serverConfig.name : DEFAULT_ENTRY_NAME; const automaticDataCollectionEnabled = serverConfig.automaticDataCollectionEnabled !== undefined ? serverConfig.automaticDataCollectionEnabled @@ -55,38 +45,40 @@ export class FirebaseServerAppImpl // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. const config: Required = { - name, + name: serverConfig.name, automaticDataCollectionEnabled }; - // Construct the parent FirebaseAppImp object. - super(options, config, container); + if((options as FirebaseOptions).apiKey !== undefined) { + // Construct the parent FirebaseAppImp object. + super(options as FirebaseOptions, config, container); + } else { + const appImpl : FirebaseAppImpl = options as FirebaseAppImpl; + + super(appImpl.options, config, container); + } // Now construct the data for the FirebaseServerAppImpl. this._serverConfig = { - name, automaticDataCollectionEnabled, ...serverConfig }; - this._setCookie = this._serverConfig.setCookie; - this._getCookie = this._serverConfig.getCookie; - this._getHeader = this._serverConfig.getHeader; - this._finalizationRegistry = new FinalizationRegistry( this.automaticCleanup ); - if (this._serverConfig.deleteOnDeref !== undefined) { + if (this._serverConfig.releaseOnDeref !== undefined) { this._finalizationRegistry.register( - this._serverConfig.deleteOnDeref, + this._serverConfig.releaseOnDeref, this ); - this._serverConfig.deleteOnDeref = undefined; // Don't keep a strong reference to the object. + this._serverConfig.releaseOnDeref = undefined; // Don't keep a strong reference to the object. } } private automaticCleanup(serverApp: FirebaseServerAppImpl): void { + // TODO: implement reference counting. void deleteApp(serverApp); } @@ -95,24 +87,18 @@ export class FirebaseServerAppImpl return this._serverConfig; } - invokeSetCookie( - cookieName: string, - cookieValue: string | undefined, - options: object - ): void { - this.checkDestroyed(); - if (this._setCookie !== undefined) { - this._setCookie(cookieName, cookieValue, options); - } + authIdTokenVerified() : Promise { + // TODO + return Promise.resolve(); } - invokeGetCookie(cookieName: string): string | undefined { - this.checkDestroyed(); - return this._getCookie(cookieName); + appCheckTokenVerified() : Promise { + // TODO + return Promise.resolve(); } - invokeGetHeader(headerName: string): string | undefined { - this.checkDestroyed(); - return this._getHeader(headerName); + installationTokenVerified() : Promise { + // TODO + return Promise.resolve(); } } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 7a1c92042bb..080e5728ef1 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -83,25 +83,54 @@ export interface FirebaseApp { */ export interface FirebaseServerApp extends FirebaseApp { /** - * Invokes a callback into the App to retrieve cookie data from the server request object. + * Checks to see if the verification of the authIdToken provided to + * @initializeServerApp has completed. + * + * It is recommend that your application awaits this promise if an authIdToken was + * provided during FirebaseServerApp initialization before invoking getAuth(). If an + * instance of Auth is created before the Auth ID Token is validated, then the token + * will not be used by that instance of the Auth SDK. + * + * The returned Promise is completed immediately if the optional authIdToken parameter + * was omitted from FirebaseServerApp initialization. + */ + authIdTokenVerified: () => Promise; + + /** + * Checks to see if the verification of the appCheckToken provided to + * @initializeServerApp has completed. If the optional appCheckToken parameter was + * omitted then the returned Promise is completed immediately. + * + * It is recommend that your application awaits this promise before initializing + * any Firebase products that use AppCheck. The Firebase SDKs will not + * use App Check tokens that are determined to be invalid or those that have not yet + * completed validation. + * + * The returned Promise is completed immediately if the optional appCheckToken + * parameter was omitted from FirebaseServerApp initialization. */ - invokeGetCookie: (name: string) => string | undefined; + appCheckTokenVerified: () => Promise; /** - * Invokes a callback into the App to set cookie data within the server response object. If - * no callback was configured when the FirebaseServerApp was created then this operation is a - * no-op. + * Checks to see if the verification of the installationToken provided to + * @initializeServerApp has completed. + * + * It is recommend that your application awaits this promise before initializing + * any Firebase products that use Firebase Installations. The Firebase SDKs will not + * use Installation Auth tokens that are determined to be invalid or those that have + * not yet completed validation. + * + * The returned Promise is completed immediately if the optional appCheckToken + * parameter was omitted from FirebaseServerApp initialization. */ - invokeSetCookie: ( - name: string, - value: string | undefined, - options: object - ) => void; + + installationTokenVerified: () => Promise; /** - * Invokes a callback into the App to retrieve header data from the server request object. + * There is no get for FirebaseServerApp, name is not relevant—however it's always + * a blank string to conform to the FirebaseApp interface */ - invokeGetHeader: (name: string) => string | undefined; + name : string; } /** @@ -179,42 +208,84 @@ export interface FirebaseAppSettings { */ export interface FirebaseServerAppSettings extends FirebaseAppSettings { /** - * A function callback the Firebase SDK may call to query a cookie value from the server request - * object. + * An optional Auth ID token used to resume a signed in user session from a client + * runtime environment. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be queried via by the application by invoking the + * FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by + * authIdTokenVerified is highly recommended if an Auth ID token is provided. + * + * Once the token has been properly verified then invoking getAuth() will attempt to + * automatically sign in a user with the provided Auth ID Token. + * + * If the token fails verification then a warning is logged and Auth SDK will not + * attempt to sign in a user upon its initalization. */ - getCookie: (name: string) => string | undefined; + authIdToken?: string; /** - * An optional function callback the Firebase SDK may call to request that a cookie be - * added to the server response cookie object. If this callback is not provided - * then there will be no automatic data propagation to the Firebase SDK running on - * the client. + * An optional AppCheck token. * - * A undefined value parameter signifies that the corresponding cookie should be - * deleted. + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be monitored by invoking the + * FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by + * appCheckTokenVerified is highly recommended if an AppCheck token is provided. + * + * If the token has been properly verified then the AppCheck token will be + * automatically used by Firebase SDKs that support App Check. + * + * If the token fails verification then a warning is logged and the token will not + * be used. */ - setCookie?: ( - name: string, - value: string | undefined, - options: object - ) => void; + appCheckToken?: string; /** - * A function callback the Firebase SDK may call to query a header value from the server request - * object. + * An optional Installation Auth token. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be monitored by invoking the + * FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by + * appCheckTokenVerified is highly recommended before initalization any other Firebase + * SDKs. + * + * If the token has been properly verified then the Installation Auth token will be + * automatically used by Firebase SDKs that support Firebase Installations. + * + * If the token fails verification then a warning is logged and the token will not + * be used. */ - getHeader: (name: string) => string | undefined; + installationsAuthToken?: string; /** - * An optional WeakRef. If provided, the Firebase SDK will cleanup and destroy - * itself when the object pointed to by the WeakRef is destroyed. This field is - * used to help reduce memory overhead for long-running cloud functions executing SSR - * fulfillment. + * An optional object. If provided, the Firebase SDK will use a FinalizationRegistry + * object to monitor the Garbage Collection status of the provided object, and the + * Firebase SDK will release its refrence on the FirebaseServerApp instance when the + * provided object is collected. or. + * + * The intent of this field is to help reduce memory overhead for long-running cloud + * functions executing SSR fulfillment without the customer's app needing to + * orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp + * instances may reused if they're identical to a previously generated one that has + * yet to be deleted. + * + * If the object is not provided then the application must clean up the + * FirebaseServerApp instance through the applicationss own standard mechanisms by + * invoking deleteApp. * - * If a WeakRef is not provided then the application must clean up the - * FirebaseServerApp instance through it's own standard mechanisms. + * If the app provides an object in this parameter, but the application is + * executed in a JavaScript engine that predates the support of FinalizationRegistry + * (introduced in node v14.6.0, for instance), then the Firebase SDK will not be able + * to automatically clean up the FirebaseServerApp instance and an error will be + * thrown. + */ + releaseOnDeref?: object; + + /** + * There is no get for FirebaseServerApp, and so the name cannot be provided as it can + * in parent interface FirebaseAppSettings. Force the name to blank string by default. */ - deleteOnDeref?: object; + name: ""; } /** From a7d111a86f0d89cc14ddbfbe72801b00f52f7e2d Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 15 Dec 2023 09:40:03 -0500 Subject: [PATCH 16/41] reflect new API proposal in source --- common/api-review/app.api.md | 5 +- docs-devsite/app.firebaseserverapp.md | 4 +- docs-devsite/app.firebaseserverappsettings.md | 6 +- docs-devsite/app.md | 14 -- packages/app/src/api.test.ts | 149 +----------------- packages/app/src/firebaseServerApp.test.ts | 27 ++-- packages/app/src/firebaseServerApp.ts | 2 +- packages/app/src/public-types.ts | 11 +- 8 files changed, 30 insertions(+), 188 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index e0b76154f26..2f9a101956b 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -84,7 +84,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { appCheckToken?: string; authIdToken?: string; installationsAuthToken?: string; - name: ""; + name?: ""; releaseOnDeref?: object; } @@ -104,9 +104,6 @@ export function getApps(): FirebaseApp[]; // @internal (undocumented) export function _getProvider(app: FirebaseApp, name: T): Provider; -// @public -export function getServerApps(): FirebaseServerApp[]; - // @public export function initializeApp(options: FirebaseOptions, name?: string): FirebaseApp; diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md index 71d223867f6..cf41ee0a633 100644 --- a/docs-devsite/app.firebaseserverapp.md +++ b/docs-devsite/app.firebaseserverapp.md @@ -28,7 +28,7 @@ export interface FirebaseServerApp extends FirebaseApp | [appCheckTokenVerified](./app.firebaseserverapp.md#firebaseserverappappchecktokenverified) | () => Promise<void> | Checks to see if the verification of the appCheckToken provided to has completed. If the optional appCheckToken parameter was omitted then the returned Promise is completed immediately.It is recommend that your application awaits this promise before initializing any Firebase products that use AppCheck. The Firebase SDKs will not use App Check tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | | [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the verification of the authIdToken provided to has completed.It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | | [installationTokenVerified](./app.firebaseserverapp.md#firebaseserverappinstallationtokenverified) | () => Promise<void> | Checks to see if the verification of the installationToken provided to has completed.It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | -| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no get for FirebaseServerApp, name is not relevant—however it's always a blank string to conform to the FirebaseApp interface | +| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. | ## FirebaseServerApp.appCheckTokenVerified @@ -74,7 +74,7 @@ installationTokenVerified: () => Promise; ## FirebaseServerApp.name -There is no get for FirebaseServerApp, name is not relevant—however it's always a blank string to conform to the FirebaseApp interface +There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. Signature: diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index 66726403030..78ea46696c7 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -26,7 +26,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings | [appCheckToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsappchecktoken) | string | An optional AppCheck token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided.If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check.If the token fails verification then a warning is logged and the token will not be used. | | [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided.Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token.If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. | | [installationsAuthToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsinstallationsauthtoken) | string | An optional Installation Auth token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs.If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations.If the token fails verification then a warning is logged and the token will not be used. | -| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | "" | There is no get for FirebaseServerApp, and so the name cannot be provided as it can in parent interface FirebaseAppSettings. Force the name to blank string by default. | +| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | "" | There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | | [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or.The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted.If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp.If the app provides an object in this parameter, but the application is executed in a JavaScript engine that predates the support of FinalizationRegistry (introduced in node v14.6.0, for instance), then the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. | ## FirebaseServerAppSettings.appCheckToken @@ -79,12 +79,12 @@ installationsAuthToken?: string; ## FirebaseServerAppSettings.name -There is no get for FirebaseServerApp, and so the name cannot be provided as it can in parent interface FirebaseAppSettings. Force the name to blank string by default. +There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Signature: ```typescript -name: ""; +name?: ""; ``` ## FirebaseServerAppSettings.releaseOnDeref diff --git a/docs-devsite/app.md b/docs-devsite/app.md index c27a49ef91f..00c414ea95a 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -22,7 +22,6 @@ This package coordinates the communication between the different Firebase compon | [deleteApp(app)](./app.md#deleteapp) | Renders this app unusable and frees the resources of all associated services. | | function() | | [getApps()](./app.md#getapps) | A (read-only) array of all initialized apps. | -| [getServerApps()](./app.md#getserverapps) | A (read-only) array of all initialized server apps. | | [initializeApp()](./app.md#initializeapp) | Creates and initializes a FirebaseApp instance. | | function(libraryKeyOrName...) | | [registerVersion(libraryKeyOrName, version, variant)](./app.md#registerversion) | Registers a library's name and version for platform logging purposes. | @@ -101,19 +100,6 @@ export declare function getApps(): FirebaseApp[]; [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface)\[\] -## getServerApps() - -A (read-only) array of all initialized server apps. - -Signature: - -```typescript -export declare function getServerApps(): FirebaseServerApp[]; -``` -Returns: - -[FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface)\[\] - ## initializeApp() Creates and initializes a FirebaseApp instance. diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 576fbdaff4c..9c9670a44b6 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -189,160 +189,19 @@ describe('API tests', () => { }); describe('initializeServerApp', () => { - const setCookieCb = ( - name: string, - value: string | undefined, - options: object - ): void => { - return; - }; - - const getCookieCb = (name: string): string | undefined => { - return; - }; - - const getHeaderCb = (name: string): string | undefined => { - return; - }; - it('creates named App', () => { const options = { apiKey: 'APIKEY' }; - const appName = 'MyApp'; - - const serverAppSettings = { - name: appName, + const serverAppSettings : FirebaseServerAppSettings = { + name: "", automaticDataCollectionEnabled: false, - setCookie: setCookieCb, - getCookie: getCookieCb, - getHeader: getHeaderCb, - deleteOnDeref: options + releaseOnDeref: options }; const app = initializeServerApp(options, serverAppSettings); - expect(app.name).to.equal(appName); - deleteApp(app); - }); - - it('creates named and DEFAULT App', () => { - const appName = 'MyApp'; - const options = { - apiKey: 'APIKEY' - }; - - let serverAppSettings: FirebaseServerAppSettings = { - automaticDataCollectionEnabled: false, - setCookie: setCookieCb, - getCookie: getCookieCb, - getHeader: getHeaderCb, - deleteOnDeref: options - }; - - const app1 = initializeServerApp(options, serverAppSettings); - serverAppSettings.name = appName; - const app2 = initializeServerApp(options, serverAppSettings); - - expect(app1.name).to.equal(DEFAULT_ENTRY_NAME); - expect(app2.name).to.equal(appName); - deleteApp(app1); - deleteApp(app2); - }); - - it('throws when creating duplicate DEDAULT Apps with different options', () => { - initializeApp({ - apiKey: 'test1' - }); - expect(() => - initializeApp({ - apiKey: 'test2' - }) - ).throws(/\[DEFAULT\].*exists/i); - }); - - it('throws when creating duplicate named Apps with different options', () => { - const appName = 'MyApp'; - initializeApp( - { - apiKey: 'test1' - }, - appName - ); - expect(() => - initializeApp( - { - apiKey: 'test2' - }, - appName - ) - ).throws(/'MyApp'.*exists/i); - }); - - it('throws when creating duplicate DEFAULT Apps with different config values', () => { - initializeApp( - { - apiKey: 'test1' - }, - { automaticDataCollectionEnabled: true } - ); - expect(() => - initializeApp( - { - apiKey: 'test1' - }, - { automaticDataCollectionEnabled: false } - ) - ).throws(/\[DEFAULT\].*exists/i); - }); - - it('throws when creating duplicate named Apps with different config values', () => { - const appName = 'MyApp'; - initializeApp( - { - apiKey: 'test1' - }, - { name: appName, automaticDataCollectionEnabled: true } - ); - expect(() => - initializeApp( - { - apiKey: 'test1' - }, - { name: appName, automaticDataCollectionEnabled: false } - ) - ).throws(/'MyApp'.*exists/i); - }); - - it('takes an object as the second parameter to create named App', () => { - const appName = 'MyApp'; - const app = initializeApp({}, { name: appName }); - expect(app.name).to.equal(appName); - }); - - it('takes an object as the second parameter to create named App', () => { - const appName = 'MyApp'; - const app = initializeApp({}, { name: appName }); - expect(app.name).to.equal(appName); - }); - - it('sets automaticDataCollectionEnabled', () => { - const app = initializeApp({}, { automaticDataCollectionEnabled: true }); - expect(app.automaticDataCollectionEnabled).to.be.true; - }); - - it('adds registered components to App', () => { - _clearComponents(); - const comp1 = createTestComponent('test1'); - const comp2 = createTestComponent('test2'); - _registerComponent(comp1); - _registerComponent(comp2); - - const app = initializeApp({}) as FirebaseAppImpl; - // -1 here to not count the FirebaseApp provider that's added during initializeApp - expect(app.container.getProviders().length - 1).to.equal( - _components.size - ); + expect(app).to.not.equal(null); }); }); diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index 052af50d369..c23f2fdd0a6 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -17,9 +17,9 @@ import { expect } from 'chai'; import '../test/setup'; -import { FirebaseAppImpl } from './firebaseApp'; -import { FirebaseServerAppImpl } from './firebaseServerApp'; import { ComponentContainer } from '@firebase/component'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; +import { FirebaseServerAppSettings } from './public-types'; describe('FirebaseServerApp', () => { it('has various accessors', () => { @@ -27,10 +27,10 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings = { - name: 'test', + const serverAppSettings : FirebaseServerAppSettings = { + name: "", automaticDataCollectionEnabled: false, - deleteOnDeref: options + releaseOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( @@ -49,10 +49,10 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings = { - name: 'test', + const serverAppSettings : FirebaseServerAppSettings = { + name: "", automaticDataCollectionEnabled: false, - deleteOnDeref: options + releaseOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( @@ -70,10 +70,10 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings = { - name: 'test', + const serverAppSettings : FirebaseServerAppSettings = { + name: "", automaticDataCollectionEnabled: false, - deleteOnDeref: options + releaseOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( @@ -92,10 +92,9 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings = { - name: 'test', + const serverAppSettings : FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, - deleteOnDeref: options + releaseOnDeref: options }; const firebaseServerAppImpl = new FirebaseServerAppImpl( diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index c0a70be3763..23b2b0fe334 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -45,7 +45,7 @@ export class FirebaseServerAppImpl // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. const config: Required = { - name: serverConfig.name, + name: "", automaticDataCollectionEnabled }; diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 080e5728ef1..9c4367de957 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -127,8 +127,9 @@ export interface FirebaseServerApp extends FirebaseApp { installationTokenVerified: () => Promise; /** - * There is no get for FirebaseServerApp, name is not relevant—however it's always - * a blank string to conform to the FirebaseApp interface + * There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here + * so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this + * string will always be empty for FirebaseServerApp instances. */ name : string; } @@ -282,10 +283,10 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { releaseOnDeref?: object; /** - * There is no get for FirebaseServerApp, and so the name cannot be provided as it can - * in parent interface FirebaseAppSettings. Force the name to blank string by default. + * There is no get for FirebaseServerApps, so the name is not relevant. however it's always + * a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. */ - name: ""; + name?: ""; } /** From 042793499910b241672bb14d59f949b330509cad Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 18 Dec 2023 15:33:08 -0500 Subject: [PATCH 17/41] FirbaseServerAppSettings name?: undefined --- common/api-review/app.api.md | 2 +- packages/app/src/api.test.ts | 1 - packages/app/src/firebaseServerApp.test.ts | 3 --- packages/app/src/public-types.ts | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 2f9a101956b..3f4f4bd9e6b 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -84,7 +84,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { appCheckToken?: string; authIdToken?: string; installationsAuthToken?: string; - name?: ""; + name?: undefined; releaseOnDeref?: object; } diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 9c9670a44b6..77851b8925a 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -195,7 +195,6 @@ describe('API tests', () => { }; const serverAppSettings : FirebaseServerAppSettings = { - name: "", automaticDataCollectionEnabled: false, releaseOnDeref: options }; diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index c23f2fdd0a6..51003b2388b 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -28,7 +28,6 @@ describe('FirebaseServerApp', () => { }; const serverAppSettings : FirebaseServerAppSettings = { - name: "", automaticDataCollectionEnabled: false, releaseOnDeref: options }; @@ -50,7 +49,6 @@ describe('FirebaseServerApp', () => { }; const serverAppSettings : FirebaseServerAppSettings = { - name: "", automaticDataCollectionEnabled: false, releaseOnDeref: options }; @@ -71,7 +69,6 @@ describe('FirebaseServerApp', () => { }; const serverAppSettings : FirebaseServerAppSettings = { - name: "", automaticDataCollectionEnabled: false, releaseOnDeref: options }; diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 9c4367de957..9743df9b947 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -286,7 +286,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * There is no get for FirebaseServerApps, so the name is not relevant. however it's always * a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. */ - name?: ""; + name?: undefined; } /** From e1468157e3447793ab348598b6fadcb83d2971a6 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 20 Dec 2023 13:52:16 -0500 Subject: [PATCH 18/41] remove firebaseServerApp name --- packages/app/src/api.test.ts | 2 +- packages/app/src/api.ts | 49 ++++++++++-------- packages/app/src/errors.ts | 2 + packages/app/src/firebaseServerApp.test.ts | 58 ++++++++++++++++------ packages/app/src/firebaseServerApp.ts | 28 ++++++++--- packages/app/src/public-types.ts | 38 +++++++------- 6 files changed, 113 insertions(+), 64 deletions(-) diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 77851b8925a..8970fcbb2a3 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -194,7 +194,7 @@ describe('API tests', () => { apiKey: 'APIKEY' }; - const serverAppSettings : FirebaseServerAppSettings = { + const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, releaseOnDeref: options }; diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 23628224f75..f6f1b54efe7 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -233,13 +233,6 @@ export function initializeServerApp( ..._serverAppConfig }; - const name = serverAppSettings.name; - if (typeof name !== 'string' || !name) { - throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, { - appName: String(name) - }); - } - let appOptions: FirebaseOptions; if ((_options as FirebaseApp).options !== undefined) { appOptions = (_options as FirebaseApp).options; @@ -247,26 +240,40 @@ export function initializeServerApp( appOptions = _options as FirebaseOptions; } - const existingApp = _serverApps.get(name) as FirebaseServerAppImpl; - if (existingApp) { - // return the existing app if options and config deep equal the ones in the existing app. - if ( - deepEqual(appOptions, existingApp.options) && - deepEqual(serverAppSettings, existingApp.serverAppConfig) - ) { - return existingApp; - } else { - throw ERROR_FACTORY.create(AppError.DUPLICATE_APP, { appName: name }); - } - } + const nameObj = { + authIdToken: _serverAppConfig?.authIdToken, + appCheckToken: _serverAppConfig?.appCheckToken, + installationsAuthToken: _serverAppConfig?.installationsAuthToken, + ...appOptions + }; if (serverAppSettings.releaseOnDeref !== undefined) { if (typeof FinalizationRegistry === 'undefined') { - throw ERROR_FACTORY.create(AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, { }); + throw ERROR_FACTORY.create( + AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, + {} + ); } } - const container = new ComponentContainer(name); + // TODO: move this into util.js. + const hashCode = (s: string): number => { + return [...s].reduce( + (hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0, + 0 + ); + }; + + const nameString = '' + hashCode(JSON.stringify(nameObj)); + const existingApp = _serverApps.get(nameString) as FirebaseServerAppImpl; + if (existingApp) { + // TODO: + // 1: Register a new reference to finalization registry. + // 2: Incrememnt reference count. + return existingApp; + } + + const container = new ComponentContainer(nameString); for (const component of _components.values()) { container.addComponent(component); } diff --git a/packages/app/src/errors.ts b/packages/app/src/errors.ts index 7876d3e379f..2fb0bad0cf6 100644 --- a/packages/app/src/errors.ts +++ b/packages/app/src/errors.ts @@ -22,6 +22,7 @@ export const enum AppError { BAD_APP_NAME = 'bad-app-name', DUPLICATE_APP = 'duplicate-app', APP_DELETED = 'app-deleted', + SERVER_APP_DELETED = 'server-app-deleted', NO_OPTIONS = 'no-options', INVALID_APP_ARGUMENT = 'invalid-app-argument', INVALID_LOG_ARGUMENT = 'invalid-log-argument', @@ -40,6 +41,7 @@ const ERRORS: ErrorMap = { [AppError.DUPLICATE_APP]: "Firebase App named '{$appName}' already exists with different options or config", [AppError.APP_DELETED]: "Firebase App named '{$appName}' already deleted", + [AppError.SERVER_APP_DELETED]: 'Firebase Server App has been deleted', [AppError.NO_OPTIONS]: 'Need to provide options, when not being deployed to hosting via source.', [AppError.INVALID_APP_ARGUMENT]: diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index 51003b2388b..a95f7e42d8a 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -27,7 +27,7 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings : FirebaseServerAppSettings = { + const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, releaseOnDeref: options }; @@ -39,7 +39,6 @@ describe('FirebaseServerApp', () => { ); expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.false; - expect(firebaseServerAppImpl.name).to.equal('test'); expect(firebaseServerAppImpl.options).to.deep.equal(options); }); @@ -48,7 +47,7 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings : FirebaseServerAppSettings = { + const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, releaseOnDeref: options }; @@ -68,7 +67,7 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings : FirebaseServerAppSettings = { + const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, releaseOnDeref: options }; @@ -89,29 +88,56 @@ describe('FirebaseServerApp', () => { apiKey: 'APIKEY' }; - const serverAppSettings : FirebaseServerAppSettings = { + const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, releaseOnDeref: options }; - const firebaseServerAppImpl = new FirebaseServerAppImpl( + const app = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(() => app.options).to.not.throw(); + (app as unknown as FirebaseServerAppImpl).isDeleted = true; + + expect(() => app.options).throws('Firebase Server App has been deleted'); + + expect(() => app.automaticDataCollectionEnabled).throws( + 'Firebase Server App has been deleted' + ); + }); + + it('throws accessing any method after being deleted', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const app = new FirebaseServerAppImpl( options, serverAppSettings, new ComponentContainer('test') ); - expect(() => firebaseServerAppImpl.name).to.not.throw(); - (firebaseServerAppImpl as unknown as FirebaseServerAppImpl).isDeleted = - true; + expect(() => app.authIdTokenVerified).to.not.throw(); + (app as unknown as FirebaseServerAppImpl).isDeleted = true; - expect(() => { - firebaseServerAppImpl.name; - }).throws("Firebase App named 'test' already deleted"); - expect(() => firebaseServerAppImpl.options).throws( - "Firebase App named 'test' already deleted" + expect(() => app.authIdTokenVerified()).throws( + 'Firebase Server App has been deleted' ); - expect(() => firebaseServerAppImpl.automaticDataCollectionEnabled).throws( - "Firebase App named 'test' already deleted" + + expect(() => app.appCheckTokenVerified()).throws( + 'Firebase Server App has been deleted' + ); + + expect(() => app.installationTokenVerified()).throws( + 'Firebase Server App has been deleted' ); }); }); diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 23b2b0fe334..cc4264ae504 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -24,6 +24,7 @@ import { import { deleteApp } from './api'; import { ComponentContainer } from '@firebase/component'; import { FirebaseAppImpl } from './firebaseApp'; +import { ERROR_FACTORY, AppError } from './errors'; export class FirebaseServerAppImpl extends FirebaseAppImpl @@ -45,16 +46,16 @@ export class FirebaseServerAppImpl // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. const config: Required = { - name: "", + name: '', automaticDataCollectionEnabled }; - if((options as FirebaseOptions).apiKey !== undefined) { + if ((options as FirebaseOptions).apiKey !== undefined) { // Construct the parent FirebaseAppImp object. super(options as FirebaseOptions, config, container); } else { - const appImpl : FirebaseAppImpl = options as FirebaseAppImpl; - + const appImpl: FirebaseAppImpl = options as FirebaseAppImpl; + super(appImpl.options, config, container); } @@ -87,18 +88,31 @@ export class FirebaseServerAppImpl return this._serverConfig; } - authIdTokenVerified() : Promise { + authIdTokenVerified(): Promise { + this.checkDestroyed(); // TODO return Promise.resolve(); } - appCheckTokenVerified() : Promise { + appCheckTokenVerified(): Promise { + this.checkDestroyed(); // TODO return Promise.resolve(); } - installationTokenVerified() : Promise { + installationTokenVerified(): Promise { + this.checkDestroyed(); // TODO return Promise.resolve(); } + + /** + * This function will throw an Error if the App has already been deleted - + * use before performing API actions on the App. + */ + protected checkDestroyed(): void { + if (this.isDeleted) { + throw ERROR_FACTORY.create(AppError.SERVER_APP_DELETED); + } + } } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 9743df9b947..f179e31dac3 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -84,13 +84,13 @@ export interface FirebaseApp { export interface FirebaseServerApp extends FirebaseApp { /** * Checks to see if the verification of the authIdToken provided to - * @initializeServerApp has completed. + * @initializeServerApp has completed. * * It is recommend that your application awaits this promise if an authIdToken was * provided during FirebaseServerApp initialization before invoking getAuth(). If an * instance of Auth is created before the Auth ID Token is validated, then the token * will not be used by that instance of the Auth SDK. - * + * * The returned Promise is completed immediately if the optional authIdToken parameter * was omitted from FirebaseServerApp initialization. */ @@ -105,7 +105,7 @@ export interface FirebaseServerApp extends FirebaseApp { * any Firebase products that use AppCheck. The Firebase SDKs will not * use App Check tokens that are determined to be invalid or those that have not yet * completed validation. - * + * * The returned Promise is completed immediately if the optional appCheckToken * parameter was omitted from FirebaseServerApp initialization. */ @@ -113,17 +113,17 @@ export interface FirebaseServerApp extends FirebaseApp { /** * Checks to see if the verification of the installationToken provided to - * @initializeServerApp has completed. + * @initializeServerApp has completed. * * It is recommend that your application awaits this promise before initializing * any Firebase products that use Firebase Installations. The Firebase SDKs will not * use Installation Auth tokens that are determined to be invalid or those that have * not yet completed validation. - * + * * The returned Promise is completed immediately if the optional appCheckToken * parameter was omitted from FirebaseServerApp initialization. */ - + installationTokenVerified: () => Promise; /** @@ -131,7 +131,7 @@ export interface FirebaseServerApp extends FirebaseApp { * so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this * string will always be empty for FirebaseServerApp instances. */ - name : string; + name: string; } /** @@ -214,12 +214,12 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * * If provided, the FirebaseServerApp instance will work to validate the token. The * result of the validation can be queried via by the application by invoking the - * FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by + * FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by * authIdTokenVerified is highly recommended if an Auth ID token is provided. * * Once the token has been properly verified then invoking getAuth() will attempt to * automatically sign in a user with the provided Auth ID Token. - * + * * If the token fails verification then a warning is logged and Auth SDK will not * attempt to sign in a user upon its initalization. */ @@ -230,7 +230,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * * If provided, the FirebaseServerApp instance will work to validate the token. The * result of the validation can be monitored by invoking the - * FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by + * FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by * appCheckTokenVerified is highly recommended if an AppCheck token is provided. * * If the token has been properly verified then the AppCheck token will be @@ -239,37 +239,37 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * If the token fails verification then a warning is logged and the token will not * be used. */ - appCheckToken?: string; + appCheckToken?: string; /** * An optional Installation Auth token. * * If provided, the FirebaseServerApp instance will work to validate the token. The * result of the validation can be monitored by invoking the - * FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by - * appCheckTokenVerified is highly recommended before initalization any other Firebase + * FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by + * appCheckTokenVerified is highly recommended before initalization any other Firebase * SDKs. * * If the token has been properly verified then the Installation Auth token will be * automatically used by Firebase SDKs that support Firebase Installations. - * + * * If the token fails verification then a warning is logged and the token will not * be used. */ - installationsAuthToken?: string; + installationsAuthToken?: string; /** * An optional object. If provided, the Firebase SDK will use a FinalizationRegistry * object to monitor the Garbage Collection status of the provided object, and the * Firebase SDK will release its refrence on the FirebaseServerApp instance when the * provided object is collected. or. - * + * * The intent of this field is to help reduce memory overhead for long-running cloud * functions executing SSR fulfillment without the customer's app needing to - * orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp + * orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp * instances may reused if they're identical to a previously generated one that has * yet to be deleted. - * + * * If the object is not provided then the application must clean up the * FirebaseServerApp instance through the applicationss own standard mechanisms by * invoking deleteApp. @@ -280,7 +280,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * to automatically clean up the FirebaseServerApp instance and an error will be * thrown. */ - releaseOnDeref?: object; + releaseOnDeref?: object; /** * There is no get for FirebaseServerApps, so the name is not relevant. however it's always From e9f28edcf84abe04a95aec47c986635288c6cff5 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 20 Dec 2023 16:06:34 -0500 Subject: [PATCH 19/41] remove engines from package.json --- packages/app/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 22614372671..a736724ccc4 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -7,9 +7,6 @@ "browser": "dist/esm/index.esm2017.js", "module": "dist/esm/index.esm2017.js", "esm5": "dist/esm/index.esm5.js", - "engines": { - "node": ">=14.6.0" - }, "exports": { ".": { "types": "./dist/app-public.d.ts", From a13f41804105276f898f908ee50b000e995ff950 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 21 Dec 2023 10:34:26 -0500 Subject: [PATCH 20/41] FirebaseApp / Options typeguard. More tests. --- common/api-review/app.api.md | 3 +++ packages/app/src/api.test.ts | 49 ++++++++++++++++++++++++++++++------ packages/app/src/api.ts | 7 +++--- packages/app/src/internal.ts | 20 ++++++++++++++- 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 3f4f4bd9e6b..266e7730610 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -116,6 +116,9 @@ export function initializeApp(): FirebaseApp; // @public export function initializeServerApp(options: FirebaseOptions | FirebaseApp, config: FirebaseServerAppSettings): FirebaseServerApp; +// @internal (undocumented) +export function _isFirebaseApp(obj: FirebaseApp | FirebaseOptions): obj is FirebaseApp; + // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 8970fcbb2a3..4312acb9f1d 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -162,12 +162,6 @@ describe('API tests', () => { expect(app.name).to.equal(appName); }); - it('takes an object as the second parameter to create named App', () => { - const appName = 'MyApp'; - const app = initializeApp({}, { name: appName }); - expect(app.name).to.equal(appName); - }); - it('sets automaticDataCollectionEnabled', () => { const app = initializeApp({}, { automaticDataCollectionEnabled: true }); expect(app.automaticDataCollectionEnabled).to.be.true; @@ -189,11 +183,34 @@ describe('API tests', () => { }); describe('initializeServerApp', () => { - it('creates named App', () => { + it('creates FirebaseServerApp with options', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = {}; + + const app = initializeServerApp(options, serverAppSettings); + expect(app).to.not.equal(null); + expect(app.automaticDataCollectionEnabled).to.be.false; + }); + + it('creates FirebaseServerApp with automaticDataCollectionEnabled', () => { const options = { apiKey: 'APIKEY' }; + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: true + }; + + const app = initializeServerApp(options, serverAppSettings); + expect(app).to.not.equal(null); + expect(app.automaticDataCollectionEnabled).to.be.true; + }); + + it('creates FirebaseServerApp with releaseOnDeref', () => { + const options = { apiKey: 'APIKEY' }; const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, releaseOnDeref: options @@ -201,6 +218,24 @@ describe('API tests', () => { const app = initializeServerApp(options, serverAppSettings); expect(app).to.not.equal(null); + expect(app.automaticDataCollectionEnabled).to.be.false; + }); + + it('creates FirebaseServerApp with FirebaseApp', () => { + const options = { + apiKey: 'test1' + }; + const standardApp = initializeApp(options); + expect(standardApp.name).to.equal(DEFAULT_ENTRY_NAME); + expect(standardApp.options.apiKey).to.equal('test1'); + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false + }; + + const serverApp = initializeServerApp(standardApp, serverAppSettings); + expect(serverApp).to.not.equal(null); + expect(serverApp.options.apiKey).to.equal('test1'); }); }); diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index f6f1b54efe7..1d68e11cf91 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -36,6 +36,7 @@ import { FirebaseServerAppImpl } from './firebaseServerApp'; import { _apps, _components, + _isFirebaseApp, _registerComponent, _serverApps } from './internal'; @@ -234,10 +235,10 @@ export function initializeServerApp( }; let appOptions: FirebaseOptions; - if ((_options as FirebaseApp).options !== undefined) { - appOptions = (_options as FirebaseApp).options; + if (_isFirebaseApp(_options)) { + appOptions = _options.options; } else { - appOptions = _options as FirebaseOptions; + appOptions = _options; } const nameObj = { diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 4d7a82a238c..94af4b9434c 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -15,7 +15,11 @@ * limitations under the License. */ -import { FirebaseApp, FirebaseServerApp } from './public-types'; +import { + FirebaseApp, + FirebaseOptions, + FirebaseServerApp +} from './public-types'; import { Component, Provider, Name } from '@firebase/component'; import { logger } from './logger'; import { DEFAULT_ENTRY_NAME } from './constants'; @@ -137,6 +141,20 @@ export function _removeServiceInstance( _getProvider(app, name).clearInstance(instanceIdentifier); } +/** + * + * @param obj - an object of type FirebaseApp or FirebaseOptions. + * + * @returns true if the provide object is of type FirebaseApp. + * + * @internal + */ +export function _isFirebaseApp( + obj: FirebaseApp | FirebaseOptions +): obj is FirebaseApp { + return (obj as FirebaseApp).options !== undefined; +} + /** * Test only * From 8a745d1fcd1a225e96839ed80f18d724049fc66e Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 21 Dec 2023 10:51:09 -0500 Subject: [PATCH 21/41] Update FirebaseServerAppConfig name to be undefined. --- packages/app-types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 433127a706c..2a4147a166b 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -107,7 +107,7 @@ export interface FirebaseServerAppConfig extends FirebaseAppConfig { */ deleteOnDeref?: object; - name: ''; + name: undefined; } export class FirebaseApp { From 7b02583a1aca73d7a3c19273550b5baf247ad768 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Sun, 31 Dec 2023 13:44:44 -0500 Subject: [PATCH 22/41] throw if firebaseServerApp constructed in browser. Remove lib ESNext from app tsconfig --- packages/app/src/api.ts | 7 ++++++- packages/app/src/errors.ts | 7 +++++-- packages/app/tsconfig.json | 3 +-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 1d68e11cf91..e3812fd7538 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -48,7 +48,7 @@ import { LogOptions, setUserLogHandler } from '@firebase/logger'; -import { deepEqual, getDefaultAppConfig } from '@firebase/util'; +import { deepEqual, getDefaultAppConfig, isBrowser } from '@firebase/util'; export { FirebaseError } from '@firebase/util'; @@ -229,6 +229,11 @@ export function initializeServerApp( _options: FirebaseOptions | FirebaseApp, _serverAppConfig: FirebaseServerAppSettings ): FirebaseServerApp { + if (isBrowser()) { + // FirebaseServerApps aren't designed to be run in browsers. + throw ERROR_FACTORY.create(AppError.INVALID_SERVER_APP_ENVIRONMENT); + } + const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, ..._serverAppConfig diff --git a/packages/app/src/errors.ts b/packages/app/src/errors.ts index 2fb0bad0cf6..0149ef3dcb1 100644 --- a/packages/app/src/errors.ts +++ b/packages/app/src/errors.ts @@ -30,7 +30,8 @@ export const enum AppError { IDB_GET = 'idb-get', IDB_WRITE = 'idb-set', IDB_DELETE = 'idb-delete', - FINALIZATION_REGISTRY_NOT_SUPPORTED = 'finalization-registry-not-supported' + FINALIZATION_REGISTRY_NOT_SUPPORTED = 'finalization-registry-not-supported', + INVALID_SERVER_APP_ENVIRONMENT = 'invalid-server-app-environment' } const ERRORS: ErrorMap = { @@ -58,7 +59,9 @@ const ERRORS: ErrorMap = { [AppError.IDB_DELETE]: 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.', [AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED]: - "FirebaseServerApp '{$appName}' deleteOnDeref field defined but the JS runtime does not support FinalizationRegistry." + 'FirebaseServerApp deleteOnDeref field defined but the JS runtime does not support FinalizationRegistry.', + [AppError.INVALID_SERVER_APP_ENVIRONMENT]: + 'FirebaseServerApp is not for use in browser environments.' }; interface ErrorParams { diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 79d5153105c..735ea623511 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -2,8 +2,7 @@ "extends": "../../config/tsconfig.base.json", "compilerOptions": { "outDir": "dist", - "downlevelIteration": true, - "lib": ["ESNext"], + "downlevelIteration": true }, "exclude": [ "dist/**/*" From d86eeb7a69af085abe9ce9b33709880f24bb26c7 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Sun, 31 Dec 2023 14:10:43 -0500 Subject: [PATCH 23/41] throw in initializeServerApp if invoked in a browser env. --- packages/app/src/api.test.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 4312acb9f1d..7faabc1e1a7 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -40,6 +40,7 @@ import { createTestComponent } from '../test/util'; import { Component, ComponentType } from '@firebase/component'; import { Logger } from '@firebase/logger'; import { FirebaseAppImpl } from './firebaseApp'; +import { isBrowser } from '@firebase/util'; declare module '@firebase/component' { interface NameServiceMapping { @@ -184,6 +185,23 @@ describe('API tests', () => { describe('initializeServerApp', () => { it('creates FirebaseServerApp with options', () => { + if (isBrowser()) { + const options = { + apiKey: 'APIKEY' + }; + const serverAppSettings: FirebaseServerAppSettings = {}; + expect(() => initializeServerApp(options, serverAppSettings)).throws( + /FirebaseServerApp is not for use in browser environments./ + ); + } + }); + + it('creates FirebaseServerApp with options', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + const options = { apiKey: 'APIKEY' }; @@ -196,6 +214,11 @@ describe('API tests', () => { }); it('creates FirebaseServerApp with automaticDataCollectionEnabled', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + const options = { apiKey: 'APIKEY' }; @@ -210,6 +233,11 @@ describe('API tests', () => { }); it('creates FirebaseServerApp with releaseOnDeref', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + const options = { apiKey: 'APIKEY' }; const serverAppSettings: FirebaseServerAppSettings = { automaticDataCollectionEnabled: false, @@ -222,6 +250,11 @@ describe('API tests', () => { }); it('creates FirebaseServerApp with FirebaseApp', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + const options = { apiKey: 'test1' }; From b34b8782641b6620cae9390829a3078207b3765d Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Sun, 31 Dec 2023 14:27:14 -0500 Subject: [PATCH 24/41] Update index.d.ts revert name: undefined. --- packages/app-types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 2a4147a166b..433127a706c 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -107,7 +107,7 @@ export interface FirebaseServerAppConfig extends FirebaseAppConfig { */ deleteOnDeref?: object; - name: undefined; + name: ''; } export class FirebaseApp { From 4aeb9a6809f9b04d4fd56a7414680c3c1ce7fc9f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Sun, 31 Dec 2023 14:40:40 -0500 Subject: [PATCH 25/41] Update index.d.ts Swap undefineds. --- packages/app-types/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index 433127a706c..b59ab631251 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -107,7 +107,7 @@ export interface FirebaseServerAppConfig extends FirebaseAppConfig { */ deleteOnDeref?: object; - name: ''; + name: undefined; } export class FirebaseApp { @@ -191,7 +191,7 @@ export class FirebaseServerApp extends FirebaseApp { * There is no get for FirebaseServerApp, name is not relevant—however it's always * a blank string to conform to the FirebaseApp interface */ - name: undefined; + name: ''; } export interface FirebaseNamespace { From 04af9312b446555f47251138920fc6b95574c68a Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 3 Jan 2024 10:13:01 -0500 Subject: [PATCH 26/41] Remove the new FirebaseServerApp from the app-types. Remove the new FirebaseServerApp from the app-types. App-types are meant only for compat, and FirebaseServerApp will not be part of compat. --- docs-devsite/app.firebaseserverappsettings.md | 6 +- docs-devsite/app.md | 7 +- packages/app-types/index.d.ts | 176 ------------------ 3 files changed, 7 insertions(+), 182 deletions(-) diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index 78ea46696c7..54ba13baa11 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -10,7 +10,7 @@ https://github.com/firebase/firebase-js-sdk {% endcomment %} # FirebaseServerAppSettings interface -Configuration options given to [initializeServerApp()](./app.md#initializeserverapp) +Configuration options given to [initializeServerApp()](./app.md#initializeserverapp_30ab697) Signature: @@ -26,7 +26,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings | [appCheckToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsappchecktoken) | string | An optional AppCheck token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided.If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check.If the token fails verification then a warning is logged and the token will not be used. | | [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided.Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token.If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. | | [installationsAuthToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsinstallationsauthtoken) | string | An optional Installation Auth token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs.If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations.If the token fails verification then a warning is logged and the token will not be used. | -| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | "" | There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | +| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | | [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or.The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted.If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp.If the app provides an object in this parameter, but the application is executed in a JavaScript engine that predates the support of FinalizationRegistry (introduced in node v14.6.0, for instance), then the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. | ## FirebaseServerAppSettings.appCheckToken @@ -84,7 +84,7 @@ There is no get for FirebaseServerApps, so the name is not relevant. however it' Signature: ```typescript -name?: ""; +name?: undefined; ``` ## FirebaseServerAppSettings.releaseOnDeref diff --git a/docs-devsite/app.md b/docs-devsite/app.md index 95c88f8f46e..b12c29819c9 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -34,6 +34,7 @@ This package coordinates the communication between the different Firebase compon | function(options, ...) | | [initializeApp(options, name)](./app.md#initializeapp_cb2f5e1) | Creates and initializes a [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) instance.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | | [initializeApp(options, config)](./app.md#initializeapp_079e917) | Creates and initializes a FirebaseApp instance. | +| [initializeServerApp(options, config)](./app.md#initializeserverapp_30ab697) | Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance.The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | ## Interfaces @@ -43,7 +44,7 @@ This package coordinates the communication between the different Firebase compon | [FirebaseAppSettings](./app.firebaseappsettings.md#firebaseappsettings_interface) | Configuration options given to [initializeApp()](./app.md#initializeapp_cb2f5e1) | | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) | Firebase configuration object. Contains a set of parameters required by services in order to successfully communicate with Firebase server APIs and to associate client data with your Firebase project and Firebase application. Typically this object is populated by the Firebase console at project setup. See also: [Learn about the Firebase config object](https://firebase.google.com/docs/web/setup#config-object). | | [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) | A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments.Do not call this constructor directly. Instead, use to create an app. | -| [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | Configuration options given to [initializeServerApp()](./app.md#initializeserverapp) | +| [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | Configuration options given to [initializeServerApp()](./app.md#initializeserverapp_30ab697) | ## Variables @@ -312,7 +313,7 @@ export declare function initializeApp(options: FirebaseOptions, config?: Firebas [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) -## initializeServerApp() +### initializeServerApp(options, config) {:#initializeserverapp_30ab697} Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance. @@ -326,7 +327,7 @@ See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_fi export declare function initializeServerApp(options: FirebaseOptions | FirebaseApp, config: FirebaseServerAppSettings): FirebaseServerApp; ``` -### Parameters +#### Parameters | Parameter | Type | Description | | --- | --- | --- | diff --git a/packages/app-types/index.d.ts b/packages/app-types/index.d.ts index b59ab631251..72b8fa68f9d 100644 --- a/packages/app-types/index.d.ts +++ b/packages/app-types/index.d.ts @@ -32,84 +32,6 @@ export interface FirebaseAppConfig { automaticDataCollectionEnabled?: boolean; } -export interface FirebaseServerAppConfig extends FirebaseAppConfig { - /** - * An optional Auth ID token used to resume a signed in user session from a client - * runtime environment. - * - * If provided, the FirebaseServerApp instance will work to validate the token. The - * result of the validation can be queried via by the application by invoking the - * FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by - * authIdTokenVerified is highly recommended if an Auth ID token is provided. - * - * Once the token has been properly verified then invoking getAuth() will attempt to - * automatically sign in a user with the provided Auth ID Token. - * - * If the token fails verification then a warning is logged and Auth SDK will not - * attempt to sign in a user upon its initalization. - */ - authIdToken?: string; - - /** - * An optional AppCheck token. - * - * If provided, the FirebaseServerApp instance will work to validate the token. The - * result of the validation can be monitored by invoking the - * FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by - * appCheckTokenVerified is highly recommended if an AppCheck token is provided. - * - * If the token has been properly verified then the AppCheck token will be - * automatically used by Firebase SDKs that support App Check. - * - * If the token fails verification then a warning is logged and the token will not - * be used. - */ - appCheckToken?: string; - - /** - * An optional Installation Auth token. - * - * If provided, the FirebaseServerApp instance will work to validate the token. The - * result of the validation can be monitored by invoking the - * FirebaseServerApp.installationAuthTokenVerified(). Awaiting the Promise returned by - * appCheckTokenVerified is highly recommended before initalization any other Firebase - * SDKs. - * - * If the token has been properly verified then the Installation Auth token will be - * automatically used by Firebase SDKs that support Firebase Installations. - * - * If the token fails verification then a warning is logged and the token will not - * be used. - */ - installationAuthToken?: string; - - /** - * An optional object. If provided, the Firebase SDK will use a FinalizationRegistry - * object to monitor its GC status. The Firebase SDK will cleanup and - * delete the corresponding FirebaseServerApp instance when the provided object is - * deleted by the garbage collector. - * - * The intent of this field is to help reduce memory overhead for long-running cloud - * functions executing SSR fulfillment without the customer's app needing to - * orchestrate FirebaseServerApp cleanup. - * - * For instance, in the case that asynchronous operations makes it difficult to - * determine the finality of the server side rendering pass. - * - * If the object is not provided then the application must clean up the - * FirebaseServerApp instance through it's own standard mechanisms by invoking - * deleteApp. - * - * If the app uses provides an object but uses a JavaScript engine that predates the - * support of FinalizationRegistry (introduced in node v14.6.0, for instance), then the - * Firebase SDK will not be able to automatically clean up the FirebaseServerApp - * instance and an error will be thrown. - */ - deleteOnDeref?: object; - - name: undefined; -} - export class FirebaseApp { /** * The (read-only) name (identifier) for this App. '[DEFAULT]' is the default @@ -133,67 +55,6 @@ export class FirebaseApp { delete(): Promise; } -/** - * A {@link @firebase/app#FirebaseServerApp} holds the initialization information - * for a collection of services running in server enviornments. - * - * Do not call this constructor directly. Instead, use - * {@link (initializeServerAppInstance:1) | initializeServerAppInstance()} to create - * an app. - * - * @public - */ -export class FirebaseServerApp extends FirebaseApp { - /** - * Checks to see if the verification of the authIdToken provided to - * @initializeServerApp has completed. - * - * It is recommend that your application awaits this promise if an authIdToken was - * provided during FirebaseServerApp initialization before invoking getAuth(). If an - * instance of Auth is created before the Auth ID Token is validated, then the token - * will not be used by that instance of the Auth SDK. - * - * The returned Promise is completed immediately if the optional authIdToken parameter - * was omitted from FirebaseServerApp initialization. - */ - authIdTokenVerified: () => Promise; - - /** - * Checks to see if the verification of the appCheckToken provided to - * @initializeServerApp has completed. If the optional appCheckToken parameter was - * omitted then the returned Promise is completed immediately. - * - * It is recommend that your application awaits this promise before initializing - * any Firebase products that use AppCheck. The Firebase SDKs will not - * use App Check tokens that are determined to be invalid or those that have not yet - * completed validation. - * - * The returned Promise is completed immediately if the optional appCheckToken - * parameter was omitted from FirebaseServerApp initialization. - */ - appCheckTokenVerified: () => Promise; - - /** - * Checks to see if the verification of the installationAuthToken provided to - * @initializeServerApp has completed. - * - * It is recommend that your application awaits this promise before initializing - * any Firebase products that use Firebase Installations. The Firebase SDKs will not - * use Installation Auth tokens that are determined to be invalid or those that have - * not yet completed validation. - * - * The returned Promise is completed immediately if the optional appCheckToken - * parameter was omitted from FirebaseServerApp initialization. - */ - installationAuthTokenVerified: () => Promise; - - /** - * There is no get for FirebaseServerApp, name is not relevant—however it's always - * a blank string to conform to the FirebaseApp interface - */ - name: ''; -} - export interface FirebaseNamespace { /** * Create (and initialize) a FirebaseApp. @@ -205,7 +66,6 @@ export interface FirebaseNamespace { options: FirebaseOptions, config?: FirebaseAppConfig ): FirebaseApp; - /** * Create (and initialize) a FirebaseApp. * @@ -215,18 +75,6 @@ export interface FirebaseNamespace { */ initializeApp(options: FirebaseOptions, name?: string): FirebaseApp; - /** - * Create (and initialize) a FirebaseServerApp. - * - * @param options - Firebase.AppOptions to configure the app's services, or a - * a FirebaseApp instance which contains the AppOptions within. - * @param config The config for your firebase server app. - */ - initializeServerApp( - options: FirebaseOptions | FirebaseApp, - config: FirebaseServerAppConfig - ): FirebaseServerApp; - app: { /** * Retrieve an instance of a FirebaseApp. @@ -246,35 +94,11 @@ export interface FirebaseNamespace { App: typeof FirebaseApp; }; - serverApp: { - /** - * Retrieve an instance of a FirebaseServerApp. - * - * Usage: firebase.serverApp(name) - * - * @param name The optional name of the server app to return ('[DEFAULT]' if omitted) - */ - (name?: string): FirebaseServerApp; - - /** - * For testing FirebaseServerApp instances: - * serverApp() instanceof firebase.app.FirebaseServerApp - * - * DO NOT call this constuctor directly (use firebase.initializeServerApp() instead). - */ - serverApp: typeof FirebaseServerApp; - }; - /** * A (read-only) array of all the initialized Apps. */ apps: FirebaseApp[]; - /** - * A (read-only) array of all the initialized Server Apps. - */ - serverApps: FirebaseServerApp[]; - /** * Registers a library's name and version for platform logging purposes. * @param library Name of 1p or 3p library (e.g. firestore, angularfire) From 968e1767be2994e1cb06363fddce8b5b5104baa6 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 12 Jan 2024 11:07:59 -0500 Subject: [PATCH 27/41] Leverage the authIdToken to login users in FirebaseServerApp instances Update auth to pull an auth idToken from the FirebaseServerApp. If it exists, login a user based on that idToken. --- common/api-review/app.api.md | 5 ++ packages/app/src/firebaseServerApp.ts | 4 ++ packages/app/src/internal.ts | 14 +++++ packages/auth/src/core/auth/initialize.ts | 39 ++++++++++++- packages/auth/src/core/user/reload.ts | 2 +- packages/auth/src/core/user/token_manager.ts | 18 ++++-- packages/auth/src/core/user/user_impl.ts | 61 +++++++++++++++++++- 7 files changed, 131 insertions(+), 12 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 266e7730610..e7688a2b203 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -119,6 +119,11 @@ export function initializeServerApp(options: FirebaseOptions | FirebaseApp, conf // @internal (undocumented) export function _isFirebaseApp(obj: FirebaseApp | FirebaseOptions): obj is FirebaseApp; +// Warning: (ae-forgotten-export) The symbol "FirebaseServerAppImpl" needs to be exported by the entry point index.d.ts +// +// @internal (undocumented) +export function _isFirebaseServerAppImpl(obj: FirebaseApp | FirebaseServerApp): obj is FirebaseServerAppImpl; + // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index cc4264ae504..fff2a4eaf28 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -69,6 +69,8 @@ export class FirebaseServerAppImpl this.automaticCleanup ); + this.authIdToken = serverConfig.authIdToken; + if (this._serverConfig.releaseOnDeref !== undefined) { this._finalizationRegistry.register( this._serverConfig.releaseOnDeref, @@ -88,6 +90,8 @@ export class FirebaseServerAppImpl return this._serverConfig; } + authIdToken?: string; + authIdTokenVerified(): Promise { this.checkDestroyed(); // TODO diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 94af4b9434c..2a99da41e12 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -155,6 +155,20 @@ export function _isFirebaseApp( return (obj as FirebaseApp).options !== undefined; } +/** + * + * @param obj - an object of type FirebaseApp. + * + * @returns true if the provided object is of type FirebaseServerAppImpl. + * + * @internal + */ +export function _isFirebaseServerAppImpl( + obj: FirebaseApp | FirebaseServerApp +): obj is FirebaseServerAppImpl { + return (obj as FirebaseServerAppImpl).authIdToken !== undefined; +} + /** * Test only * diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index c6218953508..cfebfe48392 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -15,7 +15,11 @@ * limitations under the License. */ -import { _getProvider, FirebaseApp } from '@firebase/app'; +import { + _getProvider, + _isFirebaseServerAppImpl, + FirebaseApp +} from '@firebase/app'; import { deepEqual } from '@firebase/util'; import { Auth, Dependencies } from '../../model/public_types'; @@ -23,7 +27,9 @@ import { AuthErrorCode } from '../errors'; import { PersistenceInternal } from '../persistence'; import { _fail } from '../util/assert'; import { _getInstance } from '../util/instantiator'; -import { AuthImpl } from './auth_impl'; +import { AuthImpl, _castAuth } from './auth_impl'; +import { UserImpl } from '../user/user_impl'; +import { getAccountInfo } from '../../api/account_management/account'; /** * Initializes an {@link Auth} instance with fine-grained control over @@ -65,9 +71,38 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { const auth = provider.initialize({ options: deps }) as AuthImpl; + if (_isFirebaseServerAppImpl(app)) { + if (app.authIdToken !== undefined) { + _loadUserFromIdToken(auth, app.authIdToken) + .catch(err => { + console.log(err); + }) + .then(() => { + console.log('ok!'); + }); + } + } + return auth; } +export async function _loadUserFromIdToken( + auth: Auth, + idToken: string +): Promise { + const response = await getAccountInfo(auth, { idToken }); + const authInternal = _castAuth(auth); + await authInternal._initializationPromise; + + const user = await UserImpl._fromGetAccountInfoResponse( + authInternal, + response, + idToken + ); + + await authInternal._updateCurrentUser(user); +} + export function _initializeAuthInstance( auth: AuthImpl, deps?: Dependencies diff --git a/packages/auth/src/core/user/reload.ts b/packages/auth/src/core/user/reload.ts index fc0a33b937a..ac9a1683e2d 100644 --- a/packages/auth/src/core/user/reload.ts +++ b/packages/auth/src/core/user/reload.ts @@ -102,7 +102,7 @@ function mergeProviderData( return [...deduped, ...newData]; } -function extractProviderData(providers: ProviderUserInfo[]): UserInfo[] { +export function extractProviderData(providers: ProviderUserInfo[]): UserInfo[] { return providers.map(({ providerId, ...provider }) => { return { providerId, diff --git a/packages/auth/src/core/user/token_manager.ts b/packages/auth/src/core/user/token_manager.ts index 5f56f88afb6..617d1af822b 100644 --- a/packages/auth/src/core/user/token_manager.ts +++ b/packages/auth/src/core/user/token_manager.ts @@ -73,20 +73,26 @@ export class StsTokenManager { ); } + updateFromIdToken(idToken: string): void { + _assert(idToken.length !== 0, AuthErrorCode.INTERNAL_ERROR); + const expiresIn = _tokenExpiresIn(idToken); + this.updateTokensAndExpiration(idToken, null, expiresIn); + } + async getToken( auth: AuthInternal, forceRefresh = false ): Promise { - _assert( - !this.accessToken || this.refreshToken, - auth, - AuthErrorCode.TOKEN_EXPIRED - ); + if (!this.accessToken) { + _assert(this.refreshToken, auth, AuthErrorCode.TOKEN_EXPIRED); + } if (!forceRefresh && this.accessToken && !this.isExpired) { return this.accessToken; } + _assert(this.refreshToken, auth, AuthErrorCode.TOKEN_EXPIRED); + if (this.refreshToken) { await this.refresh(auth, this.refreshToken!); return this.accessToken; @@ -113,7 +119,7 @@ export class StsTokenManager { private updateTokensAndExpiration( accessToken: string, - refreshToken: string, + refreshToken: string | null, expiresInSec: number ): void { this.refreshToken = refreshToken || null; diff --git a/packages/auth/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts index 44192cc4617..d9e43f9fc95 100644 --- a/packages/auth/src/core/user/user_impl.ts +++ b/packages/auth/src/core/user/user_impl.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { IdTokenResult } from '../../model/public_types'; +import { IdTokenResult, UserInfo } from '../../model/public_types'; import { NextFn } from '@firebase/util'; - import { APIUserInfo, + GetAccountInfoResponse, deleteAccount } from '../../api/account_management/account'; import { FinalizeMfaResponse } from '../../api/authentication/mfa'; @@ -36,7 +36,7 @@ import { _assert } from '../util/assert'; import { getIdTokenResult } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { ProactiveRefresh } from './proactive_refresh'; -import { _reloadWithoutSaving, reload } from './reload'; +import { extractProviderData, _reloadWithoutSaving, reload } from './reload'; import { StsTokenManager } from './token_manager'; import { UserMetadata } from './user_metadata'; import { ProviderId } from '../../model/enums'; @@ -333,4 +333,59 @@ export class UserImpl implements UserInternal { await _reloadWithoutSaving(user); return user; } + + /** + * Initialize a User from an idToken server response + * @param auth + * @param idTokenResponse + */ + static async _fromGetAccountInfoResponse( + auth: AuthInternal, + response: GetAccountInfoResponse, + idToken: string + ): Promise { + const coreAccount = response.users[0]; + _assert(coreAccount.localId !== undefined, AuthErrorCode.INTERNAL_ERROR); + + var providerData: UserInfo[] = []; + if (coreAccount.providerUserInfo !== undefined) { + providerData = extractProviderData(coreAccount.providerUserInfo); + } + + const isAnonymous = + !(coreAccount.email && coreAccount.passwordHash) && !providerData?.length; + + const stsTokenManager = new StsTokenManager(); + stsTokenManager.updateFromIdToken(idToken); + + // Initialize the Firebase Auth user. + const user = new UserImpl({ + uid: coreAccount.localId, + auth, + stsTokenManager, + isAnonymous + }); + + // update the user with data from the GetAccountInfo response. + const updates: Partial = { + uid: coreAccount.localId, + displayName: coreAccount.displayName || null, + photoURL: coreAccount.photoUrl || null, + email: coreAccount.email || null, + emailVerified: coreAccount.emailVerified || false, + phoneNumber: coreAccount.phoneNumber || null, + tenantId: coreAccount.tenantId || null, + providerData, + metadata: new UserMetadata( + coreAccount.createdAt, + coreAccount.lastLoginAt + ), + isAnonymous: + !(coreAccount.email && coreAccount.passwordHash) && + !providerData?.length + }; + + Object.assign(user, updates); + return user; + } } From 826a3c4929b6e4cf9881f56ab8cad0bb7cf24a0f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 16 Jan 2024 08:26:05 -0500 Subject: [PATCH 28/41] void promise --- packages/auth/src/core/auth/initialize.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index cfebfe48392..fc522b52914 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -73,13 +73,7 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { if (_isFirebaseServerAppImpl(app)) { if (app.authIdToken !== undefined) { - _loadUserFromIdToken(auth, app.authIdToken) - .catch(err => { - console.log(err); - }) - .then(() => { - console.log('ok!'); - }); + void _loadUserFromIdToken(auth, app.authIdToken); } } From e2a0f9aaa4502e008e581dc8d33be3ff6e9f4443 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 16 Jan 2024 08:26:26 -0500 Subject: [PATCH 29/41] const providerData --- packages/auth/src/core/user/user_impl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/auth/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts index d9e43f9fc95..2a4af0ce3d3 100644 --- a/packages/auth/src/core/user/user_impl.ts +++ b/packages/auth/src/core/user/user_impl.ts @@ -347,7 +347,7 @@ export class UserImpl implements UserInternal { const coreAccount = response.users[0]; _assert(coreAccount.localId !== undefined, AuthErrorCode.INTERNAL_ERROR); - var providerData: UserInfo[] = []; + const providerData: UserInfo[] = []; if (coreAccount.providerUserInfo !== undefined) { providerData = extractProviderData(coreAccount.providerUserInfo); } From a6bdf3e1020695ae8adab7342ce096ccedc2a6e7 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 16 Jan 2024 09:23:19 -0500 Subject: [PATCH 30/41] provider data initialized inline --- packages/auth/src/core/user/user_impl.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/auth/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts index 2a4af0ce3d3..e355642fe87 100644 --- a/packages/auth/src/core/user/user_impl.ts +++ b/packages/auth/src/core/user/user_impl.ts @@ -347,10 +347,9 @@ export class UserImpl implements UserInternal { const coreAccount = response.users[0]; _assert(coreAccount.localId !== undefined, AuthErrorCode.INTERNAL_ERROR); - const providerData: UserInfo[] = []; - if (coreAccount.providerUserInfo !== undefined) { - providerData = extractProviderData(coreAccount.providerUserInfo); - } + const providerData: UserInfo[] = + (coreAccount.providerUserInfo !== undefined) ? + extractProviderData(coreAccount.providerUserInfo) : []; const isAnonymous = !(coreAccount.email && coreAccount.passwordHash) && !providerData?.length; From c323811b58f3e84b069a650eef439421613afa23 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 16 Jan 2024 11:27:52 -0500 Subject: [PATCH 31/41] format and getToken test update --- packages/auth/src/core/user/token_manager.test.ts | 4 ++-- packages/auth/src/core/user/user_impl.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/auth/src/core/user/token_manager.test.ts b/packages/auth/src/core/user/token_manager.test.ts index e1648d4eb32..ccbc23f1881 100644 --- a/packages/auth/src/core/user/token_manager.test.ts +++ b/packages/auth/src/core/user/token_manager.test.ts @@ -144,8 +144,8 @@ describe('core/user/token_manager', () => { }); }); - it('returns null if the refresh token is missing', async () => { - expect(await stsTokenManager.getToken(auth)).to.be.null; + it('returns non null if the refresh token is missing', async () => { + expect(await stsTokenManager.getToken(auth)).to.not.be.null; }); it('throws an error if expired but refresh token is missing', async () => { diff --git a/packages/auth/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts index e355642fe87..0aa91861cb9 100644 --- a/packages/auth/src/core/user/user_impl.ts +++ b/packages/auth/src/core/user/user_impl.ts @@ -348,8 +348,9 @@ export class UserImpl implements UserInternal { _assert(coreAccount.localId !== undefined, AuthErrorCode.INTERNAL_ERROR); const providerData: UserInfo[] = - (coreAccount.providerUserInfo !== undefined) ? - extractProviderData(coreAccount.providerUserInfo) : []; + coreAccount.providerUserInfo !== undefined + ? extractProviderData(coreAccount.providerUserInfo) + : []; const isAnonymous = !(coreAccount.email && coreAccount.passwordHash) && !providerData?.length; From e8151d643a556b809b9f2b15391ac03fa55d12e7 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 16 Jan 2024 14:29:29 -0500 Subject: [PATCH 32/41] more stsmanager tests --- .../auth/src/core/user/token_manager.test.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/auth/src/core/user/token_manager.test.ts b/packages/auth/src/core/user/token_manager.test.ts index ccbc23f1881..68ba97cebf3 100644 --- a/packages/auth/src/core/user/token_manager.test.ts +++ b/packages/auth/src/core/user/token_manager.test.ts @@ -144,8 +144,35 @@ describe('core/user/token_manager', () => { }); }); - it('returns non null if the refresh token is missing', async () => { - expect(await stsTokenManager.getToken(auth)).to.not.be.null; + it('returns non-null if the refresh token is missing but token still valid', async () => { + Object.assign(stsTokenManager, { + accessToken: 'access-token', + expirationTime: now + 100_000 + }); + const tokens = await stsTokenManager.getToken(auth, false); + expect(tokens).to.eql('new-access-token'); + }); + + it('throws an error if the refresh token is missing and force refresh is true', async () => { + Object.assign(stsTokenManager, { + accessToken: 'access-token', + expirationTime: now + 100_000 + }); + await expect(stsTokenManager.getToken(auth, true)).to.be.rejectedWith( + FirebaseError, + "Firebase: The user's credential is no longer valid. The user must sign in again. (auth/user-token-expired)" + ); + }); + + it('throws an error if the refresh token is missing and token is no longer valid', async () => { + Object.assign(stsTokenManager, { + accessToken: 'old-access-token', + expirationTime: now - 1 + }); + await expect(stsTokenManager.getToken(auth)).to.be.rejectedWith( + FirebaseError, + "Firebase: The user's credential is no longer valid. The user must sign in again. (auth/user-token-expired)" + ); }); it('throws an error if expired but refresh token is missing', async () => { From 8e6ab2df6baa084dbd82c2fab594b8f569fa6067 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 16 Jan 2024 14:46:35 -0500 Subject: [PATCH 33/41] fix test deep-equal --- packages/auth/src/core/user/token_manager.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/auth/src/core/user/token_manager.test.ts b/packages/auth/src/core/user/token_manager.test.ts index 68ba97cebf3..b2e1609692f 100644 --- a/packages/auth/src/core/user/token_manager.test.ts +++ b/packages/auth/src/core/user/token_manager.test.ts @@ -146,16 +146,16 @@ describe('core/user/token_manager', () => { it('returns non-null if the refresh token is missing but token still valid', async () => { Object.assign(stsTokenManager, { - accessToken: 'access-token', + accessToken: 'token', expirationTime: now + 100_000 }); const tokens = await stsTokenManager.getToken(auth, false); - expect(tokens).to.eql('new-access-token'); + expect(tokens).to.eql('token'); }); it('throws an error if the refresh token is missing and force refresh is true', async () => { Object.assign(stsTokenManager, { - accessToken: 'access-token', + accessToken: 'token', expirationTime: now + 100_000 }); await expect(stsTokenManager.getToken(auth, true)).to.be.rejectedWith( From 1980c7b7f2d7422793115d31b03228ba18806d67 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 22 Jan 2024 13:45:19 -0500 Subject: [PATCH 34/41] [ServerApp] Add integration tests for FirebaseServerApp (#7960) Add integration tests for the automatic login of Auth users when initializing Auth with an instance of FirebaseServerApp. --- packages/auth/karma.conf.js | 3 +- packages/auth/scripts/run_node_tests.ts | 4 +- .../flows/firebaseserverapp.test.ts | 344 ++++++++++++++++++ 3 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 packages/auth/test/integration/flows/firebaseserverapp.test.ts diff --git a/packages/auth/karma.conf.js b/packages/auth/karma.conf.js index 6845f0bd91d..198b079a15b 100644 --- a/packages/auth/karma.conf.js +++ b/packages/auth/karma.conf.js @@ -65,7 +65,8 @@ function getTestFiles(argv) { 'src/**/*.test.ts', 'test/helpers/**/*.test.ts', 'test/integration/flows/anonymous.test.ts', - 'test/integration/flows/email.test.ts' + 'test/integration/flows/email.test.ts', + 'test/integration/flows/firebaseserverapp.test.ts' ]; } } diff --git a/packages/auth/scripts/run_node_tests.ts b/packages/auth/scripts/run_node_tests.ts index 2bfc593d8fd..ce913612f64 100644 --- a/packages/auth/scripts/run_node_tests.ts +++ b/packages/auth/scripts/run_node_tests.ts @@ -48,7 +48,9 @@ let testConfig = [ ]; if (argv.integration) { - testConfig = ['test/integration/flows/{email,anonymous}.test.ts']; + testConfig = [ + 'test/integration/flows/{email,anonymous,firebaseserverapp}.test.ts' + ]; if (argv.local) { testConfig.push('test/integration/flows/*.local.test.ts'); } diff --git a/packages/auth/test/integration/flows/firebaseserverapp.test.ts b/packages/auth/test/integration/flows/firebaseserverapp.test.ts new file mode 100644 index 00000000000..9629320172f --- /dev/null +++ b/packages/auth/test/integration/flows/firebaseserverapp.test.ts @@ -0,0 +1,344 @@ +/** + * @license + * Copyright 2024 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 { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + Auth, + OperationType, + createUserWithEmailAndPassword, + getAdditionalUserInfo, + getAuth, + onAuthStateChanged, + signInAnonymously, + signOut, + updateProfile +} from '@firebase/auth'; +import { isBrowser } from '@firebase/util'; +import { initializeServerApp } from '@firebase/app'; + +import { + cleanUpTestInstance, + getTestInstance, + randomEmail +} from '../../helpers/integration/helpers'; + +use(chaiAsPromised); + +const signInWaitDuration = 200; + +describe('Integration test: Auth FirebaseServerApp tests', () => { + let auth: Auth; + let serverAppAuth: Auth | null; + + beforeEach(() => { + auth = getTestInstance(); + }); + + afterEach(async () => { + if (serverAppAuth) { + await signOut(serverAppAuth); + serverAppAuth = null; + } + await cleanUpTestInstance(auth); + }); + + it('signs in with anonymous user', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.isAnonymous).to.be.true; + expect(user.uid).to.be.a('string'); + expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.equal(0); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); + serverAppAuth = getAuth(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + + // Note, the serverAuthUser does not fully equal the standard Auth user + // since the serverAuthUser does not have a refresh token. + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(user.isAnonymous).to.be.equal(serverAuthUser.isAnonymous); + expect(user.emailVerified).to.be.equal(serverAuthUser.emailVerified); + expect(user.providerData.length).to.eq( + serverAuthUser.providerData.length + ); + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(numberServerLogins).to.equal(1); + }); + + it('getToken operations fullfilled or rejected', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.isAnonymous).to.be.true; + expect(user.uid).to.be.a('string'); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); + serverAppAuth = getAuth(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(serverAppAuth).to.not.be.null; + expect(serverAuthUser.getIdToken); + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(numberServerLogins).to.equal(1); + expect(serverAppAuth.currentUser).to.not.be.null; + if (serverAppAuth.currentUser) { + const idToken = await serverAppAuth.currentUser.getIdToken( + /*forceRefresh=*/ false + ); + expect(idToken).to.not.be.null; + await expect(serverAppAuth.currentUser.getIdToken(/*forceRefresh=*/ true)) + .to.be.rejected; + } + }); + + it('signs in with email crednetial user', async () => { + if (isBrowser()) { + return; + } + const email = randomEmail(); + const password = 'password'; + const userCred = await createUserWithEmailAndPassword( + auth, + email, + password + ); + const user = userCred.user; + expect(auth.currentUser).to.eq(userCred.user); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + + const additionalUserInfo = getAdditionalUserInfo(userCred)!; + expect(additionalUserInfo.isNewUser).to.be.true; + expect(additionalUserInfo.providerId).to.eq('password'); + expect(user.isAnonymous).to.be.false; + expect(user.email).to.equal(email); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); + serverAppAuth = getAuth(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(serverAuthUser.refreshToken).to.be.empty; + expect(user.isAnonymous).to.be.equal(serverAuthUser.isAnonymous); + expect(user.emailVerified).to.be.equal(serverAuthUser.emailVerified); + expect(user.providerData.length).to.eq( + serverAuthUser.providerData.length + ); + expect(user.email).to.equal(serverAuthUser.email); + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(numberServerLogins).to.equal(1); + }); + + it('can reload user', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.uid).to.be.a('string'); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); + serverAppAuth = getAuth(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(serverAppAuth.currentUser).to.not.be.null; + if (serverAppAuth.currentUser) { + await serverAppAuth.currentUser.reload(); + } + expect(numberServerLogins).to.equal(1); + }); + + it('can update server based user profile', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.uid).to.be.a('string'); + expect(user.displayName).to.be.null; + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); + serverAppAuth = getAuth(serverApp); + let numberServerLogins = 0; + const newDisplayName = 'newName'; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(user.displayName).to.be.null; + void updateProfile(serverAuthUser, { + displayName: newDisplayName + }); + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(serverAppAuth.currentUser).to.not.be.null; + + if (serverAppAuth.currentUser) { + await serverAppAuth.currentUser.reload(); + } + + expect(numberServerLogins).to.equal(1); + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.not.be.null; + expect(serverAppAuth.currentUser?.displayName).to.not.be.null; + expect(serverAppAuth.currentUser?.displayName).to.equal(newDisplayName); + } + }); + + it('can sign out of main auth and still use server auth', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.uid).to.be.a('string'); + expect(user.displayName).to.be.null; + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + auth.app.options, + firebaseServerAppSettings + ); + serverAppAuth = getAuth(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(serverAppAuth).to.not.be.null; + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(user.displayName).to.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + } + }); + + await signOut(auth); + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(serverAppAuth.currentUser).to.not.be.null; + + if (serverAppAuth.currentUser) { + await serverAppAuth.currentUser.reload(); + } + + expect(numberServerLogins).to.equal(1); + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.not.be.null; + } + }); +}); From 6543206875a300c67283b8d46fac78b62c81c44f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 22 Jan 2024 14:20:44 -0500 Subject: [PATCH 35/41] add comment about authIdToken string member of FirebaseServerAppImpl --- packages/app/src/firebaseServerApp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index fff2a4eaf28..ec5f319c5e0 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -90,6 +90,8 @@ export class FirebaseServerAppImpl return this._serverConfig; } + // TODO: store the verified authIdToken somewhere. This is a placeholder waiting for the token + // validation work. authIdToken?: string; authIdTokenVerified(): Promise { From 29fa9420cea82b50337efcf15fc0fcacb2725b4f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 22 Jan 2024 15:24:21 -0500 Subject: [PATCH 36/41] remove duplicate assertions in getToken. --- packages/auth/src/core/user/token_manager.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/auth/src/core/user/token_manager.ts b/packages/auth/src/core/user/token_manager.ts index 617d1af822b..14969005d89 100644 --- a/packages/auth/src/core/user/token_manager.ts +++ b/packages/auth/src/core/user/token_manager.ts @@ -83,10 +83,6 @@ export class StsTokenManager { auth: AuthInternal, forceRefresh = false ): Promise { - if (!this.accessToken) { - _assert(this.refreshToken, auth, AuthErrorCode.TOKEN_EXPIRED); - } - if (!forceRefresh && this.accessToken && !this.isExpired) { return this.accessToken; } From 940ec40ffab1195ff86acdd0aebd39aeb1964e36 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 23 Jan 2024 09:44:41 -0500 Subject: [PATCH 37/41] test for invalid token --- packages/auth/src/core/auth/initialize.ts | 27 +++++++++++-------- .../flows/firebaseserverapp.test.ts | 26 ++++++++++++++++++ 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index fc522b52914..cbe8d5b6af5 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -84,17 +84,22 @@ export async function _loadUserFromIdToken( auth: Auth, idToken: string ): Promise { - const response = await getAccountInfo(auth, { idToken }); - const authInternal = _castAuth(auth); - await authInternal._initializationPromise; - - const user = await UserImpl._fromGetAccountInfoResponse( - authInternal, - response, - idToken - ); - - await authInternal._updateCurrentUser(user); + try { + const response = await getAccountInfo(auth, { idToken }); + const authInternal = _castAuth(auth); + await authInternal._initializationPromise; + const user = await UserImpl._fromGetAccountInfoResponse( + authInternal, + response, + idToken + ); + await authInternal._updateCurrentUser(user); + } catch (err) { + console.warn( + 'FirebaseServerApp could not login user with provided authIdToken: ', + err + ); + } } export function _initializeAuthInstance( diff --git a/packages/auth/test/integration/flows/firebaseserverapp.test.ts b/packages/auth/test/integration/flows/firebaseserverapp.test.ts index 9629320172f..d7cbc8b0151 100644 --- a/packages/auth/test/integration/flows/firebaseserverapp.test.ts +++ b/packages/auth/test/integration/flows/firebaseserverapp.test.ts @@ -147,6 +147,32 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { } }); + it('invalid token does not sign in user', async () => { + if (isBrowser()) { + return; + } + const authIdToken = '{ invalid token }'; + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); + serverAppAuth = getAuth(serverApp); + expect(serverAppAuth.currentUser).to.be.null; + + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(numberServerLogins).to.equal(0); + expect(serverAppAuth.currentUser).to.be.null; + }); + it('signs in with email crednetial user', async () => { if (isBrowser()) { return; From 8bf52860b434dda55a0ec1baba93794241f65d58 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 24 Jan 2024 15:52:05 -0500 Subject: [PATCH 38/41] Fix tests for emulators In the tests, attach emulators to Auth instances created via FirebaseServerApps. In Auth initialization, wait a tick before kicking off the automatic loading of users by idToken. This should give the app enough time to attach an emulator. --- packages/auth/src/core/auth/initialize.ts | 5 +- .../auth/test/helpers/integration/helpers.ts | 33 ++++++++++++- .../flows/firebaseserverapp.test.ts | 49 +++++++++++++------ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index cbe8d5b6af5..aa3266d132f 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -73,7 +73,10 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { if (_isFirebaseServerAppImpl(app)) { if (app.authIdToken !== undefined) { - void _loadUserFromIdToken(auth, app.authIdToken); + const idToken = app.authIdToken; + // Start the auth operation in the next tick to allow a moment for the customer's app to + // attach an emulator, if desired. + setTimeout(() => void _loadUserFromIdToken(auth, idToken), 0); } } diff --git a/packages/auth/test/helpers/integration/helpers.ts b/packages/auth/test/helpers/integration/helpers.ts index 9825a8f4ba0..c88cc275a18 100644 --- a/packages/auth/test/helpers/integration/helpers.ts +++ b/packages/auth/test/helpers/integration/helpers.ts @@ -16,7 +16,11 @@ */ import * as sinon from 'sinon'; -import { deleteApp, initializeApp } from '@firebase/app'; +import { + FirebaseServerApp, + deleteApp, + initializeApp +} from '@firebase/app'; import { Auth, User } from '@firebase/auth'; import { getAuth, connectAuthEmulator } from '../../../'; // Use browser OR node dist entrypoint depending on test env. @@ -80,6 +84,33 @@ export function getTestInstance(requireEmulator = false): Auth { return auth; } +export function getTestInstanceForServerApp( + serverApp: FirebaseServerApp, + requireEmulator = false +): Auth { + const auth = getAuth(serverApp) as IntegrationTestAuth; + auth.settings.appVerificationDisabledForTesting = true; + const emulatorUrl = getEmulatorUrl(); + + if (emulatorUrl) { + connectAuthEmulator(auth, emulatorUrl, { disableWarnings: true }); + } else if (requireEmulator) { + /* Emulator wasn't configured but test must use emulator */ + throw new Error('Test may only be run using the Auth Emulator!'); + } + + // Don't track created users on the created Auth instance like we do for Auth objects created in + // getTestInstance(...) above. FirebaseServerApp testing re-uses users created by the Auth + // instances returned by getTestInstance, so those Auth cleanup routines will suffice. + auth.cleanUp = async () => { + // If we're in an emulated environment, the emulator will clean up for us. + //if (emulatorUrl) { + // await resetEmulator(); + //} + }; + return auth; +} + export async function cleanUpTestInstance(auth: Auth): Promise { await auth.signOut(); await (auth as IntegrationTestAuth).cleanUp(); diff --git a/packages/auth/test/integration/flows/firebaseserverapp.test.ts b/packages/auth/test/integration/flows/firebaseserverapp.test.ts index d7cbc8b0151..42b14eb2edc 100644 --- a/packages/auth/test/integration/flows/firebaseserverapp.test.ts +++ b/packages/auth/test/integration/flows/firebaseserverapp.test.ts @@ -24,7 +24,6 @@ import { OperationType, createUserWithEmailAndPassword, getAdditionalUserInfo, - getAuth, onAuthStateChanged, signInAnonymously, signOut, @@ -36,9 +35,12 @@ import { initializeServerApp } from '@firebase/app'; import { cleanUpTestInstance, getTestInstance, + getTestInstanceForServerApp, randomEmail } from '../../helpers/integration/helpers'; +import { getAppConfig } from '../../helpers/integration/settings'; + use(chaiAsPromised); const signInWaitDuration = 200; @@ -77,7 +79,11 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const firebaseServerAppSettings = { authIdToken }; const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); - serverAppAuth = getAuth(serverApp); + serverAppAuth = getTestInstanceForServerApp(serverApp); + + console.log('auth.emulatorConfig ', auth.emulatorConfig); + console.log('serverAuth.emulatorConfig ', serverAppAuth.emulatorConfig); + let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -116,8 +122,11 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const authIdToken = await user.getIdToken(); const firebaseServerAppSettings = { authIdToken }; - const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); - serverAppAuth = getAuth(serverApp); + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -154,8 +163,11 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const authIdToken = '{ invalid token }'; const firebaseServerAppSettings = { authIdToken }; - const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); - serverAppAuth = getAuth(serverApp); + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); expect(serverAppAuth.currentUser).to.be.null; let numberServerLogins = 0; @@ -197,8 +209,11 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const authIdToken = await user.getIdToken(); const firebaseServerAppSettings = { authIdToken }; - const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); - serverAppAuth = getAuth(serverApp); + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -239,8 +254,11 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const authIdToken = await user.getIdToken(); const firebaseServerAppSettings = { authIdToken }; - const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); - serverAppAuth = getAuth(serverApp); + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -279,8 +297,11 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const authIdToken = await user.getIdToken(); const firebaseServerAppSettings = { authIdToken }; - const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); - serverAppAuth = getAuth(serverApp); + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; const newDisplayName = 'newName'; onAuthStateChanged(serverAppAuth, serverAuthUser => { @@ -333,10 +354,10 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const firebaseServerAppSettings = { authIdToken }; const serverApp = initializeServerApp( - auth.app.options, + getAppConfig(), firebaseServerAppSettings ); - serverAppAuth = getAuth(serverApp); + serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { From 3b066fa74b2314ddb3cc8efd9d3ff8e1d1bdb6ea Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 25 Jan 2024 16:03:15 -0500 Subject: [PATCH 39/41] Fix isFirebaseSeverAppImpl export problem --- common/api-review/app.api.md | 5 ++--- docs-devsite/app.firebaseserverapp.md | 21 +++++++++++++++++++++ packages/app/src/firebaseServerApp.ts | 8 +------- packages/app/src/internal.ts | 6 +++--- packages/app/src/public-types.ts | 12 ++++++++++++ packages/auth/src/core/auth/initialize.ts | 8 ++++---- 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index e7688a2b203..087318ae27f 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -77,6 +77,7 @@ export interface FirebaseServerApp extends FirebaseApp { authIdTokenVerified: () => Promise; installationTokenVerified: () => Promise; name: string; + readonly settings: FirebaseServerAppSettings; } // @public @@ -119,10 +120,8 @@ export function initializeServerApp(options: FirebaseOptions | FirebaseApp, conf // @internal (undocumented) export function _isFirebaseApp(obj: FirebaseApp | FirebaseOptions): obj is FirebaseApp; -// Warning: (ae-forgotten-export) The symbol "FirebaseServerAppImpl" needs to be exported by the entry point index.d.ts -// // @internal (undocumented) -export function _isFirebaseServerAppImpl(obj: FirebaseApp | FirebaseServerApp): obj is FirebaseServerAppImpl; +export function _isFirebaseServerApp(obj: FirebaseApp | FirebaseServerApp): obj is FirebaseServerApp; // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md index cf41ee0a633..387e27a9822 100644 --- a/docs-devsite/app.firebaseserverapp.md +++ b/docs-devsite/app.firebaseserverapp.md @@ -29,6 +29,7 @@ export interface FirebaseServerApp extends FirebaseApp | [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the verification of the authIdToken provided to has completed.It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | | [installationTokenVerified](./app.firebaseserverapp.md#firebaseserverappinstallationtokenverified) | () => Promise<void> | Checks to see if the verification of the installationToken provided to has completed.It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | | [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. | +| [settings](./app.firebaseserverapp.md#firebaseserverappsettings) | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). | ## FirebaseServerApp.appCheckTokenVerified @@ -81,3 +82,23 @@ There is no get for FirebaseServerApp, so the name is not relevant. However, it' ```typescript name: string; ``` + +## FirebaseServerApp.settings + +The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). + +Signature: + +```typescript +readonly settings: FirebaseServerAppSettings; +``` + +### Example + + +```javascript +const app = initializeServerApp(settings); +console.log(app.settings.authIdToken === options.authIdToken); // true + +``` + diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index ec5f319c5e0..7c51511d087 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -69,8 +69,6 @@ export class FirebaseServerAppImpl this.automaticCleanup ); - this.authIdToken = serverConfig.authIdToken; - if (this._serverConfig.releaseOnDeref !== undefined) { this._finalizationRegistry.register( this._serverConfig.releaseOnDeref, @@ -85,15 +83,11 @@ export class FirebaseServerAppImpl void deleteApp(serverApp); } - get serverAppConfig(): FirebaseServerAppSettings { + get settings(): FirebaseServerAppSettings { this.checkDestroyed(); return this._serverConfig; } - // TODO: store the verified authIdToken somewhere. This is a placeholder waiting for the token - // validation work. - authIdToken?: string; - authIdTokenVerified(): Promise { this.checkDestroyed(); // TODO diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 2a99da41e12..da0ba3f73fc 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -163,10 +163,10 @@ export function _isFirebaseApp( * * @internal */ -export function _isFirebaseServerAppImpl( +export function _isFirebaseServerApp( obj: FirebaseApp | FirebaseServerApp -): obj is FirebaseServerAppImpl { - return (obj as FirebaseServerAppImpl).authIdToken !== undefined; +): obj is FirebaseServerApp { + return (obj as FirebaseServerApp).authIdTokenVerified !== undefined; } /** diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index f179e31dac3..04ba4f0b24c 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -132,6 +132,18 @@ export interface FirebaseServerApp extends FirebaseApp { * string will always be empty for FirebaseServerApp instances. */ name: string; + + /** + * The (read-only) configuration settings for this server app. These are the original + * parameters given in {@link (initializeServerApp:1) | initializeServerApp()}. + * + * @example + * ```javascript + * const app = initializeServerApp(settings); + * console.log(app.settings.authIdToken === options.authIdToken); // true + * ``` + */ + readonly settings: FirebaseServerAppSettings; } /** diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index aa3266d132f..aaf79ee9eb2 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -17,7 +17,7 @@ import { _getProvider, - _isFirebaseServerAppImpl, + _isFirebaseServerApp, FirebaseApp } from '@firebase/app'; import { deepEqual } from '@firebase/util'; @@ -71,9 +71,9 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { const auth = provider.initialize({ options: deps }) as AuthImpl; - if (_isFirebaseServerAppImpl(app)) { - if (app.authIdToken !== undefined) { - const idToken = app.authIdToken; + if (_isFirebaseServerApp(app)) { + if (app.settings.authIdToken !== undefined) { + const idToken = app.settings.authIdToken; // Start the auth operation in the next tick to allow a moment for the customer's app to // attach an emulator, if desired. setTimeout(() => void _loadUserFromIdToken(auth, idToken), 0); From 020687b6f43df33b2b10c17e9295e5b30b436f68 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 25 Jan 2024 16:18:25 -0500 Subject: [PATCH 40/41] formatter. --- packages/auth/src/core/auth/initialize.ts | 6 +----- packages/auth/test/helpers/integration/helpers.ts | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index aaf79ee9eb2..3bb21364718 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -15,11 +15,7 @@ * limitations under the License. */ -import { - _getProvider, - _isFirebaseServerApp, - FirebaseApp -} from '@firebase/app'; +import { _getProvider, _isFirebaseServerApp, FirebaseApp } from '@firebase/app'; import { deepEqual } from '@firebase/util'; import { Auth, Dependencies } from '../../model/public_types'; diff --git a/packages/auth/test/helpers/integration/helpers.ts b/packages/auth/test/helpers/integration/helpers.ts index c88cc275a18..17865228784 100644 --- a/packages/auth/test/helpers/integration/helpers.ts +++ b/packages/auth/test/helpers/integration/helpers.ts @@ -16,11 +16,7 @@ */ import * as sinon from 'sinon'; -import { - FirebaseServerApp, - deleteApp, - initializeApp -} from '@firebase/app'; +import { FirebaseServerApp, deleteApp, initializeApp } from '@firebase/app'; import { Auth, User } from '@firebase/auth'; import { getAuth, connectAuthEmulator } from '../../../'; // Use browser OR node dist entrypoint depending on test env. From 63cf1fb14de505330514d38757daeb663f9d84a1 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 26 Jan 2024 10:07:12 -0500 Subject: [PATCH 41/41] Skip loading users from persistence with FirebaseServerApps. --- packages/auth/src/core/auth/auth_impl.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index cd75276e006..55f474eda9e 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -15,7 +15,11 @@ * limitations under the License. */ -import { _FirebaseService, FirebaseApp } from '@firebase/app'; +import { + _isFirebaseServerApp, + _FirebaseService, + FirebaseApp +} from '@firebase/app'; import { Provider } from '@firebase/component'; import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; import { @@ -167,7 +171,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } } - await this.initializeCurrentUser(popupRedirectResolver); + // Skip loading users from persistence in FirebaseServerApp Auth instances. + if (!_isFirebaseServerApp(this.app)) { + await this.initializeCurrentUser(popupRedirectResolver); + } + this.lastNotifiedUid = this.currentUser?.uid || null; if (this._deleted) {