From 10fe794ee57135dc587992efe6051b5cdb0bd6a2 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 21 Jul 2020 15:36:45 -0400 Subject: [PATCH 01/46] Add progress --- gulpfile.js | 18 ++++++++++++++++++ src/{database.d.ts => database-tmp.d.ts} | 0 src/database/database.ts | 9 +++++++-- src/database/index.ts | 18 ++++++++++++++++++ src/index.d.ts | 18 +++++++++--------- src/test.ts | 5 +++++ tsconfig.json | 3 ++- 7 files changed, 59 insertions(+), 12 deletions(-) rename src/{database.d.ts => database-tmp.d.ts} (100%) create mode 100644 src/database/index.ts create mode 100644 src/test.ts diff --git a/gulpfile.js b/gulpfile.js index d058227482..1d7f9ae98f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -30,6 +30,7 @@ var ts = require('gulp-typescript'); var del = require('del'); var header = require('gulp-header'); var replace = require('gulp-replace'); +var filter = require('gulp-filter'); /****************/ @@ -75,6 +76,23 @@ gulp.task('compile', function() { return gulp.src(paths.src) // Compile Typescript into .js and .d.ts files .pipe(buildProject()) + .pipe(filter([ + '**', + '!lib/default-namespace.d.ts', + // '!lib/firebase-namespace.d.ts', + // '!lib/firebase-app.d.ts', + // '!lib/firebase-service.d.ts', + '!lib/**/*-internal.d.ts', + '!lib/auth/*.d.ts', + '!lib/firestore/*.d.ts', + '!lib/instance-id/*.d.ts', + '!lib/machine-learning/*.d.ts', + '!lib/messaging/*.d.ts', + '!lib/project-management/*.d.ts', + '!lib/remote-config/*.d.ts', + '!lib/security-rules/*.d.ts', + '!lib/storage/*.d.ts', + '!lib/utils/*.d.ts'])) // Add header .pipe(header(banner)) diff --git a/src/database.d.ts b/src/database-tmp.d.ts similarity index 100% rename from src/database.d.ts rename to src/database-tmp.d.ts diff --git a/src/database/database.ts b/src/database/database.ts index 686120909d..7654bc66fd 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -2,9 +2,10 @@ import { URL } from 'url'; import * as path from 'path'; import { FirebaseApp } from '../firebase-app'; +import * as admin from '../'; import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; -import { Database } from '@firebase/database'; +import { Database, Reference } from '@firebase/database'; import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; @@ -42,11 +43,14 @@ declare module '@firebase/database' { } } +export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; +export interface ThenableReference extends Reference, Promise { } + export class DatabaseService implements FirebaseServiceInterface { public readonly INTERNAL: DatabaseInternals = new DatabaseInternals(); - private readonly appInternal: FirebaseApp; + private readonly appInternal: admin.app.App; constructor(app: FirebaseApp) { if (!validator.isNonNullObject(app) || !('options' in app)) { @@ -231,3 +235,4 @@ class DatabaseRulesClient { return `${intro}: ${err.response.text}`; } } + diff --git a/src/database/index.ts b/src/database/index.ts new file mode 100644 index 0000000000..f5772f04bf --- /dev/null +++ b/src/database/index.ts @@ -0,0 +1,18 @@ +/*! + * Copyright 2020 Google Inc. + * + * 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. + */ + +export * from '@firebase/database'; +export * from './database'; diff --git a/src/index.d.ts b/src/index.d.ts index 3df285bed9..c9d450bf95 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -741,15 +741,15 @@ declare namespace admin.credential { } declare namespace admin.database { - export import Database = _database.admin.database.Database; - export import DataSnapshot = _database.admin.database.DataSnapshot; - export import OnDisconnect = _database.admin.database.OnDisconnect; - export import EventType = _database.admin.database.EventType; - export import Query = _database.admin.database.Query; - export import Reference = _database.admin.database.Reference; - export import ThenableReference = _database.admin.database.ThenableReference; - export import enableLogging = _database.admin.database.enableLogging; - export import ServerValue = _database.admin.database.ServerValue; + export import Database = _database.Database; + export import DataSnapshot = _database.DataSnapshot; + export import OnDisconnect = _database.OnDisconnect; + export import EventType = _database.EventType; + export import Query = _database.Query; + export import Reference = _database.Reference; + export import ThenableReference = _database.ThenableReference; + export import enableLogging = _database.enableLogging; + export import ServerValue = _database.ServerValue; } declare namespace admin.messaging { diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000000..a481e6320b --- /dev/null +++ b/src/test.ts @@ -0,0 +1,5 @@ +const obj = { + 'hello': 'ok' +}; + +export default obj; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index e13292b408..e9fec600fb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "lib": ["es2015"], "outDir": "lib", // We manually craft typings in src/index.d.ts instead of auto-generating them. - // "declaration": true, + // Currently, only RTDB has auto-generated typings, the rest default to src/index.d.ts + "declaration": true, "rootDir": "." }, "files": [ From d8c4011cef6c27904e32ac9b81e2b3f41cae6253 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 12:54:58 -0400 Subject: [PATCH 02/46] Add auto-generated typings for RTDB --- gulpfile.js | 5 +- src/database-tmp.d.ts | 1664 --------------------------- src/database/database-internal.ts | 227 ++++ src/database/database.ts | 265 +---- src/database/index.ts | 61 +- src/firebase-app-internal.ts | 225 ++++ src/firebase-app.ts | 214 +--- src/firebase-namespace.ts | 3 +- src/index.d.ts | 18 +- src/utils/index.ts | 3 +- test/integration/database.spec.ts | 10 +- test/unit/database/database.spec.ts | 2 +- test/unit/utils.ts | 3 +- 13 files changed, 582 insertions(+), 2118 deletions(-) delete mode 100644 src/database-tmp.d.ts create mode 100644 src/database/database-internal.ts create mode 100644 src/firebase-app-internal.ts diff --git a/gulpfile.js b/gulpfile.js index 1d7f9ae98f..692bb33dea 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -79,9 +79,8 @@ gulp.task('compile', function() { .pipe(filter([ '**', '!lib/default-namespace.d.ts', - // '!lib/firebase-namespace.d.ts', - // '!lib/firebase-app.d.ts', - // '!lib/firebase-service.d.ts', + '!lib/firebase-namespace.d.ts', + '!lib/firebase-service.d.ts', '!lib/**/*-internal.d.ts', '!lib/auth/*.d.ts', '!lib/firestore/*.d.ts', diff --git a/src/database-tmp.d.ts b/src/database-tmp.d.ts deleted file mode 100644 index 1bdf425ea1..0000000000 --- a/src/database-tmp.d.ts +++ /dev/null @@ -1,1664 +0,0 @@ -import * as _admin from './index.d'; - -/* eslint-disable @typescript-eslint/ban-types */ - -export namespace admin.database { - - /** - * The Firebase Realtime Database service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.database()`](admin.database#database). - * - * See - * {@link - * https://firebase.google.com/docs/database/admin/start/ - * Introduction to the Admin Database API} - * for a full guide on how to use the Firebase Realtime Database service. - */ - interface Database { - app: _admin.app.App; - - /** - * Disconnects from the server (all Database operations will be completed - * offline). - * - * The client automatically maintains a persistent connection to the Database - * server, which will remain active indefinitely and reconnect when - * disconnected. However, the `goOffline()` and `goOnline()` methods may be used - * to control the client connection in cases where a persistent connection is - * undesirable. - * - * While offline, the client will no longer receive data updates from the - * Database. However, all Database operations performed locally will continue to - * immediately fire events, allowing your application to continue behaving - * normally. Additionally, each operation performed locally will automatically - * be queued and retried upon reconnection to the Database server. - * - * To reconnect to the Database and begin receiving remote events, see - * `goOnline()`. - * - * @example - * ```javascript - * admin.database().goOffline(); - * ``` - */ - goOffline(): void; - - /** - * Reconnects to the server and synchronizes the offline Database state - * with the server state. - * - * This method should be used after disabling the active connection with - * `goOffline()`. Once reconnected, the client will transmit the proper data - * and fire the appropriate events so that your client "catches up" - * automatically. - * - * @example - * ```javascript - * admin.database().goOnline(); - * ``` - */ - goOnline(): void; - - /** - * Returns a `Reference` representing the location in the Database - * corresponding to the provided path. Also can be invoked with an existing - * `Reference` as the argument. In that case returns a new `Reference` - * pointing to the same location. If no path argument is - * provided, returns a `Reference` that represents the root of the Database. - * - * @example - * ```javascript - * // Get a reference to the root of the Database - * var rootRef = admin.database.ref(); - * ``` - * - * @example - * ```javascript - * // Get a reference to the /users/ada node - * var adaRef = admin.database().ref("users/ada"); - * // The above is shorthand for the following operations: - * //var rootRef = admin.database().ref(); - * //var adaRef = rootRef.child("users/ada"); - * ``` - * - * @example - * ```javascript - * var adaRef = admin.database().ref("users/ada"); - * // Get a new reference pointing to the same location. - * var anotherAdaRef = admin.database().ref(adaRef); - * ``` - * - * - * @param path Optional path representing - * the location the returned `Reference` will point. Alternatively, a - * `Reference` object to copy. If not provided, the returned `Reference` will - * point to the root of the Database. - * @return If a path is provided, a `Reference` - * pointing to the provided path. Otherwise, a `Reference` pointing to the - * root of the Database. - */ - ref(path?: string | admin.database.Reference): admin.database.Reference; - - /** - * Returns a `Reference` representing the location in the Database - * corresponding to the provided Firebase URL. - * - * An exception is thrown if the URL is not a valid Firebase Database URL or it - * has a different domain than the current `Database` instance. - * - * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored - * and are not applied to the returned `Reference`. - * - * @example - * ```javascript - * // Get a reference to the root of the Database - * var rootRef = admin.database().ref("https://.firebaseio.com"); - * ``` - * - * @example - * ```javascript - * // Get a reference to the /users/ada node - * var adaRef = admin.database().ref("https://.firebaseio.com/users/ada"); - * ``` - * - * @param url The Firebase URL at which the returned `Reference` will - * point. - * @return A `Reference` pointing to the provided Firebase URL. - */ - refFromURL(url: string): admin.database.Reference; - - /** - * Gets the currently applied security rules as a string. The return value consists of - * the rules source including comments. - * - * @return A promise fulfilled with the rules as a raw string. - */ - getRules(): Promise; - - /** - * Gets the currently applied security rules as a parsed JSON object. Any comments in - * the original source are stripped away. - * - * @return A promise fulfilled with the parsed rules object. - */ - getRulesJSON(): Promise; - - /** - * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is - * specified as a string or a Buffer, it may include comments. - * - * @param source Source of the rules to apply. Must not be `null` or empty. - * @return Resolves when the rules are set on the Realtime Database. - */ - setRules(source: string | Buffer | object): Promise; - } - - /** - * A `DataSnapshot` contains data from a Database location. - * - * Any time you read data from the Database, you receive the data as a - * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach - * with `on()` or `once()`. You can extract the contents of the snapshot as a - * JavaScript object by calling the `val()` method. Alternatively, you can - * traverse into the snapshot by calling `child()` to return child snapshots - * (which you could then call `val()` on). - * - * A `DataSnapshot` is an efficiently generated, immutable copy of the data at - * a Database location. It cannot be modified and will never change (to modify - * data, you always call the `set()` method on a `Reference` directly). - */ - interface DataSnapshot { - key: string | null; - ref: admin.database.Reference; - - /** - * Gets another `DataSnapshot` for the location at the specified relative path. - * - * Passing a relative path to the `child()` method of a DataSnapshot returns - * another `DataSnapshot` for the location at the specified relative path. The - * relative path can either be a simple child name (for example, "ada") or a - * deeper, slash-separated path (for example, "ada/name/first"). If the child - * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot` - * whose value is `null`) is returned. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Test for the existence of certain keys within a DataSnapshot - * var ref = admin.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var name = snapshot.child("name").val(); // {first:"Ada",last:"Lovelace"} - * var firstName = snapshot.child("name/first").val(); // "Ada" - * var lastName = snapshot.child("name").child("last").val(); // "Lovelace" - * var age = snapshot.child("age").val(); // null - * }); - * ``` - * - * @param path A relative path to the location of child data. - * @return `DataSnapshot` for the location at the specified relative path. - */ - child(path: string): admin.database.DataSnapshot; - - /** - * Returns true if this `DataSnapshot` contains any data. It is slightly more - * efficient than using `snapshot.val() !== null`. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Test for the existence of certain keys within a DataSnapshot - * var ref = admin.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.exists(); // true - * var b = snapshot.child("name").exists(); // true - * var c = snapshot.child("name/first").exists(); // true - * var d = snapshot.child("name/middle").exists(); // false - * }); - * ``` - * - * @return Whether this `DataSnapshot` contains any data. - */ - exists(): boolean; - - /** - * Exports the entire contents of the DataSnapshot as a JavaScript object. - * - * The `exportVal()` method is similar to `val()`, except priority information - * is included (if available), making it suitable for backing up your data. - * - * @return The DataSnapshot's contents as a JavaScript value (Object, - * Array, string, number, boolean, or `null`). - */ - exportVal(): any; - - /** - * Enumerates the top-level children in the `DataSnapshot`. - * - * Because of the way JavaScript objects work, the ordering of data in the - * JavaScript object returned by `val()` is not guaranteed to match the ordering - * on the server nor the ordering of `child_added` events. That is where - * `forEach()` comes in handy. It guarantees the children of a `DataSnapshot` - * will be iterated in their query order. - * - * If no explicit `orderBy*()` method is used, results are returned - * ordered by key (unless priorities are used, in which case, results are - * returned by priority). - * - * @example - * ```javascript - * - * // Assume we have the following data in the Database: - * { - * "users": { - * "ada": { - * "first": "Ada", - * "last": "Lovelace" - * }, - * "alan": { - * "first": "Alan", - * "last": "Turing" - * } - * } - * } - * - * // Loop through users in order with the forEach() method. The callback - * // provided to forEach() will be called synchronously with a DataSnapshot - * // for each child: - * var query = admin.database().ref("users").orderByKey(); - * query.once("value") - * .then(function(snapshot) { - * snapshot.forEach(function(childSnapshot) { - * // key will be "ada" the first time and "alan" the second time - * var key = childSnapshot.key; - * // childData will be the actual contents of the child - * var childData = childSnapshot.val(); - * }); - * }); - * ``` - * - * @example - * ```javascript - * // You can cancel the enumeration at any point by having your callback - * // function return true. For example, the following code sample will only - * // fire the callback function one time: - * var query = admin.database().ref("users").orderByKey(); - * query.once("value") - * .then(function(snapshot) { - * snapshot.forEach(function(childSnapshot) { - * var key = childSnapshot.key; // "ada" - * - * // Cancel enumeration - * return true; - * }); - * }); - * ``` - * - * @param action A function - * that will be called for each child `DataSnapshot`. The callback can return - * true to cancel further enumeration. - * @return True if enumeration was canceled due to your callback - * returning true. - */ - forEach(action: (a: admin.database.DataSnapshot) => boolean | void): boolean; - - /** - * Gets the priority value of the data in this `DataSnapshot`. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - * - * @return The the priority value of the data in this `DataSnapshot`. - */ - getPriority(): string | number | null; - - /** - * Returns true if the specified child path has (non-null) data. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Determine which child keys in DataSnapshot have data. - * var ref = admin.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var hasName = snapshot.hasChild("name"); // true - * var hasAge = snapshot.hasChild("age"); // false - * }); - * ``` - * - * @param path A relative path to the location of a potential child. - * @return `true` if data exists at the specified child path; else - * `false`. - */ - hasChild(path: string): boolean; - - /** - * Returns whether or not the `DataSnapshot` has any non-`null` child - * properties. - * - * You can use `hasChildren()` to determine if a `DataSnapshot` has any - * children. If it does, you can enumerate them using `forEach()`. If it - * doesn't, then either this snapshot contains a primitive value (which can be - * retrieved with `val()`) or it is empty (in which case, `val()` will return - * `null`). - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = admin.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.hasChildren(); // true - * var b = snapshot.child("name").hasChildren(); // true - * var c = snapshot.child("name/first").hasChildren(); // false - * }); - * ``` - * - * @return True if this snapshot has any children; else false. - */ - hasChildren(): boolean; - - /** - * Returns the number of child properties of this `DataSnapshot`. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = admin.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.numChildren(); // 1 ("name") - * var b = snapshot.child("name").numChildren(); // 2 ("first", "last") - * var c = snapshot.child("name/first").numChildren(); // 0 - * }); - * ``` - * - * @return The number of child properties of this `DataSnapshot`. - */ - numChildren(): number; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): Object | null; - - /** - * Extracts a JavaScript value from a `DataSnapshot`. - * - * Depending on the data in a `DataSnapshot`, the `val()` method may return a - * scalar type (string, number, or boolean), an array, or an object. It may also - * return null, indicating that the `DataSnapshot` is empty (contains no data). - * - * @example - * ```javascript - * // Write and then read back a string from the Database. - * ref.set("hello") - * .then(function() { - * return ref.once("value"); - * }) - * .then(function(snapshot) { - * var data = snapshot.val(); // data === "hello" - * }); - * ``` - * - * @example - * ```javascript - * // Write and then read back a JavaScript object from the Database. - * ref.set({ name: "Ada", age: 36 }) - * .then(function() { - * return ref.once("value"); - * }) - * .then(function(snapshot) { - * var data = snapshot.val(); - * // data is { "name": "Ada", "age": 36 } - * // data.name === "Ada" - * // data.age === 36 - * }); - * ``` - * - * @return The DataSnapshot's contents as a JavaScript value (Object, - * Array, string, number, boolean, or `null`). - */ - val(): any; - } - - /** - * The `onDisconnect` class allows you to write or clear data when your client - * disconnects from the Database server. These updates occur whether your - * client disconnects cleanly or not, so you can rely on them to clean up data - * even if a connection is dropped or a client crashes. - * - * The `onDisconnect` class is most commonly used to manage presence in - * applications where it is useful to detect how many clients are connected and - * when other clients disconnect. See - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information. - * - * To avoid problems when a connection is dropped before the requests can be - * transferred to the Database server, these functions should be called before - * any data is written. - * - * Note that `onDisconnect` operations are only triggered once. If you want an - * operation to occur each time a disconnect occurs, you'll need to re-establish - * the `onDisconnect` operations each time you reconnect. - */ - interface OnDisconnect { - - /** - * Cancels all previously queued `onDisconnect()` set or update events for this - * location and all children. - * - * If a write has been queued for this location via a `set()` or `update()` at a - * parent location, the write at this location will be canceled, though all - * other siblings will still be written. - * - * @example - * ```javascript - * var ref = admin.database().ref("onlineState"); - * ref.onDisconnect().set(false); - * // ... sometime later - * ref.onDisconnect().cancel(); - * ``` - * - * @param onComplete An optional callback function that is - * called when synchronization to the server has completed. The callback - * will be passed a single parameter: null for success, or an Error object - * indicating a failure. - * @return Resolves when synchronization to the server is complete. - */ - cancel(onComplete?: (a: Error | null) => any): Promise; - - /** - * Ensures the data at this location is deleted when the client is disconnected - * (due to closing the browser, navigating to a new page, or network issues). - * - * @param onComplete An optional callback function that is - * called when synchronization to the server has completed. The callback - * will be passed a single parameter: null for success, or an Error object - * indicating a failure. - * @return Resolves when synchronization to the server is complete. - */ - remove(onComplete?: (a: Error | null) => any): Promise; - - /** - * Ensures the data at this location is set to the specified value when the - * client is disconnected (due to closing the browser, navigating to a new page, - * or network issues). - * - * `set()` is especially useful for implementing "presence" systems, where a - * value should be changed or cleared when a user disconnects so that they - * appear "offline" to other users. See - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information. - * - * Note that `onDisconnect` operations are only triggered once. If you want an - * operation to occur each time a disconnect occurs, you'll need to re-establish - * the `onDisconnect` operations each time. - * - * @example - * ```javascript - * var ref = admin.database().ref("users/ada/status"); - * ref.onDisconnect().set("I disconnected!"); - * ``` - * - * @param value The value to be written to this location on - * disconnect (can be an object, array, string, number, boolean, or null). - * @param onComplete An optional callback function that - * will be called when synchronization to the database server has completed. - * The callback will be passed a single parameter: null for success, or an - * `Error` object indicating a failure. - * @return A promise that resolves when synchronization to the database is complete. - */ - set(value: any, onComplete?: (a: Error | null) => any): Promise; - - /** - * Ensures the data at this location is set to the specified value and priority - * when the client is disconnected (due to closing the browser, navigating to a - * new page, or network issues). - * - * @param value The value to be written to this location on - * disconnect (can be an object, array, string, number, boolean, or null). - * @param priority - * @param onComplete An optional callback function that is - * called when synchronization to the server has completed. The callback - * will be passed a single parameter: null for success, or an Error object - * indicating a failure. - * @return A promise that resolves when synchronization to the database is complete. - */ - setWithPriority( - value: any, - priority: number | string | null, - onComplete?: (a: Error | null) => any - ): Promise; - - /** - * Writes multiple values at this location when the client is disconnected (due - * to closing the browser, navigating to a new page, or network issues). - * - * The `values` argument contains multiple property-value pairs that will be - * written to the Database together. Each child property can either be a simple - * property (for example, "name") or a relative path (for example, "name/first") - * from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update - * only the referenced properties at the current location (instead of replacing - * all the child properties at the current location). - * - * See {@link https://firebase.google.com/docs/reference/admin/node/admin.database.Reference#update} - * for examples of using the connected version of `update`. - * - * @example - * ```javascript - * var ref = admin.database().ref("users/ada"); - * ref.update({ - * onlineState: true, - * status: "I'm online." - * }); - * ref.onDisconnect().update({ - * onlineState: false, - * status: "I'm offline." - * }); - * ``` - * - * @param values Object containing multiple values. - * @param onComplete An optional callback function that will - * be called when synchronization to the server has completed. The - * callback will be passed a single parameter: null for success, or an Error - * object indicating a failure. - * @return Resolves when synchronization to the - * Database is complete. - */ - update(values: Object, onComplete?: (a: Error | null) => any): Promise; - } - - type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; - - /** - * A `Query` sorts and filters the data at a Database location so only a subset - * of the child data is included. This can be used to order a collection of - * data by some attribute (for example, height of dinosaurs) as well as to - * restrict a large list of items (for example, chat messages) down to a number - * suitable for synchronizing to the client. Queries are created by chaining - * together one or more of the filter methods defined here. - * - * Just as with a `Reference`, you can receive data from a `Query` by using the - * `on()` method. You will only receive events and `DataSnapshot`s for the - * subset of the data that matches your query. - * - * See - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data} for more information. - */ - interface Query { - ref: admin.database.Reference; - - /** - * Creates a `Query` with the specified ending point. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary - * starting and ending points for your queries. - * - * The ending point is inclusive, so children with exactly the specified value - * will be included in the query. The optional key argument can be used to - * further limit the range of the query. If it is specified, then children that - * have exactly the specified value must also have a key name less than or equal - * to the specified key. - * - * You can read more about `endAt()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find all dinosaurs whose names come before Pterodactyl lexicographically. - * var ref = admin.database().ref("dinosaurs"); - * ref.orderByKey().endAt("pterodactyl").on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * ``` - * - * @param value The value to end at. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to end at, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * priority. - * @return A new `Query` object. - */ - endAt(value: number | string | boolean | null, key?: string): admin.database.Query; - - /** - * Creates a `Query` that includes children that match the specified value. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows us to choose arbitrary - * starting and ending points for our queries. - * - * The optional key argument can be used to further limit the range of the - * query. If it is specified, then children that have exactly the specified - * value must also have exactly the specified key as their key name. This can be - * used to filter result sets with many matches for the same value. - * - * You can read more about `equalTo()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * // Find all dinosaurs whose height is exactly 25 meters. - * var ref = admin.database().ref("dinosaurs"); - * ref.orderByChild("height").equalTo(25).on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * - * @param value The value to match for. The - * argument type depends on which `orderBy*()` function was used in this - * query. Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to start at, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * priority. - * @return A new `Query` object. - */ - equalTo(value: number | string | boolean | null, key?: string): admin.database.Query; - - /** - * Returns whether or not the current and provided queries represent the same - * location, have the same query parameters, and are from the same instance of - * `admin.app.App`. - * - * Two `Reference` objects are equivalent if they represent the same location - * and are from the same instance of `admin.app.App`. - * - * Two `Query` objects are equivalent if they represent the same location, have - * the same query parameters, and are from the same instance of `admin.app.App`. - * Equivalent queries share the same sort order, limits, and starting and - * ending points. - * - * @example - * ```javascript - * var rootRef = admin.database().ref(); - * var usersRef = rootRef.child("users"); - * - * usersRef.isEqual(rootRef); // false - * usersRef.isEqual(rootRef.child("users")); // true - * usersRef.parent.isEqual(rootRef); // true - * ``` - * - * @example - * ```javascript - * var rootRef = admin.database().ref(); - * var usersRef = rootRef.child("users"); - * var usersQuery = usersRef.limitToLast(10); - * - * usersQuery.isEqual(usersRef); // false - * usersQuery.isEqual(usersRef.limitToLast(10)); // true - * usersQuery.isEqual(rootRef.limitToLast(10)); // false - * usersQuery.isEqual(usersRef.orderByKey().limitToLast(10)); // false - * ``` - * - * @param other The query to compare against. - * @return Whether or not the current and provided queries are - * equivalent. - */ - isEqual(other: admin.database.Query | null): boolean; - - /** - * Generates a new `Query` limited to the first specific number of children. - * - * The `limitToFirst()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the first 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * You can read more about `limitToFirst()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find the two shortest dinosaurs. - * var ref = admin.database().ref("dinosaurs"); - * ref.orderByChild("height").limitToFirst(2).on("child_added", function(snapshot) { - * // This will be called exactly two times (unless there are less than two - * // dinosaurs in the Database). - * - * // It will also get fired again if one of the first two dinosaurs is - * // removed from the data set, as a new dinosaur will now be the second - * // shortest. - * console.log(snapshot.key); - * }); - * ``` - * - * @param limit The maximum number of nodes to include in this query. - * @return A `Query` object. - */ - limitToFirst(limit: number): admin.database.Query; - - /** - * Generates a new `Query` object limited to the last specific number of - * children. - * - * The `limitToLast()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the last 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * You can read more about `limitToLast()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find the two heaviest dinosaurs. - * var ref = admin.database().ref("dinosaurs"); - * ref.orderByChild("weight").limitToLast(2).on("child_added", function(snapshot) { - * // This callback will be triggered exactly two times, unless there are - * // fewer than two dinosaurs stored in the Database. It will also get fired - * // for every new, heavier dinosaur that gets added to the data set. - * console.log(snapshot.key); - * }); - * ``` - * - * @param limit The maximum number of nodes to include in this query. - * @return A `Query` object. - */ - limitToLast(limit: number): admin.database.Query; - - /** - * Detaches a callback previously attached with `on()`. - * - * Detach a callback previously attached with `on()`. Note that if `on()` was - * called multiple times with the same eventType and callback, the callback - * will be called multiple times for each event, and `off()` must be called - * multiple times to remove the callback. Calling `off()` on a parent listener - * will not automatically remove listeners registered on child nodes, `off()` - * must also be called on any child listeners to remove the callback. - * - * If a callback is not specified, all callbacks for the specified eventType - * will be removed. Similarly, if no eventType or callback is specified, all - * callbacks for the `Reference` will be removed. - * - * @example - * ```javascript - * var onValueChange = function(dataSnapshot) { ... }; - * ref.on('value', onValueChange); - * ref.child('meta-data').on('child_added', onChildAdded); - * // Sometime later... - * ref.off('value', onValueChange); - * - * // You must also call off() for any child listeners on ref - * // to cancel those callbacks - * ref.child('meta-data').off('child_added', onValueAdded); - * ``` - * - * @example - * ```javascript - * // Or you can save a line of code by using an inline function - * // and on()'s return value. - * var onValueChange = ref.on('value', function(dataSnapshot) { ... }); - * // Sometime later... - * ref.off('value', onValueChange); - * ``` - * - * @param eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param callback The callback function that was passed to `on()`. - * @param context The context that was passed to `on()`. - */ - off( - eventType?: admin.database.EventType, - callback?: (a: admin.database.DataSnapshot, b?: string | null) => any, - context?: Object | null - ): void; - - /** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Use `off( )` to stop receiving updates. See - * {@link https://firebase.google.com/docs/database/web/retrieve-data - * Retrieve Data on the Web} - * for more details. - * - *

value event

- * - * This event will trigger once with the initial data stored at this location, - * and then trigger again each time the data changes. The `DataSnapshot` passed - * to the callback will be for the location at which `on()` was called. It - * won't trigger until the entire contents has been synchronized. If the - * location has no data, it will be triggered with an empty `DataSnapshot` - * (`val()` will return `null`). - * - *

child_added event

- * - * This event will be triggered once for each initial child at this location, - * and it will be triggered again every time a new child is added. The - * `DataSnapshot` passed into the callback will reflect the data for the - * relevant child. For ordering purposes, it is passed a second argument which - * is a string containing the key of the previous sibling child by sort order - * (or `null` if it is the first child). - * - *

child_removed event

- * - * This event will be triggered once every time a child is removed. The - * `DataSnapshot` passed into the callback will be the old data for the child - * that was removed. A child will get removed when either: - * - * - a client explicitly calls `remove()` on that child or one of its ancestors - * - a client calls `set(null)` on that child or one of its ancestors - * - that child has all of its children removed - * - there is a query in effect which now filters out the child (because it's - * sort order changed or the max limit was hit) - * - *

child_changed event

- * - * This event will be triggered when the data stored in a child (or any of its - * descendants) changes. Note that a single `child_changed` event may represent - * multiple changes to the child. The `DataSnapshot` passed to the callback will - * contain the new child contents. For ordering purposes, the callback is also - * passed a second argument which is a string containing the key of the previous - * sibling child by sort order (or `null` if it is the first child). - * - *

child_moved event

- * - * This event will be triggered when a child's sort order changes such that its - * position relative to its siblings changes. The `DataSnapshot` passed to the - * callback will be for the data of the child that has moved. It is also passed - * a second argument which is a string containing the key of the previous - * sibling child by sort order (or `null` if it is the first child). - * - * @example - * ```javascript - * // Handle a new value. - * ref.on('value', function(dataSnapshot) { - * ... - * }); - * ``` - * - * @example - * ```javascript - * // Handle a new child. - * ref.on('child_added', function(childSnapshot, prevChildKey) { - * ... - * }); - * ``` - * - * @example - * ```javascript - * // Handle child removal. - * ref.on('child_removed', function(oldChildSnapshot) { - * ... - * }); - * ``` - * - * @example - * ```javascript - * // Handle child data changes. - * ref.on('child_changed', function(childSnapshot, prevChildKey) { - * ... - * }); - * ``` - * - * @example - * ```javascript - * // Handle child ordering changes. - * ref.on('child_moved', function(childSnapshot, prevChildKey) { - * ... - * }); - * ``` - * - * @param eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param callback A callback that fires when the specified event occurs. The callback is - * passed a DataSnapshot. For ordering purposes, "child_added", - * "child_changed", and "child_moved" will also be passed a string containing - * the key of the previous child, by sort order (or `null` if it is the - * first child). - * @param cancelCallbackOrContext An optional - * callback that will be notified if your event subscription is ever canceled - * because your client does not have permission to read this data (or it had - * permission but has now lost it). This callback will be passed an `Error` - * object indicating why the failure occurred. - * @param context If provided, this object will be used as `this` - * when calling your callback(s). - * @return The provided - * callback function is returned unmodified. This is just for convenience if - * you want to pass an inline function to `on()`, but store the callback - * function for later passing to `off()`. - */ - on( - eventType: admin.database.EventType, - callback: (a: admin.database.DataSnapshot, b?: string | null) => any, - cancelCallbackOrContext?: ((a: Error) => any) | Object | null, - context?: Object | null - ): (a: admin.database.DataSnapshot | null, b?: string) => any; - - /** - * Listens for exactly one event of the specified event type, and then stops - * listening. - * - * This is equivalent to calling `on()`, and then calling `off()` inside the - * callback function. See `on()` for details on the event types. - * - * @example - * ```javascript - * // Basic usage of .once() to read the data located at ref. - * ref.once('value') - * .then(function(dataSnapshot) { - * // handle read data. - * }); - * ``` - * - * @param eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param successCallback A callback that fires when the specified event occurs. The callback is - * passed a `DataSnapshot`. For ordering purposes, "child_added", - * "child_changed", and "child_moved" will also be passed a string containing - * the key of the previous child by sort order (or `null` if it is the - * first child). - * @param failureCallbackOrContext An optional - * callback that will be notified if your client does not have permission to - * read the data. This callback will be passed an `Error` object indicating - * why the failure occurred. - * @param context If provided, this object will be used as `this` - * when calling your callback(s). - * @return {!Promise} - */ - once( - eventType: admin.database.EventType, - successCallback?: (a: admin.database.DataSnapshot, b?: string | null ) => any, - failureCallbackOrContext?: ((a: Error) => void) | Object | null, - context?: Object | null - ): Promise; - - /** - * Generates a new `Query` object ordered by the specified child key. - * - * Queries can only order by one key at a time. Calling `orderByChild()` - * multiple times on the same query is an error. - * - * Firebase queries allow you to order your data by any child key on the fly. - * However, if you know in advance what your indexes will be, you can define - * them via the .indexOn rule in your Security Rules for better performance. See - * the {@link https://firebase.google.com/docs/database/security/indexing-data - * .indexOn} rule for more information. - * - * You can read more about `orderByChild()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * ```javascript - * var ref = admin.database().ref("dinosaurs"); - * ref.orderByChild("height").on("child_added", function(snapshot) { - * console.log(snapshot.key + " was " + snapshot.val().height + " m tall"); - * }); - * ``` - * - * @param path - * @return A new `Query` object. - */ - orderByChild(path: string): admin.database.Query; - - /** - * Generates a new `Query` object ordered by key. - * - * Sorts the results of a query by their (ascending) key values. - * - * You can read more about `orderByKey()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * ```javascript - * var ref = admin.database().ref("dinosaurs"); - * ref.orderByKey().on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * ``` - * - * @return A new `Query` object. - */ - orderByKey(): admin.database.Query; - - /** - * Generates a new `Query` object ordered by priority. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data} for alternatives to priority. - * - * @return A new `Query` object. - */ - orderByPriority(): admin.database.Query; - - /** - * Generates a new `Query` object ordered by value. - * - * If the children of a query are all scalar values (string, number, or - * boolean), you can order the results by their (ascending) values. - * - * You can read more about `orderByValue()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * ```javascript - * var scoresRef = admin.database().ref("scores"); - * scoresRef.orderByValue().limitToLast(3).on("value", function(snapshot) { - * snapshot.forEach(function(data) { - * console.log("The " + data.key + " score is " + data.val()); - * }); - * }); - * ``` - * - * @return A new `Query` object. - */ - orderByValue(): admin.database.Query; - - /** - * Creates a `Query` with the specified starting point. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary - * starting and ending points for your queries. - * - * The starting point is inclusive, so children with exactly the specified value - * will be included in the query. The optional key argument can be used to - * further limit the range of the query. If it is specified, then children that - * have exactly the specified value must also have a key name greater than or - * equal to the specified key. - * - * You can read more about `startAt()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find all dinosaurs that are at least three meters tall. - * var ref = admin.database().ref("dinosaurs"); - * ref.orderByChild("height").startAt(3).on("child_added", function(snapshot) { - * console.log(snapshot.key) - * }); - * ``` - * - * @param value The value to start at. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to start at. This argument is allowed if - * ordering by child, value, or priority. - * @return A new `Query` object. - */ - startAt(value: number | string | boolean | null, key?: string): admin.database.Query; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): Object; - - /** - * Gets the absolute URL for this location. - * - * The `toString()` method returns a URL that is ready to be put into a browser, - * curl command, or a `admin.database().refFromURL()` call. Since all of those - * expect the URL to be url-encoded, `toString()` returns an encoded URL. - * - * Append '.json' to the returned URL when typed into a browser to download - * JSON-formatted data. If the location is secured (that is, not publicly - * readable), you will get a permission-denied error. - * - * @example - * ```javascript - * // Calling toString() on a root Firebase reference returns the URL where its - * // data is stored within the Database: - * var rootRef = admin.database().ref(); - * var rootUrl = rootRef.toString(); - * // rootUrl === "https://sample-app.firebaseio.com/". - * - * // Calling toString() at a deeper Firebase reference returns the URL of that - * // deep path within the Database: - * var adaRef = rootRef.child('users/ada'); - * var adaURL = adaRef.toString(); - * // adaURL === "https://sample-app.firebaseio.com/users/ada". - * ``` - * - * @return The absolute URL for this location. - * @override - */ - toString(): string; - } - - /** - * A `Reference` represents a specific location in your Database and can be used - * for reading or writing data to that Database location. - * - * You can reference the root or child location in your Database by calling - * `admin.database().ref()` or `admin.database().ref("child/path")`. - * - * Writing is done with the `set()` method and reading can be done with the - * `on()` method. See - * {@link - * https://firebase.google.com/docs/database/web/read-and-write - * Read and Write Data on the Web} - */ - interface Reference extends admin.database.Query { - - /** - * The last part of the `Reference`'s path. - * - * For example, `"ada"` is the key for - * `https://.firebaseio.com/users/ada`. - * - * The key of a root `Reference` is `null`. - * - * @example - * ```javascript - * // The key of a root reference is null - * var rootRef = admin.database().ref(); - * var key = rootRef.key; // key === null - * ``` - * - * @example - * ```javascript - * // The key of any non-root reference is the last token in the path - * var adaRef = admin.database().ref("users/ada"); - * var key = adaRef.key; // key === "ada" - * key = adaRef.child("name/last").key; // key === "last" - * ``` - */ - key: string | null; - - /** - * The parent location of a `Reference`. - * - * The parent of a root `Reference` is `null`. - * - * @example - * ```javascript - * // The parent of a root reference is null - * var rootRef = admin.database().ref(); - * parent = rootRef.parent; // parent === null - * ``` - * - * @example - * ```javascript - * // The parent of any non-root reference is the parent location - * var usersRef = admin.database().ref("users"); - * var adaRef = admin.database().ref("users/ada"); - * // usersRef and adaRef.parent represent the same location - * ``` - */ - parent: admin.database.Reference | null; - - /** - * The root `Reference` of the Database. - * - * @example - * ```javascript - * // The root of a root reference is itself - * var rootRef = admin.database().ref(); - * // rootRef and rootRef.root represent the same location - * ``` - * - * @example - * ```javascript - * // The root of any non-root reference is the root location - * var adaRef = admin.database().ref("users/ada"); - * // rootRef and adaRef.root represent the same location - * ``` - */ - root: admin.database.Reference; - /** @deprecated Removed in next major release to match Web SDK typings. */ - path: string; - - /** - * Gets a `Reference` for the location at the specified relative path. - * - * The relative path can either be a simple child name (for example, "ada") or - * a deeper slash-separated path (for example, "ada/name/first"). - * - * @example - * ```javascript - * var usersRef = admin.database().ref('users'); - * var adaRef = usersRef.child('ada'); - * var adaFirstNameRef = adaRef.child('name/first'); - * var path = adaFirstNameRef.toString(); - * // path is now 'https://sample-app.firebaseio.com/users/ada/name/first' - * ``` - * - * @param path A relative path from this location to the desired child - * location. - * @return The specified child location. - */ - child(path: string): admin.database.Reference; - - /** - * Returns an `OnDisconnect` object - see - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information on how - * to use it. - * - * @return An `OnDisconnect` object . - */ - onDisconnect(): admin.database.OnDisconnect; - - /** - * Generates a new child location using a unique key and returns its - * `Reference`. - * - * This is the most common pattern for adding data to a collection of items. - * - * If you provide a value to `push()`, the value will be written to the - * generated location. If you don't pass a value, nothing will be written to the - * Database and the child will remain empty (but you can use the `Reference` - * elsewhere). - * - * The unique key generated by `push()` are ordered by the current time, so the - * resulting list of items will be chronologically sorted. The keys are also - * designed to be unguessable (they contain 72 random bits of entropy). - * - * - * See - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data - * Append to a list of data} - *
See - * {@link - * https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html - * The 2^120 Ways to Ensure Unique Identifiers} - * - * @example - * ```javascript - * var messageListRef = admin.database().ref('message_list'); - * var newMessageRef = messageListRef.push(); - * newMessageRef.set({ - * user_id: 'ada', - * text: 'The Analytical Engine weaves algebraical patterns just as the Jacquard loom weaves flowers and leaves.' - * }); - * // We've appended a new message to the message_list location. - * var path = newMessageRef.toString(); - * // path will be something like - * // 'https://sample-app.firebaseio.com/message_list/-IKo28nwJLH0Nc5XeFmj' - * ``` - * - * @param value Optional value to be written at the generated location. - * @param onComplete Callback called when write to server is - * complete. - * @return Combined `Promise` and - * `Reference`; resolves when write is complete, but can be used immediately - * as the `Reference` to the child location. - */ - push(value?: any, onComplete?: (a: Error | null) => any): admin.database.ThenableReference; - - /** - * Removes the data at this Database location. - * - * Any data at child locations will also be deleted. - * - * The effect of the remove will be visible immediately and the corresponding - * event 'value' will be triggered. Synchronization of the remove to the - * Firebase servers will also be started, and the returned Promise will resolve - * when complete. If provided, the onComplete callback will be called - * asynchronously after synchronization has finished. - * - * @example - * ```javascript - * var adaRef = admin.database().ref('users/ada'); - * adaRef.remove() - * .then(function() { - * console.log("Remove succeeded.") - * }) - * .catch(function(error) { - * console.log("Remove failed: " + error.message) - * }); - * ``` - * - * @param onComplete Callback called when write to server is - * complete. - * @return Resolves when remove on server is complete. - */ - remove(onComplete?: (a: Error | null) => any): Promise; - - /** - * Writes data to this Database location. - * - * This will overwrite any data at this location and all child locations. - * - * The effect of the write will be visible immediately, and the corresponding - * events ("value", "child_added", etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * Passing `null` for the new value is equivalent to calling `remove()`; namely, - * all data at this location and all child locations will be deleted. - * - * `set()` will remove any priority stored at this location, so if priority is - * meant to be preserved, you need to use `setWithPriority()` instead. - * - * Note that modifying data with `set()` will cancel any pending transactions - * at that location, so extreme care should be taken if mixing `set()` and - * `transaction()` to modify the same data. - * - * A single `set()` will generate a single "value" event at the location where - * the `set()` was performed. - * - * @example - * ```javascript - * var adaNameRef = admin.database().ref('users/ada/name'); - * adaNameRef.child('first').set('Ada'); - * adaNameRef.child('last').set('Lovelace'); - * // We've written 'Ada' to the Database location storing Ada's first name, - * // and 'Lovelace' to the location storing her last name. - * ``` - * - * @example - * ```javascript - * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }); - * // Exact same effect as the previous example, except we've written - * // Ada's first and last name simultaneously. - * ``` - * - * @example - * ```javascript - * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }) - * .then(function() { - * console.log('Synchronization succeeded'); - * }) - * .catch(function(error) { - * console.log('Synchronization failed'); - * }); - * // Same as the previous example, except we will also log a message - * // when the data has finished synchronizing. - * ``` - * - * @param value The value to be written (string, number, boolean, object, - * array, or null). - * @param onComplete Callback called when write to server is - * complete. - * @return Resolves when write to server is complete. - */ - set(value: any, onComplete?: (a: Error | null) => any): Promise; - - /** - * Sets a priority for the data at this Database location. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - * - * @param priority - * @param onComplete - * @return - */ - setPriority( - priority: string | number | null, - onComplete: (a: Error | null) => any - ): Promise; - - /** - * Writes data the Database location. Like `set()` but also specifies the - * priority for that data. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - * - * @param newVal - * @param newPriority - * @param onComplete - * @return - */ - setWithPriority( - newVal: any, newPriority: string | number | null, - onComplete?: (a: Error | null) => any - ): Promise; - - /** - * Atomically modifies the data at this location. - * - * Atomically modify the data at this location. Unlike a normal `set()`, which - * just overwrites the data regardless of its previous value, `transaction()` is - * used to modify the existing value to a new value, ensuring there are no - * conflicts with other clients writing to the same location at the same time. - * - * To accomplish this, you pass `transaction()` an update function which is used - * to transform the current value into a new value. If another client writes to - * the location before your new value is successfully written, your update - * function will be called again with the new current value, and the write will - * be retried. This will happen repeatedly until your write succeeds without - * conflict or you abort the transaction by not returning a value from your - * update function. - * - * Note: Modifying data with `set()` will cancel any pending transactions at - * that location, so extreme care should be taken if mixing `set()` and - * `transaction()` to update the same data. - * - * Note: When using transactions with Security and Firebase Rules in place, be - * aware that a client needs `.read` access in addition to `.write` access in - * order to perform a transaction. This is because the client-side nature of - * transactions requires the client to read the data in order to transactionally - * update it. - * - * @example - * ```javascript - * // Increment Ada's rank by 1. - * var adaRankRef = admin.database().ref('users/ada/rank'); - * adaRankRef.transaction(function(currentRank) { - * // If users/ada/rank has never been set, currentRank will be `null`. - * return currentRank + 1; - * }); - * ``` - * - * @example - * ```javascript - * // Try to create a user for ada, but only if the user id 'ada' isn't - * // already taken - * var adaRef = admin.database().ref('users/ada'); - * adaRef.transaction(function(currentData) { - * if (currentData === null) { - * return { name: { first: 'Ada', last: 'Lovelace' } }; - * } else { - * console.log('User ada already exists.'); - * return; // Abort the transaction. - * } - * }, function(error, committed, snapshot) { - * if (error) { - * console.log('Transaction failed abnormally!', error); - * } else if (!committed) { - * console.log('We aborted the transaction (because ada already exists).'); - * } else { - * console.log('User ada added!'); - * } - * console.log("Ada's data: ", snapshot.val()); - * }); - * ``` - * - * @param transactionUpdate A developer-supplied function which - * will be passed the current data stored at this location (as a JavaScript - * object). The function should return the new value it would like written (as - * a JavaScript object). If `undefined` is returned (i.e. you return with no - * arguments) the transaction will be aborted and the data at this location - * will not be modified. - * @param onComplete A callback - * function that will be called when the transaction completes. The callback - * is passed three arguments: a possibly-null `Error`, a `boolean` indicating - * whether the transaction was committed, and a `DataSnapshot` indicating the - * final result. If the transaction failed abnormally, the first argument will - * be an `Error` object indicating the failure cause. If the transaction - * finished normally, but no data was committed because no data was returned - * from `transactionUpdate`, then second argument will be false. If the - * transaction completed and committed data to Firebase, the second argument - * will be true. Regardless, the third argument will be a `DataSnapshot` - * containing the resulting data in this location. - * @param applyLocally By default, events are raised each time the - * transaction update function runs. So if it is run multiple times, you may - * see intermediate states. You can set this to false to suppress these - * intermediate states and instead wait until the transaction has completed - * before events are raised. - * @return Returns a Promise that can optionally be used instead of the `onComplete` - * callback to handle success and failure. - */ - transaction( - transactionUpdate: (a: any) => any, - onComplete?: (a: Error | null, b: boolean, c: admin.database.DataSnapshot | null) => any, - applyLocally?: boolean - ): Promise<{ - committed: boolean; - snapshot: admin.database.DataSnapshot | null; - }>; - - /** - * Writes multiple values to the Database at once. - * - * The `values` argument contains multiple property-value pairs that will be - * written to the Database together. Each child property can either be a simple - * property (for example, "name") or a relative path (for example, - * "name/first") from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update - * only the referenced properties at the current location (instead of replacing - * all the child properties at the current location). - * - * The effect of the write will be visible immediately, and the corresponding - * events ('value', 'child_added', etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * A single `update()` will generate a single "value" event at the location - * where the `update()` was performed, regardless of how many children were - * modified. - * - * Note that modifying data with `update()` will cancel any pending - * transactions at that location, so extreme care should be taken if mixing - * `update()` and `transaction()` to modify the same data. - * - * Passing `null` to `update()` will remove the data at this location. - * - * See - * {@link - * https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html - * Introducing multi-location updates and more}. - * - * @example - * ```javascript - * var adaNameRef = admin.database().ref('users/ada/name'); - * // Modify the 'first' and 'last' properties, but leave other data at - * // adaNameRef unchanged. - * adaNameRef.update({ first: 'Ada', last: 'Lovelace' }); - * ``` - * - * @param values Object containing multiple values. - * @param onComplete Callback called when write to server is - * complete. - * @return Resolves when update on server is complete. - */ - update(values: Object, onComplete?: (a: Error | null) => any): Promise; - } - - /** - * @extends {Reference} - */ - interface ThenableReference extends admin.database.Reference, Promise { } - - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ - function enableLogging(logger?: boolean | ((message: string) => any), persistent?: boolean): any; -} - -/* eslint-disable @typescript-eslint/no-unused-vars */ -export namespace admin.database.ServerValue { - - /** - * A placeholder value for auto-populating the current timestamp (time - * since the Unix epoch, in milliseconds) as determined by the Firebase - * servers. - * - * @example - * ```javascript - * var sessionsRef = firebase.database().ref("sessions"); - * sessionsRef.push({ - * startedAt: firebase.database.ServerValue.TIMESTAMP - * }); - * ``` - */ - const TIMESTAMP: Object; - - /** - * Returns a placeholder value that can be used to atomically increment the - * current database value by the provided delta. - * - * @param delta the amount to modify the current value atomically. - * @return a placeholder value for modifying data atomically server-side. - */ - function increment(delta: number): Object; -} diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts new file mode 100644 index 0000000000..97c3301b20 --- /dev/null +++ b/src/database/database-internal.ts @@ -0,0 +1,227 @@ +import { URL } from 'url'; +import * as path from 'path'; + +import { FirebaseApp } from '../firebase-app'; +import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; +import { Database } from '@firebase/database'; +import './database'; + +import * as validator from '../utils/validator'; +import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; +import { getSdkVersion } from '../utils/index'; + + +/** + * Internals of a Database instance. + */ +class DatabaseInternals implements FirebaseServiceInternalsInterface { + + public databases: { + [dbUrl: string]: Database; + } = {}; + + /** + * Deletes the service and its associated resources. + * + * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. + */ + public delete(): Promise { + for (const dbUrl of Object.keys(this.databases)) { + const db: Database = this.databases[dbUrl]; + db.INTERNAL.delete(); + } + return Promise.resolve(undefined); + } +} + +export class DatabaseService implements FirebaseServiceInterface { + + public readonly INTERNAL: DatabaseInternals = new DatabaseInternals(); + + private readonly appInternal: FirebaseApp; + + constructor(app: FirebaseApp) { + if (!validator.isNonNullObject(app) || !('options' in app)) { + throw new FirebaseDatabaseError({ + code: 'invalid-argument', + message: 'First argument passed to admin.database() must be a valid Firebase app instance.', + }); + } + this.appInternal = app; + } + + /** + * Returns the app associated with this DatabaseService instance. + * + * @return {FirebaseApp} The app associated with this DatabaseService instance. + */ + get app(): FirebaseApp { + return this.appInternal; + } + + public getDatabase(url?: string): Database { + const dbUrl: string = this.ensureUrl(url); + if (!validator.isNonEmptyString(dbUrl)) { + throw new FirebaseDatabaseError({ + code: 'invalid-argument', + message: 'Database URL must be a valid, non-empty URL string.', + }); + } + + let db: Database = this.INTERNAL.databases[dbUrl]; + if (typeof db === 'undefined') { + const rtdb = require('@firebase/database'); // eslint-disable-line @typescript-eslint/no-var-requires + db = rtdb.initStandalone(this.appInternal, dbUrl, getSdkVersion()).instance; + + const rulesClient = new DatabaseRulesClient(this.app, dbUrl); + db.getRules = () => { + return rulesClient.getRules(); + }; + db.getRulesJSON = () => { + return rulesClient.getRulesJSON(); + }; + db.setRules = (source) => { + return rulesClient.setRules(source); + }; + + this.INTERNAL.databases[dbUrl] = db; + } + return db; + } + + private ensureUrl(url?: string): string { + if (typeof url !== 'undefined') { + return url; + } else if (typeof this.appInternal.options.databaseURL !== 'undefined') { + return this.appInternal.options.databaseURL; + } + throw new FirebaseDatabaseError({ + code: 'invalid-argument', + message: 'Can\'t determine Firebase Database URL.', + }); + } +} + +const RULES_URL_PATH = '.settings/rules.json'; + +/** + * A helper client for managing RTDB security rules. + */ +class DatabaseRulesClient { + + private readonly dbUrl: string; + private readonly httpClient: AuthorizedHttpClient; + + constructor(app: FirebaseApp, dbUrl: string) { + const parsedUrl = new URL(dbUrl); + parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH); + this.dbUrl = parsedUrl.toString(); + this.httpClient = new AuthorizedHttpClient(app); + } + + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @return {Promise} A promise fulfilled with the rules as a raw string. + */ + public getRules(): Promise { + const req: HttpRequestConfig = { + method: 'GET', + url: this.dbUrl, + }; + return this.httpClient.send(req) + .then((resp) => { + if (!resp.text) { + throw new FirebaseAppError(AppErrorCodes.INTERNAL_ERROR, 'HTTP response missing data.'); + } + return resp.text; + }) + .catch((err) => { + throw this.handleError(err); + }); + } + + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @return {Promise} A promise fulfilled with the parsed rules source. + */ + public getRulesJSON(): Promise { + const req: HttpRequestConfig = { + method: 'GET', + url: this.dbUrl, + data: { format: 'strict' }, + }; + return this.httpClient.send(req) + .then((resp) => { + return resp.data; + }) + .catch((err) => { + throw this.handleError(err); + }); + } + + /** + * Sets the specified rules on the Firebase Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param {string|Buffer|object} source Source of the rules to apply. Must not be `null` + * or empty. + * @return {Promise} Resolves when the rules are set on the Database. + */ + public setRules(source: string | Buffer | object): Promise { + if (!validator.isNonEmptyString(source) && + !validator.isBuffer(source) && + !validator.isNonNullObject(source)) { + const error = new FirebaseDatabaseError({ + code: 'invalid-argument', + message: 'Source must be a non-empty string, Buffer or an object.', + }); + return Promise.reject(error); + } + + const req: HttpRequestConfig = { + method: 'PUT', + url: this.dbUrl, + data: source, + headers: { + 'content-type': 'application/json; charset=utf-8', + }, + }; + return this.httpClient.send(req) + .then(() => { + return; + }) + .catch((err) => { + throw this.handleError(err); + }); + } + + private handleError(err: Error): Error { + if (err instanceof HttpError) { + return new FirebaseDatabaseError({ + code: AppErrorCodes.INTERNAL_ERROR, + message: this.getErrorMessage(err), + }); + } + return err; + } + + private getErrorMessage(err: HttpError): string { + const intro = 'Error while accessing security rules'; + try { + const body: { error?: string } = err.response.data; + if (body && body.error) { + return `${intro}: ${body.error.trim()}`; + } + } catch { + // Ignore parsing errors + } + + return `${intro}: ${err.response.text}`; + } +} + diff --git a/src/database/database.ts b/src/database/database.ts index 7654bc66fd..87c96b2a53 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -1,238 +1,53 @@ -import { URL } from 'url'; -import * as path from 'path'; - -import { FirebaseApp } from '../firebase-app'; -import * as admin from '../'; -import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; -import { Database, Reference } from '@firebase/database'; +/*! + * Copyright 2020 Google Inc. + * + * 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 * as validator from '../utils/validator'; -import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; -import { getSdkVersion } from '../utils/index'; +import { Reference } from '@firebase/database'; +export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; /** - * Internals of a Database instance. + * @extends {Reference} */ -class DatabaseInternals implements FirebaseServiceInternalsInterface { - - public databases: { - [dbUrl: string]: Database; - } = {}; - - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - for (const dbUrl of Object.keys(this.databases)) { - const db: Database = this.databases[dbUrl]; - db.INTERNAL.delete(); - } - return Promise.resolve(undefined); - } -} +export interface ThenableReference extends Reference, Promise { } declare module '@firebase/database' { interface Database { + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @return A promise fulfilled with the rules as a raw string. + */ getRules(): Promise; - getRulesJSON(): Promise; - setRules(source: string | Buffer | object): Promise; - } -} - -export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; -export interface ThenableReference extends Reference, Promise { } - -export class DatabaseService implements FirebaseServiceInterface { - - public readonly INTERNAL: DatabaseInternals = new DatabaseInternals(); - - private readonly appInternal: admin.app.App; - - constructor(app: FirebaseApp) { - if (!validator.isNonNullObject(app) || !('options' in app)) { - throw new FirebaseDatabaseError({ - code: 'invalid-argument', - message: 'First argument passed to admin.database() must be a valid Firebase app instance.', - }); - } - this.appInternal = app; - } - - /** - * Returns the app associated with this DatabaseService instance. - * - * @return {FirebaseApp} The app associated with this DatabaseService instance. - */ - get app(): FirebaseApp { - return this.appInternal; - } - - public getDatabase(url?: string): Database { - const dbUrl: string = this.ensureUrl(url); - if (!validator.isNonEmptyString(dbUrl)) { - throw new FirebaseDatabaseError({ - code: 'invalid-argument', - message: 'Database URL must be a valid, non-empty URL string.', - }); - } - - let db: Database = this.INTERNAL.databases[dbUrl]; - if (typeof db === 'undefined') { - const rtdb = require('@firebase/database'); // eslint-disable-line @typescript-eslint/no-var-requires - db = rtdb.initStandalone(this.appInternal, dbUrl, getSdkVersion()).instance; - - const rulesClient = new DatabaseRulesClient(this.app, dbUrl); - db.getRules = () => { - return rulesClient.getRules(); - }; - db.getRulesJSON = () => { - return rulesClient.getRulesJSON(); - }; - db.setRules = (source) => { - return rulesClient.setRules(source); - }; - this.INTERNAL.databases[dbUrl] = db; - } - return db; - } - - private ensureUrl(url?: string): string { - if (typeof url !== 'undefined') { - return url; - } else if (typeof this.appInternal.options.databaseURL !== 'undefined') { - return this.appInternal.options.databaseURL; - } - throw new FirebaseDatabaseError({ - code: 'invalid-argument', - message: 'Can\'t determine Firebase Database URL.', - }); - } -} - -const RULES_URL_PATH = '.settings/rules.json'; - -/** - * A helper client for managing RTDB security rules. - */ -class DatabaseRulesClient { - - private readonly dbUrl: string; - private readonly httpClient: AuthorizedHttpClient; - - constructor(app: FirebaseApp, dbUrl: string) { - const parsedUrl = new URL(dbUrl); - parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH); - this.dbUrl = parsedUrl.toString(); - this.httpClient = new AuthorizedHttpClient(app); - } - - /** - * Gets the currently applied security rules as a string. The return value consists of - * the rules source including comments. - * - * @return {Promise} A promise fulfilled with the rules as a raw string. - */ - public getRules(): Promise { - const req: HttpRequestConfig = { - method: 'GET', - url: this.dbUrl, - }; - return this.httpClient.send(req) - .then((resp) => { - if (!resp.text) { - throw new FirebaseAppError(AppErrorCodes.INTERNAL_ERROR, 'HTTP response missing data.'); - } - return resp.text; - }) - .catch((err) => { - throw this.handleError(err); - }); - } - - /** - * Gets the currently applied security rules as a parsed JSON object. Any comments in - * the original source are stripped away. - * - * @return {Promise} A promise fulfilled with the parsed rules source. - */ - public getRulesJSON(): Promise { - const req: HttpRequestConfig = { - method: 'GET', - url: this.dbUrl, - data: { format: 'strict' }, - }; - return this.httpClient.send(req) - .then((resp) => { - return resp.data; - }) - .catch((err) => { - throw this.handleError(err); - }); - } - - /** - * Sets the specified rules on the Firebase Database instance. If the rules source is - * specified as a string or a Buffer, it may include comments. - * - * @param {string|Buffer|object} source Source of the rules to apply. Must not be `null` - * or empty. - * @return {Promise} Resolves when the rules are set on the Database. - */ - public setRules(source: string | Buffer | object): Promise { - if (!validator.isNonEmptyString(source) && - !validator.isBuffer(source) && - !validator.isNonNullObject(source)) { - const error = new FirebaseDatabaseError({ - code: 'invalid-argument', - message: 'Source must be a non-empty string, Buffer or an object.', - }); - return Promise.reject(error); - } - - const req: HttpRequestConfig = { - method: 'PUT', - url: this.dbUrl, - data: source, - headers: { - 'content-type': 'application/json; charset=utf-8', - }, - }; - return this.httpClient.send(req) - .then(() => { - return; - }) - .catch((err) => { - throw this.handleError(err); - }); - } - - private handleError(err: Error): Error { - if (err instanceof HttpError) { - return new FirebaseDatabaseError({ - code: AppErrorCodes.INTERNAL_ERROR, - message: this.getErrorMessage(err), - }); - } - return err; - } - - private getErrorMessage(err: HttpError): string { - const intro = 'Error while accessing security rules'; - try { - const body: {error?: string} = err.response.data; - if (body && body.error) { - return `${intro}: ${body.error.trim()}`; - } - } catch { - // Ignore parsing errors - } + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @return A promise fulfilled with the parsed rules object. + */ + getRulesJSON(): Promise; - return `${intro}: ${err.response.text}`; + /** + * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param source Source of the rules to apply. Must not be `null` or empty. + * @return Resolves when the rules are set on the Realtime Database. + */ + setRules(source: string | Buffer | object): Promise; } } - diff --git a/src/database/index.ts b/src/database/index.ts index f5772f04bf..39669d1166 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -14,5 +14,62 @@ * limitations under the License. */ -export * from '@firebase/database'; -export * from './database'; +import { FirebaseApp } from '../firebase-app'; +import * as firebaseDbApi from '@firebase/database'; +import * as adminDbApi from './database'; + +/** + * Gets the {@link admin.database.Database `Database`} service for the default + * app or a given app. + * + * `admin.database()` can be called with no arguments to access the default + * app's {@link admin.database.Database `Database`} service or as + * `admin.database(app)` to access the + * {@link admin.database.Database `Database`} service associated with a specific + * app. + * + * `admin.database` is also a namespace that can be used to access global + * constants and methods associated with the `Database` service. + * + * @example + * ```javascript + * // Get the Database service for the default app + * var defaultDatabase = admin.database(); + * ``` + * + * @example + * ```javascript + * // Get the Database service for a specific app + * var otherDatabase = admin.database(app); + * ``` + * + * @param App whose `Database` service to + * return. If not provided, the default `Database` service will be returned. + * + * @return The default `Database` service if no app + * is provided or the `Database` service associated with the provided app. + */ +export function database(app: FirebaseApp): firebaseDbApi.Database { + return app.database(); +} + +// This is unfortunate. But it seems we must define a namespace to make +// the typings work correctly. Otherwise `admin.database()` cannot be called +// like a function. It would be great if we can find an alternative. +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace database { + // See https://github.com/microsoft/TypeScript/issues/4336 + /* eslint-disable @typescript-eslint/no-unused-vars */ + // For context: github.com/typescript-eslint/typescript-eslint/issues/363 + export import Database = firebaseDbApi.Database; + export import DataSnapshot = firebaseDbApi.DataSnapshot; + export import OnDisconnect = firebaseDbApi.OnDisconnect; + export import EventType = adminDbApi.EventType; + export import Query = firebaseDbApi.Query; + export import Reference = firebaseDbApi.Reference; + export import ThenableReference = adminDbApi.ThenableReference; + export import enableLogging = firebaseDbApi.enableLogging; + export import ServerValue = firebaseDbApi.ServerValue; +} + + diff --git a/src/firebase-app-internal.ts b/src/firebase-app-internal.ts new file mode 100644 index 0000000000..03a131b317 --- /dev/null +++ b/src/firebase-app-internal.ts @@ -0,0 +1,225 @@ +/*! + * Copyright 2020 Google Inc. + * + * 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 { FirebaseApp } from './firebase-app'; +import { Credential, GoogleOAuthAccessToken } from './auth/credential'; +import * as validator from './utils/validator'; +import { AppErrorCodes, FirebaseAppError } from './utils/error'; + +import { Agent } from 'http'; + +/** + * Type representing a callback which is called every time an app lifecycle event occurs. + */ +export type AppHook = (event: string, app: FirebaseApp) => void; + +/** + * Type representing the options object passed into initializeApp(). + */ +export interface FirebaseAppOptions { + credential?: Credential; + databaseAuthVariableOverride?: object | null; + databaseURL?: string; + serviceAccountId?: string; + storageBucket?: string; + projectId?: string; + httpAgent?: Agent; +} + +/** + * Type representing a Firebase OAuth access token (derived from a Google OAuth2 access token) which + * can be used to authenticate to Firebase services such as the Realtime Database and Auth. + */ +export interface FirebaseAccessToken { + accessToken: string; + expirationTime: number; +} + +/** + * Internals of a FirebaseApp instance. + */ +export class FirebaseAppInternals { + private isDeleted_ = false; + private cachedToken_: FirebaseAccessToken; + private cachedTokenPromise_: Promise | null; + private tokenListeners_: Array<(token: string) => void>; + private tokenRefreshTimeout_: NodeJS.Timer; + + constructor(private credential_: Credential) { + this.tokenListeners_ = []; + } + + /** + * Gets an auth token for the associated app. + * + * @param {boolean} forceRefresh Whether or not to force a token refresh. + * @return {Promise} A Promise that will be fulfilled with the current or + * new token. + */ + public getToken(forceRefresh?: boolean): Promise { + const expired = this.cachedToken_ && this.cachedToken_.expirationTime < Date.now(); + if (this.cachedTokenPromise_ && !forceRefresh && !expired) { + return this.cachedTokenPromise_ + .catch((error) => { + // Update the cached token promise to avoid caching errors. Set it to resolve with the + // cached token if we have one (and return that promise since the token has still not + // expired). + if (this.cachedToken_) { + this.cachedTokenPromise_ = Promise.resolve(this.cachedToken_); + return this.cachedTokenPromise_; + } + + // Otherwise, set the cached token promise to null so that it will force a refresh next + // time getToken() is called. + this.cachedTokenPromise_ = null; + + // And re-throw the caught error. + throw error; + }); + } else { + // Clear the outstanding token refresh timeout. This is a noop if the timeout is undefined. + clearTimeout(this.tokenRefreshTimeout_); + + // this.credential_ may be an external class; resolving it in a promise helps us + // protect against exceptions and upgrades the result to a promise in all cases. + this.cachedTokenPromise_ = Promise.resolve(this.credential_.getAccessToken()) + .then((result: GoogleOAuthAccessToken) => { + // Since the developer can provide the credential implementation, we want to weakly verify + // the return type until the type is properly exported. + if (!validator.isNonNullObject(result) || + typeof result.expires_in !== 'number' || + typeof result.access_token !== 'string') { + throw new FirebaseAppError( + AppErrorCodes.INVALID_CREDENTIAL, + `Invalid access token generated: "${JSON.stringify(result)}". Valid access ` + + 'tokens must be an object with the "expires_in" (number) and "access_token" ' + + '(string) properties.', + ); + } + + const token: FirebaseAccessToken = { + accessToken: result.access_token, + expirationTime: Date.now() + (result.expires_in * 1000), + }; + + const hasAccessTokenChanged = (this.cachedToken_ && this.cachedToken_.accessToken !== token.accessToken); + const hasExpirationChanged = (this.cachedToken_ && this.cachedToken_.expirationTime !== token.expirationTime); + if (!this.cachedToken_ || hasAccessTokenChanged || hasExpirationChanged) { + this.cachedToken_ = token; + this.tokenListeners_.forEach((listener) => { + listener(token.accessToken); + }); + } + + // Establish a timeout to proactively refresh the token every minute starting at five + // minutes before it expires. Once a token refresh succeeds, no further retries are + // needed; if it fails, retry every minute until the token expires (resulting in a total + // of four retries: at 4, 3, 2, and 1 minutes). + let refreshTimeInSeconds = (result.expires_in - (5 * 60)); + let numRetries = 4; + + // In the rare cases the token is short-lived (that is, it expires in less than five + // minutes from when it was fetched), establish the timeout to refresh it after the + // current minute ends and update the number of retries that should be attempted before + // the token expires. + if (refreshTimeInSeconds <= 0) { + refreshTimeInSeconds = result.expires_in % 60; + numRetries = Math.floor(result.expires_in / 60) - 1; + } + + // The token refresh timeout keeps the Node.js process alive, so only create it if this + // instance has not already been deleted. + if (numRetries && !this.isDeleted_) { + this.setTokenRefreshTimeout(refreshTimeInSeconds * 1000, numRetries); + } + + return token; + }) + .catch((error) => { + let errorMessage = (typeof error === 'string') ? error : error.message; + + errorMessage = 'Credential implementation provided to initializeApp() via the ' + + '"credential" property failed to fetch a valid Google OAuth2 access token with the ' + + `following error: "${errorMessage}".`; + + if (errorMessage.indexOf('invalid_grant') !== -1) { + errorMessage += ' There are two likely causes: (1) your server time is not properly ' + + 'synced or (2) your certificate key file has been revoked. To solve (1), re-sync the ' + + 'time on your server. To solve (2), make sure the key ID for your key file is still ' + + 'present at https://console.firebase.google.com/iam-admin/serviceaccounts/project. If ' + + 'not, generate a new key file at ' + + 'https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk.'; + } + + throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage); + }); + + return this.cachedTokenPromise_; + } + } + + /** + * Adds a listener that is called each time a token changes. + * + * @param {function(string)} listener The listener that will be called with each new token. + */ + public addAuthTokenListener(listener: (token: string) => void): void { + this.tokenListeners_.push(listener); + if (this.cachedToken_) { + listener(this.cachedToken_.accessToken); + } + } + + /** + * Removes a token listener. + * + * @param {function(string)} listener The listener to remove. + */ + public removeAuthTokenListener(listener: (token: string) => void): void { + this.tokenListeners_ = this.tokenListeners_.filter((other) => other !== listener); + } + + /** + * Deletes the FirebaseAppInternals instance. + */ + public delete(): void { + this.isDeleted_ = true; + + // Clear the token refresh timeout so it doesn't keep the Node.js process alive. + clearTimeout(this.tokenRefreshTimeout_); + } + + /** + * Establishes timeout to refresh the Google OAuth2 access token used by the SDK. + * + * @param {number} delayInMilliseconds The delay to use for the timeout. + * @param {number} numRetries The number of times to retry fetching a new token if the prior fetch + * failed. + */ + private setTokenRefreshTimeout(delayInMilliseconds: number, numRetries: number): void { + this.tokenRefreshTimeout_ = setTimeout(() => { + this.getToken(/* forceRefresh */ true) + .catch(() => { + // Ignore the error since this might just be an intermittent failure. If we really cannot + // refresh the token, an error will be logged once the existing token expires and we try + // to fetch a fresh one. + if (numRetries > 0) { + this.setTokenRefreshTimeout(60 * 1000, numRetries - 1); + } + }); + }, delayInMilliseconds); + } +} diff --git a/src/firebase-app.ts b/src/firebase-app.ts index 4baf2de08d..373216aafe 100644 --- a/src/firebase-app.ts +++ b/src/firebase-app.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2017 Google Inc. + * Copyright 2020 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Credential, GoogleOAuthAccessToken, getApplicationDefault } from './auth/credential'; +import { getApplicationDefault } from './auth/credential'; import * as validator from './utils/validator'; import { deepCopy, deepExtend } from './utils/deep-copy'; import { FirebaseServiceInterface } from './firebase-service'; @@ -26,7 +26,7 @@ import { MachineLearning } from './machine-learning/machine-learning'; import { Messaging } from './messaging/messaging'; import { Storage } from './storage/storage'; import { Database } from '@firebase/database'; -import { DatabaseService } from './database/database'; +import { DatabaseService } from './database/database-internal'; import { Firestore } from '@google-cloud/firestore'; import { FirestoreService } from './firestore/firestore'; import { InstanceId } from './instance-id/instance-id'; @@ -35,211 +35,7 @@ import { ProjectManagement } from './project-management/project-management'; import { SecurityRules } from './security-rules/security-rules'; import { RemoteConfig } from './remote-config/remote-config'; -import { Agent } from 'http'; - -/** - * Type representing a callback which is called every time an app lifecycle event occurs. - */ -export type AppHook = (event: string, app: FirebaseApp) => void; - -/** - * Type representing the options object passed into initializeApp(). - */ -export interface FirebaseAppOptions { - credential?: Credential; - databaseAuthVariableOverride?: object | null; - databaseURL?: string; - serviceAccountId?: string; - storageBucket?: string; - projectId?: string; - httpAgent?: Agent; -} - -/** - * Type representing a Firebase OAuth access token (derived from a Google OAuth2 access token) which - * can be used to authenticate to Firebase services such as the Realtime Database and Auth. - */ -export interface FirebaseAccessToken { - accessToken: string; - expirationTime: number; -} - -/** - * Internals of a FirebaseApp instance. - */ -export class FirebaseAppInternals { - private isDeleted_ = false; - private cachedToken_: FirebaseAccessToken; - private cachedTokenPromise_: Promise | null; - private tokenListeners_: Array<(token: string) => void>; - private tokenRefreshTimeout_: NodeJS.Timer; - - constructor(private credential_: Credential) { - this.tokenListeners_ = []; - } - - /** - * Gets an auth token for the associated app. - * - * @param {boolean} forceRefresh Whether or not to force a token refresh. - * @return {Promise} A Promise that will be fulfilled with the current or - * new token. - */ - public getToken(forceRefresh?: boolean): Promise { - const expired = this.cachedToken_ && this.cachedToken_.expirationTime < Date.now(); - if (this.cachedTokenPromise_ && !forceRefresh && !expired) { - return this.cachedTokenPromise_ - .catch((error) => { - // Update the cached token promise to avoid caching errors. Set it to resolve with the - // cached token if we have one (and return that promise since the token has still not - // expired). - if (this.cachedToken_) { - this.cachedTokenPromise_ = Promise.resolve(this.cachedToken_); - return this.cachedTokenPromise_; - } - - // Otherwise, set the cached token promise to null so that it will force a refresh next - // time getToken() is called. - this.cachedTokenPromise_ = null; - - // And re-throw the caught error. - throw error; - }); - } else { - // Clear the outstanding token refresh timeout. This is a noop if the timeout is undefined. - clearTimeout(this.tokenRefreshTimeout_); - - // this.credential_ may be an external class; resolving it in a promise helps us - // protect against exceptions and upgrades the result to a promise in all cases. - this.cachedTokenPromise_ = Promise.resolve(this.credential_.getAccessToken()) - .then((result: GoogleOAuthAccessToken) => { - // Since the developer can provide the credential implementation, we want to weakly verify - // the return type until the type is properly exported. - if (!validator.isNonNullObject(result) || - typeof result.expires_in !== 'number' || - typeof result.access_token !== 'string') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_CREDENTIAL, - `Invalid access token generated: "${JSON.stringify(result)}". Valid access ` + - 'tokens must be an object with the "expires_in" (number) and "access_token" ' + - '(string) properties.', - ); - } - - const token: FirebaseAccessToken = { - accessToken: result.access_token, - expirationTime: Date.now() + (result.expires_in * 1000), - }; - - const hasAccessTokenChanged = (this.cachedToken_ && this.cachedToken_.accessToken !== token.accessToken); - const hasExpirationChanged = (this.cachedToken_ && this.cachedToken_.expirationTime !== token.expirationTime); - if (!this.cachedToken_ || hasAccessTokenChanged || hasExpirationChanged) { - this.cachedToken_ = token; - this.tokenListeners_.forEach((listener) => { - listener(token.accessToken); - }); - } - - // Establish a timeout to proactively refresh the token every minute starting at five - // minutes before it expires. Once a token refresh succeeds, no further retries are - // needed; if it fails, retry every minute until the token expires (resulting in a total - // of four retries: at 4, 3, 2, and 1 minutes). - let refreshTimeInSeconds = (result.expires_in - (5 * 60)); - let numRetries = 4; - - // In the rare cases the token is short-lived (that is, it expires in less than five - // minutes from when it was fetched), establish the timeout to refresh it after the - // current minute ends and update the number of retries that should be attempted before - // the token expires. - if (refreshTimeInSeconds <= 0) { - refreshTimeInSeconds = result.expires_in % 60; - numRetries = Math.floor(result.expires_in / 60) - 1; - } - - // The token refresh timeout keeps the Node.js process alive, so only create it if this - // instance has not already been deleted. - if (numRetries && !this.isDeleted_) { - this.setTokenRefreshTimeout(refreshTimeInSeconds * 1000, numRetries); - } - - return token; - }) - .catch((error) => { - let errorMessage = (typeof error === 'string') ? error : error.message; - - errorMessage = 'Credential implementation provided to initializeApp() via the ' + - '"credential" property failed to fetch a valid Google OAuth2 access token with the ' + - `following error: "${errorMessage}".`; - - if (errorMessage.indexOf('invalid_grant') !== -1) { - errorMessage += ' There are two likely causes: (1) your server time is not properly ' + - 'synced or (2) your certificate key file has been revoked. To solve (1), re-sync the ' + - 'time on your server. To solve (2), make sure the key ID for your key file is still ' + - 'present at https://console.firebase.google.com/iam-admin/serviceaccounts/project. If ' + - 'not, generate a new key file at ' + - 'https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk.'; - } - - throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage); - }); - - return this.cachedTokenPromise_; - } - } - - /** - * Adds a listener that is called each time a token changes. - * - * @param {function(string)} listener The listener that will be called with each new token. - */ - public addAuthTokenListener(listener: (token: string) => void): void { - this.tokenListeners_.push(listener); - if (this.cachedToken_) { - listener(this.cachedToken_.accessToken); - } - } - - /** - * Removes a token listener. - * - * @param {function(string)} listener The listener to remove. - */ - public removeAuthTokenListener(listener: (token: string) => void): void { - this.tokenListeners_ = this.tokenListeners_.filter((other) => other !== listener); - } - - /** - * Deletes the FirebaseAppInternals instance. - */ - public delete(): void { - this.isDeleted_ = true; - - // Clear the token refresh timeout so it doesn't keep the Node.js process alive. - clearTimeout(this.tokenRefreshTimeout_); - } - - /** - * Establishes timeout to refresh the Google OAuth2 access token used by the SDK. - * - * @param {number} delayInMilliseconds The delay to use for the timeout. - * @param {number} numRetries The number of times to retry fetching a new token if the prior fetch - * failed. - */ - private setTokenRefreshTimeout(delayInMilliseconds: number, numRetries: number): void { - this.tokenRefreshTimeout_ = setTimeout(() => { - this.getToken(/* forceRefresh */ true) - .catch(() => { - // Ignore the error since this might just be an intermittent failure. If we really cannot - // refresh the token, an error will be logged once the existing token expires and we try - // to fetch a fresh one. - if (numRetries > 0) { - this.setTokenRefreshTimeout(60 * 1000, numRetries - 1); - } - }); - }, delayInMilliseconds); - } -} - +import { FirebaseAppInternals, FirebaseAppOptions } from './firebase-app-internal'; /** @@ -307,7 +103,7 @@ export class FirebaseApp { */ public database(url?: string): Database { const service: DatabaseService = this.ensureService_('database', () => { - const dbService: typeof DatabaseService = require('./database/database').DatabaseService; + const dbService: typeof DatabaseService = require('./database/database-internal').DatabaseService; return new dbService(this); }); return service.getDatabase(url); diff --git a/src/firebase-namespace.ts b/src/firebase-namespace.ts index 916132ebbf..d4c81ca668 100644 --- a/src/firebase-namespace.ts +++ b/src/firebase-namespace.ts @@ -18,7 +18,8 @@ import fs = require('fs'); import { Agent } from 'http'; import { deepExtend } from './utils/deep-copy'; import { AppErrorCodes, FirebaseAppError } from './utils/error'; -import { AppHook, FirebaseApp, FirebaseAppOptions } from './firebase-app'; +import { FirebaseApp } from './firebase-app'; +import { AppHook, FirebaseAppOptions } from './firebase-app-internal'; import { FirebaseServiceFactory, FirebaseServiceInterface } from './firebase-service'; import { Credential, diff --git a/src/index.d.ts b/src/index.d.ts index c9d450bf95..4651c4fc71 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -741,15 +741,15 @@ declare namespace admin.credential { } declare namespace admin.database { - export import Database = _database.Database; - export import DataSnapshot = _database.DataSnapshot; - export import OnDisconnect = _database.OnDisconnect; - export import EventType = _database.EventType; - export import Query = _database.Query; - export import Reference = _database.Reference; - export import ThenableReference = _database.ThenableReference; - export import enableLogging = _database.enableLogging; - export import ServerValue = _database.ServerValue; + export import Database = _database.database.Database; + export import DataSnapshot = _database.database.DataSnapshot; + export import OnDisconnect = _database.database.OnDisconnect; + export import EventType = _database.database.EventType; + export import Query = _database.database.Query; + export import Reference = _database.database.Reference; + export import ThenableReference = _database.database.ThenableReference; + export import enableLogging = _database.database.enableLogging; + export import ServerValue = _database.database.ServerValue; } declare namespace admin.messaging { diff --git a/src/utils/index.ts b/src/utils/index.ts index 8c3a540dc0..53911b75e6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { FirebaseApp, FirebaseAppOptions } from '../firebase-app'; +import { FirebaseApp } from '../firebase-app'; +import { FirebaseAppOptions } from '../firebase-app-internal'; import { ServiceAccountCredential, ComputeEngineCredential } from '../auth/credential'; import * as validator from './validator'; diff --git a/test/integration/database.spec.ts b/test/integration/database.spec.ts index e8d0a746a4..71efcbc185 100644 --- a/test/integration/database.spec.ts +++ b/test/integration/database.spec.ts @@ -29,6 +29,11 @@ const expect = chai.expect; const path = 'adminNodeSdkManualTest'; +interface DatabaseRecord { + success: boolean; + timestamp: string; +} + describe('admin.database', () => { before(() => { @@ -97,7 +102,7 @@ describe('admin.database', () => { it('once() returns the current value of the reference', () => { return ref.once('value') .then((snapshot) => { - const value = snapshot.val(); + const value = snapshot.val() as DatabaseRecord; expect(value.success).to.be.true; expect(typeof value.timestamp).to.equal('number'); }); @@ -139,7 +144,7 @@ describe('admin.database', () => { it('once() returns the current value of the reference', () => { return refWithUrl.once('value') .then((snapshot) => { - const value = snapshot.val(); + const value = snapshot.val() as DatabaseRecord; expect(value.success).to.be.true; expect(typeof value.timestamp).to.equal('number'); }); @@ -164,6 +169,7 @@ describe('admin.database', () => { }); it('admin.database().getRulesJSON() returns currently defined rules as an object', () => { + return admin.database().getRulesJSON().then((result) => { return expect(result).to.be.not.undefined; }); diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index 78d73f21d6..bae9320fc4 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -22,7 +22,7 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/firebase-app'; -import { DatabaseService } from '../../../src/database/database'; +import { DatabaseService } from '../../../src/database/database-internal'; import { Database } from '@firebase/database'; import * as utils from '../utils'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; diff --git a/test/unit/utils.ts b/test/unit/utils.ts index fa655dd4e8..a3f0fd1719 100644 --- a/test/unit/utils.ts +++ b/test/unit/utils.ts @@ -20,7 +20,8 @@ import * as sinon from 'sinon'; import * as mocks from '../resources/mocks'; import { FirebaseNamespace } from '../../src/firebase-namespace'; -import { FirebaseApp, FirebaseAppOptions, FirebaseAppInternals, FirebaseAccessToken } from '../../src/firebase-app'; +import { FirebaseApp } from '../../src/firebase-app'; +import { FirebaseAppOptions, FirebaseAppInternals, FirebaseAccessToken } from '../../src/firebase-app-internal'; import { HttpError, HttpResponse } from '../../src/utils/api-request'; /** From 88772e3af705ef6518e075446df3fc95f2602f52 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 13:12:40 -0400 Subject: [PATCH 03/46] Add back database.d.ts --- src/database.d.ts | 1664 +++++++++++++++++++++++++++++++++++++++++++++ src/index.d.ts | 18 +- 2 files changed, 1673 insertions(+), 9 deletions(-) create mode 100644 src/database.d.ts diff --git a/src/database.d.ts b/src/database.d.ts new file mode 100644 index 0000000000..ee9cec8057 --- /dev/null +++ b/src/database.d.ts @@ -0,0 +1,1664 @@ +import * as _admin from './index.d'; + +/* eslint-disable @typescript-eslint/ban-types */ + +export namespace admin.database { + + /** + * The Firebase Realtime Database service interface. + * + * Do not call this constructor directly. Instead, use + * [`admin.database()`](admin.database#database). + * + * See + * {@link + * https://firebase.google.com/docs/database/admin/start/ + * Introduction to the Admin Database API} + * for a full guide on how to use the Firebase Realtime Database service. + */ + interface Database { + app: _admin.app.App; + + /** + * Disconnects from the server (all Database operations will be completed + * offline). + * + * The client automatically maintains a persistent connection to the Database + * server, which will remain active indefinitely and reconnect when + * disconnected. However, the `goOffline()` and `goOnline()` methods may be used + * to control the client connection in cases where a persistent connection is + * undesirable. + * + * While offline, the client will no longer receive data updates from the + * Database. However, all Database operations performed locally will continue to + * immediately fire events, allowing your application to continue behaving + * normally. Additionally, each operation performed locally will automatically + * be queued and retried upon reconnection to the Database server. + * + * To reconnect to the Database and begin receiving remote events, see + * `goOnline()`. + * + * @example + * ```javascript + * admin.database().goOffline(); + * ``` + */ + goOffline(): void; + + /** + * Reconnects to the server and synchronizes the offline Database state + * with the server state. + * + * This method should be used after disabling the active connection with + * `goOffline()`. Once reconnected, the client will transmit the proper data + * and fire the appropriate events so that your client "catches up" + * automatically. + * + * @example + * ```javascript + * admin.database().goOnline(); + * ``` + */ + goOnline(): void; + + /** + * Returns a `Reference` representing the location in the Database + * corresponding to the provided path. Also can be invoked with an existing + * `Reference` as the argument. In that case returns a new `Reference` + * pointing to the same location. If no path argument is + * provided, returns a `Reference` that represents the root of the Database. + * + * @example + * ```javascript + * // Get a reference to the root of the Database + * var rootRef = admin.database.ref(); + * ``` + * + * @example + * ```javascript + * // Get a reference to the /users/ada node + * var adaRef = admin.database().ref("users/ada"); + * // The above is shorthand for the following operations: + * //var rootRef = admin.database().ref(); + * //var adaRef = rootRef.child("users/ada"); + * ``` + * + * @example + * ```javascript + * var adaRef = admin.database().ref("users/ada"); + * // Get a new reference pointing to the same location. + * var anotherAdaRef = admin.database().ref(adaRef); + * ``` + * + * + * @param path Optional path representing + * the location the returned `Reference` will point. Alternatively, a + * `Reference` object to copy. If not provided, the returned `Reference` will + * point to the root of the Database. + * @return If a path is provided, a `Reference` + * pointing to the provided path. Otherwise, a `Reference` pointing to the + * root of the Database. + */ + ref(path?: string | admin.database.Reference): admin.database.Reference; + + /** + * Returns a `Reference` representing the location in the Database + * corresponding to the provided Firebase URL. + * + * An exception is thrown if the URL is not a valid Firebase Database URL or it + * has a different domain than the current `Database` instance. + * + * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored + * and are not applied to the returned `Reference`. + * + * @example + * ```javascript + * // Get a reference to the root of the Database + * var rootRef = admin.database().ref("https://.firebaseio.com"); + * ``` + * + * @example + * ```javascript + * // Get a reference to the /users/ada node + * var adaRef = admin.database().ref("https://.firebaseio.com/users/ada"); + * ``` + * + * @param url The Firebase URL at which the returned `Reference` will + * point. + * @return A `Reference` pointing to the provided Firebase URL. + */ + refFromURL(url: string): admin.database.Reference; + + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @return A promise fulfilled with the rules as a raw string. + */ + getRules(): Promise; + + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @return A promise fulfilled with the parsed rules object. + */ + getRulesJSON(): Promise; + + /** + * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param source Source of the rules to apply. Must not be `null` or empty. + * @return Resolves when the rules are set on the Realtime Database. + */ + setRules(source: string | Buffer | object): Promise; + } + + /** + * A `DataSnapshot` contains data from a Database location. + * + * Any time you read data from the Database, you receive the data as a + * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach + * with `on()` or `once()`. You can extract the contents of the snapshot as a + * JavaScript object by calling the `val()` method. Alternatively, you can + * traverse into the snapshot by calling `child()` to return child snapshots + * (which you could then call `val()` on). + * + * A `DataSnapshot` is an efficiently generated, immutable copy of the data at + * a Database location. It cannot be modified and will never change (to modify + * data, you always call the `set()` method on a `Reference` directly). + */ + interface DataSnapshot { + key: string | null; + ref: admin.database.Reference; + + /** + * Gets another `DataSnapshot` for the location at the specified relative path. + * + * Passing a relative path to the `child()` method of a DataSnapshot returns + * another `DataSnapshot` for the location at the specified relative path. The + * relative path can either be a simple child name (for example, "ada") or a + * deeper, slash-separated path (for example, "ada/name/first"). If the child + * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot` + * whose value is `null`) is returned. + * + * @example + * ```javascript + * // Assume we have the following data in the Database: + * { + * "name": { + * "first": "Ada", + * "last": "Lovelace" + * } + * } + * + * // Test for the existence of certain keys within a DataSnapshot + * var ref = admin.database().ref("users/ada"); + * ref.once("value") + * .then(function(snapshot) { + * var name = snapshot.child("name").val(); // {first:"Ada",last:"Lovelace"} + * var firstName = snapshot.child("name/first").val(); // "Ada" + * var lastName = snapshot.child("name").child("last").val(); // "Lovelace" + * var age = snapshot.child("age").val(); // null + * }); + * ``` + * + * @param path A relative path to the location of child data. + * @return `DataSnapshot` for the location at the specified relative path. + */ + child(path: string): admin.database.DataSnapshot; + + /** + * Returns true if this `DataSnapshot` contains any data. It is slightly more + * efficient than using `snapshot.val() !== null`. + * + * @example + * ```javascript + * // Assume we have the following data in the Database: + * { + * "name": { + * "first": "Ada", + * "last": "Lovelace" + * } + * } + * + * // Test for the existence of certain keys within a DataSnapshot + * var ref = admin.database().ref("users/ada"); + * ref.once("value") + * .then(function(snapshot) { + * var a = snapshot.exists(); // true + * var b = snapshot.child("name").exists(); // true + * var c = snapshot.child("name/first").exists(); // true + * var d = snapshot.child("name/middle").exists(); // false + * }); + * ``` + * + * @return Whether this `DataSnapshot` contains any data. + */ + exists(): boolean; + + /** + * Exports the entire contents of the DataSnapshot as a JavaScript object. + * + * The `exportVal()` method is similar to `val()`, except priority information + * is included (if available), making it suitable for backing up your data. + * + * @return The DataSnapshot's contents as a JavaScript value (Object, + * Array, string, number, boolean, or `null`). + */ + exportVal(): any; + + /** + * Enumerates the top-level children in the `DataSnapshot`. + * + * Because of the way JavaScript objects work, the ordering of data in the + * JavaScript object returned by `val()` is not guaranteed to match the ordering + * on the server nor the ordering of `child_added` events. That is where + * `forEach()` comes in handy. It guarantees the children of a `DataSnapshot` + * will be iterated in their query order. + * + * If no explicit `orderBy*()` method is used, results are returned + * ordered by key (unless priorities are used, in which case, results are + * returned by priority). + * + * @example + * ```javascript + * + * // Assume we have the following data in the Database: + * { + * "users": { + * "ada": { + * "first": "Ada", + * "last": "Lovelace" + * }, + * "alan": { + * "first": "Alan", + * "last": "Turing" + * } + * } + * } + * + * // Loop through users in order with the forEach() method. The callback + * // provided to forEach() will be called synchronously with a DataSnapshot + * // for each child: + * var query = admin.database().ref("users").orderByKey(); + * query.once("value") + * .then(function(snapshot) { + * snapshot.forEach(function(childSnapshot) { + * // key will be "ada" the first time and "alan" the second time + * var key = childSnapshot.key; + * // childData will be the actual contents of the child + * var childData = childSnapshot.val(); + * }); + * }); + * ``` + * + * @example + * ```javascript + * // You can cancel the enumeration at any point by having your callback + * // function return true. For example, the following code sample will only + * // fire the callback function one time: + * var query = admin.database().ref("users").orderByKey(); + * query.once("value") + * .then(function(snapshot) { + * snapshot.forEach(function(childSnapshot) { + * var key = childSnapshot.key; // "ada" + * + * // Cancel enumeration + * return true; + * }); + * }); + * ``` + * + * @param action A function + * that will be called for each child `DataSnapshot`. The callback can return + * true to cancel further enumeration. + * @return True if enumeration was canceled due to your callback + * returning true. + */ + forEach(action: (a: admin.database.DataSnapshot) => boolean | void): boolean; + + /** + * Gets the priority value of the data in this `DataSnapshot`. + * + * Applications need not use priority but can order collections by + * ordinary properties (see + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data + * Sorting and filtering data}). + * + * @return The the priority value of the data in this `DataSnapshot`. + */ + getPriority(): string | number | null; + + /** + * Returns true if the specified child path has (non-null) data. + * + * @example + * ```javascript + * // Assume we have the following data in the Database: + * { + * "name": { + * "first": "Ada", + * "last": "Lovelace" + * } + * } + * + * // Determine which child keys in DataSnapshot have data. + * var ref = admin.database().ref("users/ada"); + * ref.once("value") + * .then(function(snapshot) { + * var hasName = snapshot.hasChild("name"); // true + * var hasAge = snapshot.hasChild("age"); // false + * }); + * ``` + * + * @param path A relative path to the location of a potential child. + * @return `true` if data exists at the specified child path; else + * `false`. + */ + hasChild(path: string): boolean; + + /** + * Returns whether or not the `DataSnapshot` has any non-`null` child + * properties. + * + * You can use `hasChildren()` to determine if a `DataSnapshot` has any + * children. If it does, you can enumerate them using `forEach()`. If it + * doesn't, then either this snapshot contains a primitive value (which can be + * retrieved with `val()`) or it is empty (in which case, `val()` will return + * `null`). + * + * @example + * ```javascript + * // Assume we have the following data in the Database: + * { + * "name": { + * "first": "Ada", + * "last": "Lovelace" + * } + * } + * + * var ref = admin.database().ref("users/ada"); + * ref.once("value") + * .then(function(snapshot) { + * var a = snapshot.hasChildren(); // true + * var b = snapshot.child("name").hasChildren(); // true + * var c = snapshot.child("name/first").hasChildren(); // false + * }); + * ``` + * + * @return True if this snapshot has any children; else false. + */ + hasChildren(): boolean; + + /** + * Returns the number of child properties of this `DataSnapshot`. + * + * @example + * ```javascript + * // Assume we have the following data in the Database: + * { + * "name": { + * "first": "Ada", + * "last": "Lovelace" + * } + * } + * + * var ref = admin.database().ref("users/ada"); + * ref.once("value") + * .then(function(snapshot) { + * var a = snapshot.numChildren(); // 1 ("name") + * var b = snapshot.child("name").numChildren(); // 2 ("first", "last") + * var c = snapshot.child("name/first").numChildren(); // 0 + * }); + * ``` + * + * @return The number of child properties of this `DataSnapshot`. + */ + numChildren(): number; + + /** + * @return A JSON-serializable representation of this object. + */ + toJSON(): Object | null; + + /** + * Extracts a JavaScript value from a `DataSnapshot`. + * + * Depending on the data in a `DataSnapshot`, the `val()` method may return a + * scalar type (string, number, or boolean), an array, or an object. It may also + * return null, indicating that the `DataSnapshot` is empty (contains no data). + * + * @example + * ```javascript + * // Write and then read back a string from the Database. + * ref.set("hello") + * .then(function() { + * return ref.once("value"); + * }) + * .then(function(snapshot) { + * var data = snapshot.val(); // data === "hello" + * }); + * ``` + * + * @example + * ```javascript + * // Write and then read back a JavaScript object from the Database. + * ref.set({ name: "Ada", age: 36 }) + * .then(function() { + * return ref.once("value"); + * }) + * .then(function(snapshot) { + * var data = snapshot.val(); + * // data is { "name": "Ada", "age": 36 } + * // data.name === "Ada" + * // data.age === 36 + * }); + * ``` + * + * @return The DataSnapshot's contents as a JavaScript value (Object, + * Array, string, number, boolean, or `null`). + */ + val(): any; + } + + /** + * The `onDisconnect` class allows you to write or clear data when your client + * disconnects from the Database server. These updates occur whether your + * client disconnects cleanly or not, so you can rely on them to clean up data + * even if a connection is dropped or a client crashes. + * + * The `onDisconnect` class is most commonly used to manage presence in + * applications where it is useful to detect how many clients are connected and + * when other clients disconnect. See + * {@link + * https://firebase.google.com/docs/database/web/offline-capabilities + * Enabling Offline Capabilities in JavaScript} for more information. + * + * To avoid problems when a connection is dropped before the requests can be + * transferred to the Database server, these functions should be called before + * any data is written. + * + * Note that `onDisconnect` operations are only triggered once. If you want an + * operation to occur each time a disconnect occurs, you'll need to re-establish + * the `onDisconnect` operations each time you reconnect. + */ + interface OnDisconnect { + + /** + * Cancels all previously queued `onDisconnect()` set or update events for this + * location and all children. + * + * If a write has been queued for this location via a `set()` or `update()` at a + * parent location, the write at this location will be canceled, though all + * other siblings will still be written. + * + * @example + * ```javascript + * var ref = admin.database().ref("onlineState"); + * ref.onDisconnect().set(false); + * // ... sometime later + * ref.onDisconnect().cancel(); + * ``` + * + * @param onComplete An optional callback function that is + * called when synchronization to the server has completed. The callback + * will be passed a single parameter: null for success, or an Error object + * indicating a failure. + * @return Resolves when synchronization to the server is complete. + */ + cancel(onComplete?: (a: Error | null) => any): Promise; + + /** + * Ensures the data at this location is deleted when the client is disconnected + * (due to closing the browser, navigating to a new page, or network issues). + * + * @param onComplete An optional callback function that is + * called when synchronization to the server has completed. The callback + * will be passed a single parameter: null for success, or an Error object + * indicating a failure. + * @return Resolves when synchronization to the server is complete. + */ + remove(onComplete?: (a: Error | null) => any): Promise; + + /** + * Ensures the data at this location is set to the specified value when the + * client is disconnected (due to closing the browser, navigating to a new page, + * or network issues). + * + * `set()` is especially useful for implementing "presence" systems, where a + * value should be changed or cleared when a user disconnects so that they + * appear "offline" to other users. See + * {@link + * https://firebase.google.com/docs/database/web/offline-capabilities + * Enabling Offline Capabilities in JavaScript} for more information. + * + * Note that `onDisconnect` operations are only triggered once. If you want an + * operation to occur each time a disconnect occurs, you'll need to re-establish + * the `onDisconnect` operations each time. + * + * @example + * ```javascript + * var ref = admin.database().ref("users/ada/status"); + * ref.onDisconnect().set("I disconnected!"); + * ``` + * + * @param value The value to be written to this location on + * disconnect (can be an object, array, string, number, boolean, or null). + * @param onComplete An optional callback function that + * will be called when synchronization to the database server has completed. + * The callback will be passed a single parameter: null for success, or an + * `Error` object indicating a failure. + * @return A promise that resolves when synchronization to the database is complete. + */ + set(value: any, onComplete?: (a: Error | null) => any): Promise; + + /** + * Ensures the data at this location is set to the specified value and priority + * when the client is disconnected (due to closing the browser, navigating to a + * new page, or network issues). + * + * @param value The value to be written to this location on + * disconnect (can be an object, array, string, number, boolean, or null). + * @param priority + * @param onComplete An optional callback function that is + * called when synchronization to the server has completed. The callback + * will be passed a single parameter: null for success, or an Error object + * indicating a failure. + * @return A promise that resolves when synchronization to the database is complete. + */ + setWithPriority( + value: any, + priority: number | string | null, + onComplete?: (a: Error | null) => any + ): Promise; + + /** + * Writes multiple values at this location when the client is disconnected (due + * to closing the browser, navigating to a new page, or network issues). + * + * The `values` argument contains multiple property-value pairs that will be + * written to the Database together. Each child property can either be a simple + * property (for example, "name") or a relative path (for example, "name/first") + * from the current location to the data to update. + * + * As opposed to the `set()` method, `update()` can be use to selectively update + * only the referenced properties at the current location (instead of replacing + * all the child properties at the current location). + * + * See {@link https://firebase.google.com/docs/reference/admin/node/admin.database.Reference#update} + * for examples of using the connected version of `update`. + * + * @example + * ```javascript + * var ref = admin.database().ref("users/ada"); + * ref.update({ + * onlineState: true, + * status: "I'm online." + * }); + * ref.onDisconnect().update({ + * onlineState: false, + * status: "I'm offline." + * }); + * ``` + * + * @param values Object containing multiple values. + * @param onComplete An optional callback function that will + * be called when synchronization to the server has completed. The + * callback will be passed a single parameter: null for success, or an Error + * object indicating a failure. + * @return Resolves when synchronization to the + * Database is complete. + */ + update(values: Object, onComplete?: (a: Error | null) => any): Promise; + } + + type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; + + /** + * A `Query` sorts and filters the data at a Database location so only a subset + * of the child data is included. This can be used to order a collection of + * data by some attribute (for example, height of dinosaurs) as well as to + * restrict a large list of items (for example, chat messages) down to a number + * suitable for synchronizing to the client. Queries are created by chaining + * together one or more of the filter methods defined here. + * + * Just as with a `Reference`, you can receive data from a `Query` by using the + * `on()` method. You will only receive events and `DataSnapshot`s for the + * subset of the data that matches your query. + * + * See + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data + * Sorting and filtering data} for more information. + */ + interface Query { + ref: admin.database.Reference; + + /** + * Creates a `Query` with the specified ending point. + * + * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary + * starting and ending points for your queries. + * + * The ending point is inclusive, so children with exactly the specified value + * will be included in the query. The optional key argument can be used to + * further limit the range of the query. If it is specified, then children that + * have exactly the specified value must also have a key name less than or equal + * to the specified key. + * + * You can read more about `endAt()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data + * Filtering data}. + * + * @example + * ```javascript + * // Find all dinosaurs whose names come before Pterodactyl lexicographically. + * var ref = admin.database().ref("dinosaurs"); + * ref.orderByKey().endAt("pterodactyl").on("child_added", function(snapshot) { + * console.log(snapshot.key); + * }); + * ``` + * + * @param value The value to end at. The argument + * type depends on which `orderBy*()` function was used in this query. + * Specify a value that matches the `orderBy*()` type. When used in + * combination with `orderByKey()`, the value must be a string. + * @param key The child key to end at, among the children with the + * previously specified priority. This argument is only allowed if ordering by + * priority. + * @return A new `Query` object. + */ + endAt(value: number | string | boolean | null, key?: string): admin.database.Query; + + /** + * Creates a `Query` that includes children that match the specified value. + * + * Using `startAt()`, `endAt()`, and `equalTo()` allows us to choose arbitrary + * starting and ending points for our queries. + * + * The optional key argument can be used to further limit the range of the + * query. If it is specified, then children that have exactly the specified + * value must also have exactly the specified key as their key name. This can be + * used to filter result sets with many matches for the same value. + * + * You can read more about `equalTo()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data + * Filtering data}. + * + * @example + * // Find all dinosaurs whose height is exactly 25 meters. + * var ref = admin.database().ref("dinosaurs"); + * ref.orderByChild("height").equalTo(25).on("child_added", function(snapshot) { + * console.log(snapshot.key); + * }); + * + * @param value The value to match for. The + * argument type depends on which `orderBy*()` function was used in this + * query. Specify a value that matches the `orderBy*()` type. When used in + * combination with `orderByKey()`, the value must be a string. + * @param key The child key to start at, among the children with the + * previously specified priority. This argument is only allowed if ordering by + * priority. + * @return A new `Query` object. + */ + equalTo(value: number | string | boolean | null, key?: string): admin.database.Query; + + /** + * Returns whether or not the current and provided queries represent the same + * location, have the same query parameters, and are from the same instance of + * `admin.app.App`. + * + * Two `Reference` objects are equivalent if they represent the same location + * and are from the same instance of `admin.app.App`. + * + * Two `Query` objects are equivalent if they represent the same location, have + * the same query parameters, and are from the same instance of `admin.app.App`. + * Equivalent queries share the same sort order, limits, and starting and + * ending points. + * + * @example + * ```javascript + * var rootRef = admin.database().ref(); + * var usersRef = rootRef.child("users"); + * + * usersRef.isEqual(rootRef); // false + * usersRef.isEqual(rootRef.child("users")); // true + * usersRef.parent.isEqual(rootRef); // true + * ``` + * + * @example + * ```javascript + * var rootRef = admin.database().ref(); + * var usersRef = rootRef.child("users"); + * var usersQuery = usersRef.limitToLast(10); + * + * usersQuery.isEqual(usersRef); // false + * usersQuery.isEqual(usersRef.limitToLast(10)); // true + * usersQuery.isEqual(rootRef.limitToLast(10)); // false + * usersQuery.isEqual(usersRef.orderByKey().limitToLast(10)); // false + * ``` + * + * @param other The query to compare against. + * @return Whether or not the current and provided queries are + * equivalent. + */ + isEqual(other: admin.database.Query | null): boolean; + + /** + * Generates a new `Query` limited to the first specific number of children. + * + * The `limitToFirst()` method is used to set a maximum number of children to be + * synced for a given callback. If we set a limit of 100, we will initially only + * receive up to 100 `child_added` events. If we have fewer than 100 messages + * stored in our Database, a `child_added` event will fire for each message. + * However, if we have over 100 messages, we will only receive a `child_added` + * event for the first 100 ordered messages. As items change, we will receive + * `child_removed` events for each item that drops out of the active list so + * that the total number stays at 100. + * + * You can read more about `limitToFirst()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data + * Filtering data}. + * + * @example + * ```javascript + * // Find the two shortest dinosaurs. + * var ref = admin.database().ref("dinosaurs"); + * ref.orderByChild("height").limitToFirst(2).on("child_added", function(snapshot) { + * // This will be called exactly two times (unless there are less than two + * // dinosaurs in the Database). + * + * // It will also get fired again if one of the first two dinosaurs is + * // removed from the data set, as a new dinosaur will now be the second + * // shortest. + * console.log(snapshot.key); + * }); + * ``` + * + * @param limit The maximum number of nodes to include in this query. + * @return A `Query` object. + */ + limitToFirst(limit: number): admin.database.Query; + + /** + * Generates a new `Query` object limited to the last specific number of + * children. + * + * The `limitToLast()` method is used to set a maximum number of children to be + * synced for a given callback. If we set a limit of 100, we will initially only + * receive up to 100 `child_added` events. If we have fewer than 100 messages + * stored in our Database, a `child_added` event will fire for each message. + * However, if we have over 100 messages, we will only receive a `child_added` + * event for the last 100 ordered messages. As items change, we will receive + * `child_removed` events for each item that drops out of the active list so + * that the total number stays at 100. + * + * You can read more about `limitToLast()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data + * Filtering data}. + * + * @example + * ```javascript + * // Find the two heaviest dinosaurs. + * var ref = admin.database().ref("dinosaurs"); + * ref.orderByChild("weight").limitToLast(2).on("child_added", function(snapshot) { + * // This callback will be triggered exactly two times, unless there are + * // fewer than two dinosaurs stored in the Database. It will also get fired + * // for every new, heavier dinosaur that gets added to the data set. + * console.log(snapshot.key); + * }); + * ``` + * + * @param limit The maximum number of nodes to include in this query. + * @return A `Query` object. + */ + limitToLast(limit: number): admin.database.Query; + + /** + * Detaches a callback previously attached with `on()`. + * + * Detach a callback previously attached with `on()`. Note that if `on()` was + * called multiple times with the same eventType and callback, the callback + * will be called multiple times for each event, and `off()` must be called + * multiple times to remove the callback. Calling `off()` on a parent listener + * will not automatically remove listeners registered on child nodes, `off()` + * must also be called on any child listeners to remove the callback. + * + * If a callback is not specified, all callbacks for the specified eventType + * will be removed. Similarly, if no eventType or callback is specified, all + * callbacks for the `Reference` will be removed. + * + * @example + * ```javascript + * var onValueChange = function(dataSnapshot) { ... }; + * ref.on('value', onValueChange); + * ref.child('meta-data').on('child_added', onChildAdded); + * // Sometime later... + * ref.off('value', onValueChange); + * + * // You must also call off() for any child listeners on ref + * // to cancel those callbacks + * ref.child('meta-data').off('child_added', onValueAdded); + * ``` + * + * @example + * ```javascript + * // Or you can save a line of code by using an inline function + * // and on()'s return value. + * var onValueChange = ref.on('value', function(dataSnapshot) { ... }); + * // Sometime later... + * ref.off('value', onValueChange); + * ``` + * + * @param eventType One of the following strings: "value", + * "child_added", "child_changed", "child_removed", or "child_moved." + * @param callback The callback function that was passed to `on()`. + * @param context The context that was passed to `on()`. + */ + off( + eventType?: admin.database.EventType, + callback?: (a: admin.database.DataSnapshot, b?: string | null) => any, + context?: Object | null + ): void; + + /** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Use `off( )` to stop receiving updates. See + * {@link https://firebase.google.com/docs/database/web/retrieve-data + * Retrieve Data on the Web} + * for more details. + * + *

value event

+ * + * This event will trigger once with the initial data stored at this location, + * and then trigger again each time the data changes. The `DataSnapshot` passed + * to the callback will be for the location at which `on()` was called. It + * won't trigger until the entire contents has been synchronized. If the + * location has no data, it will be triggered with an empty `DataSnapshot` + * (`val()` will return `null`). + * + *

child_added event

+ * + * This event will be triggered once for each initial child at this location, + * and it will be triggered again every time a new child is added. The + * `DataSnapshot` passed into the callback will reflect the data for the + * relevant child. For ordering purposes, it is passed a second argument which + * is a string containing the key of the previous sibling child by sort order + * (or `null` if it is the first child). + * + *

child_removed event

+ * + * This event will be triggered once every time a child is removed. The + * `DataSnapshot` passed into the callback will be the old data for the child + * that was removed. A child will get removed when either: + * + * - a client explicitly calls `remove()` on that child or one of its ancestors + * - a client calls `set(null)` on that child or one of its ancestors + * - that child has all of its children removed + * - there is a query in effect which now filters out the child (because it's + * sort order changed or the max limit was hit) + * + *

child_changed event

+ * + * This event will be triggered when the data stored in a child (or any of its + * descendants) changes. Note that a single `child_changed` event may represent + * multiple changes to the child. The `DataSnapshot` passed to the callback will + * contain the new child contents. For ordering purposes, the callback is also + * passed a second argument which is a string containing the key of the previous + * sibling child by sort order (or `null` if it is the first child). + * + *

child_moved event

+ * + * This event will be triggered when a child's sort order changes such that its + * position relative to its siblings changes. The `DataSnapshot` passed to the + * callback will be for the data of the child that has moved. It is also passed + * a second argument which is a string containing the key of the previous + * sibling child by sort order (or `null` if it is the first child). + * + * @example + * ```javascript + * // Handle a new value. + * ref.on('value', function(dataSnapshot) { + * ... + * }); + * ``` + * + * @example + * ```javascript + * // Handle a new child. + * ref.on('child_added', function(childSnapshot, prevChildKey) { + * ... + * }); + * ``` + * + * @example + * ```javascript + * // Handle child removal. + * ref.on('child_removed', function(oldChildSnapshot) { + * ... + * }); + * ``` + * + * @example + * ```javascript + * // Handle child data changes. + * ref.on('child_changed', function(childSnapshot, prevChildKey) { + * ... + * }); + * ``` + * + * @example + * ```javascript + * // Handle child ordering changes. + * ref.on('child_moved', function(childSnapshot, prevChildKey) { + * ... + * }); + * ``` + * + * @param eventType One of the following strings: "value", + * "child_added", "child_changed", "child_removed", or "child_moved." + * @param callback A callback that fires when the specified event occurs. The callback is + * passed a DataSnapshot. For ordering purposes, "child_added", + * "child_changed", and "child_moved" will also be passed a string containing + * the key of the previous child, by sort order (or `null` if it is the + * first child). + * @param cancelCallbackOrContext An optional + * callback that will be notified if your event subscription is ever canceled + * because your client does not have permission to read this data (or it had + * permission but has now lost it). This callback will be passed an `Error` + * object indicating why the failure occurred. + * @param context If provided, this object will be used as `this` + * when calling your callback(s). + * @return The provided + * callback function is returned unmodified. This is just for convenience if + * you want to pass an inline function to `on()`, but store the callback + * function for later passing to `off()`. + */ + on( + eventType: admin.database.EventType, + callback: (a: admin.database.DataSnapshot, b?: string | null) => any, + cancelCallbackOrContext?: ((a: Error) => any) | Object | null, + context?: Object | null + ): (a: admin.database.DataSnapshot | null, b?: string) => any; + + /** + * Listens for exactly one event of the specified event type, and then stops + * listening. + * + * This is equivalent to calling `on()`, and then calling `off()` inside the + * callback function. See `on()` for details on the event types. + * + * @example + * ```javascript + * // Basic usage of .once() to read the data located at ref. + * ref.once('value') + * .then(function(dataSnapshot) { + * // handle read data. + * }); + * ``` + * + * @param eventType One of the following strings: "value", + * "child_added", "child_changed", "child_removed", or "child_moved." + * @param successCallback A callback that fires when the specified event occurs. The callback is + * passed a `DataSnapshot`. For ordering purposes, "child_added", + * "child_changed", and "child_moved" will also be passed a string containing + * the key of the previous child by sort order (or `null` if it is the + * first child). + * @param failureCallbackOrContext An optional + * callback that will be notified if your client does not have permission to + * read the data. This callback will be passed an `Error` object indicating + * why the failure occurred. + * @param context If provided, this object will be used as `this` + * when calling your callback(s). + * @return {!Promise} + */ + once( + eventType: admin.database.EventType, + successCallback?: (a: admin.database.DataSnapshot, b?: string | null ) => any, + failureCallbackOrContext?: ((a: Error) => void) | Object | null, + context?: Object | null + ): Promise; + + /** + * Generates a new `Query` object ordered by the specified child key. + * + * Queries can only order by one key at a time. Calling `orderByChild()` + * multiple times on the same query is an error. + * + * Firebase queries allow you to order your data by any child key on the fly. + * However, if you know in advance what your indexes will be, you can define + * them via the .indexOn rule in your Security Rules for better performance. See + * the {@link https://firebase.google.com/docs/database/security/indexing-data + * .indexOn} rule for more information. + * + * You can read more about `orderByChild()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sort_data + * Sort data}. + * + * @example + * ```javascript + * var ref = admin.database().ref("dinosaurs"); + * ref.orderByChild("height").on("child_added", function(snapshot) { + * console.log(snapshot.key + " was " + snapshot.val().height + " m tall"); + * }); + * ``` + * + * @param path + * @return A new `Query` object. + */ + orderByChild(path: string): admin.database.Query; + + /** + * Generates a new `Query` object ordered by key. + * + * Sorts the results of a query by their (ascending) key values. + * + * You can read more about `orderByKey()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sort_data + * Sort data}. + * + * @example + * ```javascript + * var ref = admin.database().ref("dinosaurs"); + * ref.orderByKey().on("child_added", function(snapshot) { + * console.log(snapshot.key); + * }); + * ``` + * + * @return A new `Query` object. + */ + orderByKey(): admin.database.Query; + + /** + * Generates a new `Query` object ordered by priority. + * + * Applications need not use priority but can order collections by + * ordinary properties (see + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sort_data + * Sort data} for alternatives to priority. + * + * @return A new `Query` object. + */ + orderByPriority(): admin.database.Query; + + /** + * Generates a new `Query` object ordered by value. + * + * If the children of a query are all scalar values (string, number, or + * boolean), you can order the results by their (ascending) values. + * + * You can read more about `orderByValue()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sort_data + * Sort data}. + * + * @example + * ```javascript + * var scoresRef = admin.database().ref("scores"); + * scoresRef.orderByValue().limitToLast(3).on("value", function(snapshot) { + * snapshot.forEach(function(data) { + * console.log("The " + data.key + " score is " + data.val()); + * }); + * }); + * ``` + * + * @return A new `Query` object. + */ + orderByValue(): admin.database.Query; + + /** + * Creates a `Query` with the specified starting point. + * + * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary + * starting and ending points for your queries. + * + * The starting point is inclusive, so children with exactly the specified value + * will be included in the query. The optional key argument can be used to + * further limit the range of the query. If it is specified, then children that + * have exactly the specified value must also have a key name greater than or + * equal to the specified key. + * + * You can read more about `startAt()` in + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data + * Filtering data}. + * + * @example + * ```javascript + * // Find all dinosaurs that are at least three meters tall. + * var ref = admin.database().ref("dinosaurs"); + * ref.orderByChild("height").startAt(3).on("child_added", function(snapshot) { + * console.log(snapshot.key) + * }); + * ``` + * + * @param value The value to start at. The argument + * type depends on which `orderBy*()` function was used in this query. + * Specify a value that matches the `orderBy*()` type. When used in + * combination with `orderByKey()`, the value must be a string. + * @param key The child key to start at. This argument is allowed if + * ordering by child, value, or priority. + * @return A new `Query` object. + */ + startAt(value: number | string | boolean | null, key?: string): admin.database.Query; + + /** + * @return A JSON-serializable representation of this object. + */ + toJSON(): Object; + + /** + * Gets the absolute URL for this location. + * + * The `toString()` method returns a URL that is ready to be put into a browser, + * curl command, or a `admin.database().refFromURL()` call. Since all of those + * expect the URL to be url-encoded, `toString()` returns an encoded URL. + * + * Append '.json' to the returned URL when typed into a browser to download + * JSON-formatted data. If the location is secured (that is, not publicly + * readable), you will get a permission-denied error. + * + * @example + * ```javascript + * // Calling toString() on a root Firebase reference returns the URL where its + * // data is stored within the Database: + * var rootRef = admin.database().ref(); + * var rootUrl = rootRef.toString(); + * // rootUrl === "https://sample-app.firebaseio.com/". + * + * // Calling toString() at a deeper Firebase reference returns the URL of that + * // deep path within the Database: + * var adaRef = rootRef.child('users/ada'); + * var adaURL = adaRef.toString(); + * // adaURL === "https://sample-app.firebaseio.com/users/ada". + * ``` + * + * @return The absolute URL for this location. + * @override + */ + toString(): string; + } + + /** + * A `Reference` represents a specific location in your Database and can be used + * for reading or writing data to that Database location. + * + * You can reference the root or child location in your Database by calling + * `admin.database().ref()` or `admin.database().ref("child/path")`. + * + * Writing is done with the `set()` method and reading can be done with the + * `on()` method. See + * {@link + * https://firebase.google.com/docs/database/web/read-and-write + * Read and Write Data on the Web} + */ + interface Reference extends admin.database.Query { + + /** + * The last part of the `Reference`'s path. + * + * For example, `"ada"` is the key for + * `https://.firebaseio.com/users/ada`. + * + * The key of a root `Reference` is `null`. + * + * @example + * ```javascript + * // The key of a root reference is null + * var rootRef = admin.database().ref(); + * var key = rootRef.key; // key === null + * ``` + * + * @example + * ```javascript + * // The key of any non-root reference is the last token in the path + * var adaRef = admin.database().ref("users/ada"); + * var key = adaRef.key; // key === "ada" + * key = adaRef.child("name/last").key; // key === "last" + * ``` + */ + key: string | null; + + /** + * The parent location of a `Reference`. + * + * The parent of a root `Reference` is `null`. + * + * @example + * ```javascript + * // The parent of a root reference is null + * var rootRef = admin.database().ref(); + * parent = rootRef.parent; // parent === null + * ``` + * + * @example + * ```javascript + * // The parent of any non-root reference is the parent location + * var usersRef = admin.database().ref("users"); + * var adaRef = admin.database().ref("users/ada"); + * // usersRef and adaRef.parent represent the same location + * ``` + */ + parent: admin.database.Reference | null; + + /** + * The root `Reference` of the Database. + * + * @example + * ```javascript + * // The root of a root reference is itself + * var rootRef = admin.database().ref(); + * // rootRef and rootRef.root represent the same location + * ``` + * + * @example + * ```javascript + * // The root of any non-root reference is the root location + * var adaRef = admin.database().ref("users/ada"); + * // rootRef and adaRef.root represent the same location + * ``` + */ + root: admin.database.Reference; + /** @deprecated Removed in next major release to match Web SDK typings. */ + path: string; + + /** + * Gets a `Reference` for the location at the specified relative path. + * + * The relative path can either be a simple child name (for example, "ada") or + * a deeper slash-separated path (for example, "ada/name/first"). + * + * @example + * ```javascript + * var usersRef = admin.database().ref('users'); + * var adaRef = usersRef.child('ada'); + * var adaFirstNameRef = adaRef.child('name/first'); + * var path = adaFirstNameRef.toString(); + * // path is now 'https://sample-app.firebaseio.com/users/ada/name/first' + * ``` + * + * @param path A relative path from this location to the desired child + * location. + * @return The specified child location. + */ + child(path: string): admin.database.Reference; + + /** + * Returns an `OnDisconnect` object - see + * {@link + * https://firebase.google.com/docs/database/web/offline-capabilities + * Enabling Offline Capabilities in JavaScript} for more information on how + * to use it. + * + * @return An `OnDisconnect` object . + */ + onDisconnect(): admin.database.OnDisconnect; + + /** + * Generates a new child location using a unique key and returns its + * `Reference`. + * + * This is the most common pattern for adding data to a collection of items. + * + * If you provide a value to `push()`, the value will be written to the + * generated location. If you don't pass a value, nothing will be written to the + * Database and the child will remain empty (but you can use the `Reference` + * elsewhere). + * + * The unique key generated by `push()` are ordered by the current time, so the + * resulting list of items will be chronologically sorted. The keys are also + * designed to be unguessable (they contain 72 random bits of entropy). + * + * + * See + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data + * Append to a list of data} + *
See + * {@link + * https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html + * The 2^120 Ways to Ensure Unique Identifiers} + * + * @example + * ```javascript + * var messageListRef = admin.database().ref('message_list'); + * var newMessageRef = messageListRef.push(); + * newMessageRef.set({ + * user_id: 'ada', + * text: 'The Analytical Engine weaves algebraical patterns just as the Jacquard loom weaves flowers and leaves.' + * }); + * // We've appended a new message to the message_list location. + * var path = newMessageRef.toString(); + * // path will be something like + * // 'https://sample-app.firebaseio.com/message_list/-IKo28nwJLH0Nc5XeFmj' + * ``` + * + * @param value Optional value to be written at the generated location. + * @param onComplete Callback called when write to server is + * complete. + * @return Combined `Promise` and + * `Reference`; resolves when write is complete, but can be used immediately + * as the `Reference` to the child location. + */ + push(value?: any, onComplete?: (a: Error | null) => any): admin.database.ThenableReference; + + /** + * Removes the data at this Database location. + * + * Any data at child locations will also be deleted. + * + * The effect of the remove will be visible immediately and the corresponding + * event 'value' will be triggered. Synchronization of the remove to the + * Firebase servers will also be started, and the returned Promise will resolve + * when complete. If provided, the onComplete callback will be called + * asynchronously after synchronization has finished. + * + * @example + * ```javascript + * var adaRef = admin.database().ref('users/ada'); + * adaRef.remove() + * .then(function() { + * console.log("Remove succeeded.") + * }) + * .catch(function(error) { + * console.log("Remove failed: " + error.message) + * }); + * ``` + * + * @param onComplete Callback called when write to server is + * complete. + * @return Resolves when remove on server is complete. + */ + remove(onComplete?: (a: Error | null) => any): Promise; + + /** + * Writes data to this Database location. + * + * This will overwrite any data at this location and all child locations. + * + * The effect of the write will be visible immediately, and the corresponding + * events ("value", "child_added", etc.) will be triggered. Synchronization of + * the data to the Firebase servers will also be started, and the returned + * Promise will resolve when complete. If provided, the `onComplete` callback + * will be called asynchronously after synchronization has finished. + * + * Passing `null` for the new value is equivalent to calling `remove()`; namely, + * all data at this location and all child locations will be deleted. + * + * `set()` will remove any priority stored at this location, so if priority is + * meant to be preserved, you need to use `setWithPriority()` instead. + * + * Note that modifying data with `set()` will cancel any pending transactions + * at that location, so extreme care should be taken if mixing `set()` and + * `transaction()` to modify the same data. + * + * A single `set()` will generate a single "value" event at the location where + * the `set()` was performed. + * + * @example + * ```javascript + * var adaNameRef = admin.database().ref('users/ada/name'); + * adaNameRef.child('first').set('Ada'); + * adaNameRef.child('last').set('Lovelace'); + * // We've written 'Ada' to the Database location storing Ada's first name, + * // and 'Lovelace' to the location storing her last name. + * ``` + * + * @example + * ```javascript + * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }); + * // Exact same effect as the previous example, except we've written + * // Ada's first and last name simultaneously. + * ``` + * + * @example + * ```javascript + * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }) + * .then(function() { + * console.log('Synchronization succeeded'); + * }) + * .catch(function(error) { + * console.log('Synchronization failed'); + * }); + * // Same as the previous example, except we will also log a message + * // when the data has finished synchronizing. + * ``` + * + * @param value The value to be written (string, number, boolean, object, + * array, or null). + * @param onComplete Callback called when write to server is + * complete. + * @return Resolves when write to server is complete. + */ + set(value: any, onComplete?: (a: Error | null) => any): Promise; + + /** + * Sets a priority for the data at this Database location. + * + * Applications need not use priority but can order collections by + * ordinary properties (see + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data + * Sorting and filtering data}). + * + * @param priority + * @param onComplete + * @return + */ + setPriority( + priority: string | number | null, + onComplete: (a: Error | null) => any + ): Promise; + + /** + * Writes data the Database location. Like `set()` but also specifies the + * priority for that data. + * + * Applications need not use priority but can order collections by + * ordinary properties (see + * {@link + * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data + * Sorting and filtering data}). + * + * @param newVal + * @param newPriority + * @param onComplete + * @return + */ + setWithPriority( + newVal: any, newPriority: string | number | null, + onComplete?: (a: Error | null) => any + ): Promise; + + /** + * Atomically modifies the data at this location. + * + * Atomically modify the data at this location. Unlike a normal `set()`, which + * just overwrites the data regardless of its previous value, `transaction()` is + * used to modify the existing value to a new value, ensuring there are no + * conflicts with other clients writing to the same location at the same time. + * + * To accomplish this, you pass `transaction()` an update function which is used + * to transform the current value into a new value. If another client writes to + * the location before your new value is successfully written, your update + * function will be called again with the new current value, and the write will + * be retried. This will happen repeatedly until your write succeeds without + * conflict or you abort the transaction by not returning a value from your + * update function. + * + * Note: Modifying data with `set()` will cancel any pending transactions at + * that location, so extreme care should be taken if mixing `set()` and + * `transaction()` to update the same data. + * + * Note: When using transactions with Security and Firebase Rules in place, be + * aware that a client needs `.read` access in addition to `.write` access in + * order to perform a transaction. This is because the client-side nature of + * transactions requires the client to read the data in order to transactionally + * update it. + * + * @example + * ```javascript + * // Increment Ada's rank by 1. + * var adaRankRef = admin.database().ref('users/ada/rank'); + * adaRankRef.transaction(function(currentRank) { + * // If users/ada/rank has never been set, currentRank will be `null`. + * return currentRank + 1; + * }); + * ``` + * + * @example + * ```javascript + * // Try to create a user for ada, but only if the user id 'ada' isn't + * // already taken + * var adaRef = admin.database().ref('users/ada'); + * adaRef.transaction(function(currentData) { + * if (currentData === null) { + * return { name: { first: 'Ada', last: 'Lovelace' } }; + * } else { + * console.log('User ada already exists.'); + * return; // Abort the transaction. + * } + * }, function(error, committed, snapshot) { + * if (error) { + * console.log('Transaction failed abnormally!', error); + * } else if (!committed) { + * console.log('We aborted the transaction (because ada already exists).'); + * } else { + * console.log('User ada added!'); + * } + * console.log("Ada's data: ", snapshot.val()); + * }); + * ``` + * + * @param transactionUpdate A developer-supplied function which + * will be passed the current data stored at this location (as a JavaScript + * object). The function should return the new value it would like written (as + * a JavaScript object). If `undefined` is returned (i.e. you return with no + * arguments) the transaction will be aborted and the data at this location + * will not be modified. + * @param onComplete A callback + * function that will be called when the transaction completes. The callback + * is passed three arguments: a possibly-null `Error`, a `boolean` indicating + * whether the transaction was committed, and a `DataSnapshot` indicating the + * final result. If the transaction failed abnormally, the first argument will + * be an `Error` object indicating the failure cause. If the transaction + * finished normally, but no data was committed because no data was returned + * from `transactionUpdate`, then second argument will be false. If the + * transaction completed and committed data to Firebase, the second argument + * will be true. Regardless, the third argument will be a `DataSnapshot` + * containing the resulting data in this location. + * @param applyLocally By default, events are raised each time the + * transaction update function runs. So if it is run multiple times, you may + * see intermediate states. You can set this to false to suppress these + * intermediate states and instead wait until the transaction has completed + * before events are raised. + * @return Returns a Promise that can optionally be used instead of the `onComplete` + * callback to handle success and failure. + */ + transaction( + transactionUpdate: (a: any) => any, + onComplete?: (a: Error | null, b: boolean, c: admin.database.DataSnapshot | null) => any, + applyLocally?: boolean + ): Promise<{ + committed: boolean; + snapshot: admin.database.DataSnapshot | null; + }>; + + /** + * Writes multiple values to the Database at once. + * + * The `values` argument contains multiple property-value pairs that will be + * written to the Database together. Each child property can either be a simple + * property (for example, "name") or a relative path (for example, + * "name/first") from the current location to the data to update. + * + * As opposed to the `set()` method, `update()` can be use to selectively update + * only the referenced properties at the current location (instead of replacing + * all the child properties at the current location). + * + * The effect of the write will be visible immediately, and the corresponding + * events ('value', 'child_added', etc.) will be triggered. Synchronization of + * the data to the Firebase servers will also be started, and the returned + * Promise will resolve when complete. If provided, the `onComplete` callback + * will be called asynchronously after synchronization has finished. + * + * A single `update()` will generate a single "value" event at the location + * where the `update()` was performed, regardless of how many children were + * modified. + * + * Note that modifying data with `update()` will cancel any pending + * transactions at that location, so extreme care should be taken if mixing + * `update()` and `transaction()` to modify the same data. + * + * Passing `null` to `update()` will remove the data at this location. + * + * See + * {@link + * https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html + * Introducing multi-location updates and more}. + * + * @example + * ```javascript + * var adaNameRef = admin.database().ref('users/ada/name'); + * // Modify the 'first' and 'last' properties, but leave other data at + * // adaNameRef unchanged. + * adaNameRef.update({ first: 'Ada', last: 'Lovelace' }); + * ``` + * + * @param values Object containing multiple values. + * @param onComplete Callback called when write to server is + * complete. + * @return Resolves when update on server is complete. + */ + update(values: Object, onComplete?: (a: Error | null) => any): Promise; + } + + /** + * @extends {Reference} + */ + interface ThenableReference extends admin.database.Reference, Promise { } + + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + function enableLogging(logger?: boolean | ((message: string) => any), persistent?: boolean): any; +} + +/* eslint-disable @typescript-eslint/no-unused-vars */ +export namespace admin.database.ServerValue { + + /** + * A placeholder value for auto-populating the current timestamp (time + * since the Unix epoch, in milliseconds) as determined by the Firebase + * servers. + * + * @example + * ```javascript + * var sessionsRef = firebase.database().ref("sessions"); + * sessionsRef.push({ + * startedAt: firebase.database.ServerValue.TIMESTAMP + * }); + * ``` + */ + const TIMESTAMP: Object; + + /** + * Returns a placeholder value that can be used to atomically increment the + * current database value by the provided delta. + * + * @param delta the amount to modify the current value atomically. + * @return a placeholder value for modifying data atomically server-side. + */ + function increment(delta: number): Object; +} \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts index 4651c4fc71..3df285bed9 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -741,15 +741,15 @@ declare namespace admin.credential { } declare namespace admin.database { - export import Database = _database.database.Database; - export import DataSnapshot = _database.database.DataSnapshot; - export import OnDisconnect = _database.database.OnDisconnect; - export import EventType = _database.database.EventType; - export import Query = _database.database.Query; - export import Reference = _database.database.Reference; - export import ThenableReference = _database.database.ThenableReference; - export import enableLogging = _database.database.enableLogging; - export import ServerValue = _database.database.ServerValue; + export import Database = _database.admin.database.Database; + export import DataSnapshot = _database.admin.database.DataSnapshot; + export import OnDisconnect = _database.admin.database.OnDisconnect; + export import EventType = _database.admin.database.EventType; + export import Query = _database.admin.database.Query; + export import Reference = _database.admin.database.Reference; + export import ThenableReference = _database.admin.database.ThenableReference; + export import enableLogging = _database.admin.database.enableLogging; + export import ServerValue = _database.admin.database.ServerValue; } declare namespace admin.messaging { From 98ea0573268152a9c7038a213a7f8c3d7e6aa0d1 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 13:17:15 -0400 Subject: [PATCH 04/46] Remove src/test.ts --- src/test.ts | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/test.ts diff --git a/src/test.ts b/src/test.ts deleted file mode 100644 index a481e6320b..0000000000 --- a/src/test.ts +++ /dev/null @@ -1,5 +0,0 @@ -const obj = { - 'hello': 'ok' -}; - -export default obj; \ No newline at end of file From aacb3bed2b913cdb5fd9e4b58dc70ec54375c4bd Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 13:26:12 -0400 Subject: [PATCH 05/46] Add missing newline in database.d.ts --- src/database.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database.d.ts b/src/database.d.ts index ee9cec8057..1bdf425ea1 100644 --- a/src/database.d.ts +++ b/src/database.d.ts @@ -1661,4 +1661,4 @@ export namespace admin.database.ServerValue { * @return a placeholder value for modifying data atomically server-side. */ function increment(delta: number): Object; -} \ No newline at end of file +} From ed922c0a12eca9195a579139484882926aedf39d Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 17:40:52 -0400 Subject: [PATCH 06/46] Add progress --- gulpfile.js | 57 ++++++++++++++++++++++++------------ package.json | 2 ++ src/database/database.ts | 7 +++++ test/integration/app.spec.ts | 12 ++++++++ tsconfig.json | 3 +- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 692bb33dea..372576d13d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -56,10 +56,12 @@ var paths = { // Create a separate project for buildProject that overrides the rootDir // This ensures that the generated production files are in their own root // rather than including both src and test in the lib dir. -var buildProject = ts.createProject('tsconfig.json', {rootDir: 'src'}); +var buildProject = ts.createProject('tsconfig.json', { rootDir: 'src' }); var buildTest = ts.createProject('tsconfig.json'); +var buildAutogenTyping = ts.createProject('tsconfig.json', { declaration: true }); + var banner = `/*! firebase-admin v${pkg.version} */\n`; /***********/ @@ -76,22 +78,6 @@ gulp.task('compile', function() { return gulp.src(paths.src) // Compile Typescript into .js and .d.ts files .pipe(buildProject()) - .pipe(filter([ - '**', - '!lib/default-namespace.d.ts', - '!lib/firebase-namespace.d.ts', - '!lib/firebase-service.d.ts', - '!lib/**/*-internal.d.ts', - '!lib/auth/*.d.ts', - '!lib/firestore/*.d.ts', - '!lib/instance-id/*.d.ts', - '!lib/machine-learning/*.d.ts', - '!lib/messaging/*.d.ts', - '!lib/project-management/*.d.ts', - '!lib/remote-config/*.d.ts', - '!lib/security-rules/*.d.ts', - '!lib/storage/*.d.ts', - '!lib/utils/*.d.ts'])) // Add header .pipe(header(banner)) @@ -110,6 +96,38 @@ gulp.task('compile_test', function() { .pipe(buildTest()) }); +/** + * Task only used to verify TypeScript autogenerated typings. + */ +gulp.task('compile_autogen_typing', function() { + return gulp.src(paths.src) + // Compile Typescript into .js and .d.ts files + .pipe(buildAutogenTyping()) + + // Exclude typings that are unintended (only RTDB is expected to produce + // typings). + .pipe(filter([ + 'lib/src/**/*.js', + '!lib/src/index.d.ts', + '!lib/src/default-namespace.d.ts', + '!lib/src/firebase-namespace.d.ts', + '!lib/src/firebase-service.d.ts', + '!lib/**/*-internal.d.ts', + '!lib/src/auth/*.d.ts', + '!lib/src/firestore/*.d.ts', + '!lib/src/instance-id/*.d.ts', + '!lib/src/machine-learning/*.d.ts', + '!lib/src/messaging/*.d.ts', + '!lib/src/project-management/*.d.ts', + '!lib/src/remote-config/*.d.ts', + '!lib/src/security-rules/*.d.ts', + '!lib/src/storage/*.d.ts', + '!lib/src/utils/*.d.ts'])) + + // Write to build directory + .pipe(gulp.dest(paths.build)) +}); + gulp.task('copyDatabase', function() { return gulp.src(paths.databaseSrc) // Add headers @@ -133,11 +151,14 @@ gulp.task('compile_all', gulp.series('compile', 'copyDatabase', 'copyTypings', ' // Regenerates js every time a source file changes gulp.task('watch', function() { - gulp.watch(paths.src.concat(paths.test), {ignoreInitial: false}, gulp.series('compile_all')); + gulp.watch(paths.src.concat(paths.test), { ignoreInitial: false }, gulp.series('compile_all')); }); // Build task gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings')); +// Build typings +gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings')); // , 'copyTypings')); + // Default task gulp.task('default', gulp.series('build')); diff --git a/package.json b/package.json index 0cda64390a..e6a96ef8e4 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "scripts": { "build": "gulp build", "build:tests": "gulp compile_test", + "build:typing": "gulp build_typings", "prepare": "npm run build", "lint": "run-p lint:src lint:test", "test": "run-s lint test:unit", @@ -18,6 +19,7 @@ "test:unit": "mocha test/unit/*.spec.ts --require ts-node/register", "test:integration": "mocha test/integration/*.ts --slow 5000 --timeout 20000 --require ts-node/register", "test:coverage": "nyc npm run test:unit", + "test:typing": "run-s build:typing test:unit test:integration", "lint:src": "eslint src/ --ext .ts", "lint:test": "eslint test/ --ext .ts", "apidocs": "node docgen/generate-docs.js --api node" diff --git a/src/database/database.ts b/src/database/database.ts index 87c96b2a53..c4dcda850d 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -15,9 +15,16 @@ */ import { Reference } from '@firebase/database'; +import { FirebaseApp } from '../firebase-app'; +import { Database } from '@firebase/database'; export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; + +export function database(app: FirebaseApp): Database { + return app.database(); +} + /** * @extends {Reference} */ diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index 3cdf356245..82aa66bafc 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -27,6 +27,18 @@ describe('admin', () => { expect(storageBucket).to.be.not.empty; }); + it('does not load RTDB by default', () => { + const rtdb = require.cache[require.resolve('@firebase/database')]; + expect(rtdb).to.be.undefined; + }); + + it('loads RTDB when calling admin.database', () => { + const rtdbNamespace = admin.database; + expect(rtdbNamespace).to.not.be.null; + const rtdb = require.cache[require.resolve('@firebase/database')]; + expect(rtdb).to.not.be.undefined; + }); + it('does not load Firestore by default', () => { const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; expect(gcloud).to.be.undefined; diff --git a/tsconfig.json b/tsconfig.json index e9fec600fb..e13292b408 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,8 +14,7 @@ "lib": ["es2015"], "outDir": "lib", // We manually craft typings in src/index.d.ts instead of auto-generating them. - // Currently, only RTDB has auto-generated typings, the rest default to src/index.d.ts - "declaration": true, + // "declaration": true, "rootDir": "." }, "files": [ From c92e67078e0d753834a3a9a4a096650d9ee56dcb Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 18:40:41 -0400 Subject: [PATCH 07/46] Add more integration tests --- gulpfile.js | 8 +++++++- test/integration/app.spec.ts | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 372576d13d..b016d25081 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -51,6 +51,8 @@ var paths = { ], build: 'lib/', + + autoGeneratedTypings: ['lib/database.d.ts'], }; // Create a separate project for buildProject that overrides the rootDir @@ -147,6 +149,10 @@ gulp.task('copyTypings', function() { .pipe(gulp.dest(paths.build)) }); +gulp.task('removeAutogeneratedTypings', function() { + return del(paths.autoGeneratedTypings); +}); + gulp.task('compile_all', gulp.series('compile', 'copyDatabase', 'copyTypings', 'compile_test')); // Regenerates js every time a source file changes @@ -158,7 +164,7 @@ gulp.task('watch', function() { gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings')); // Build typings -gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings')); // , 'copyTypings')); +gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeAutogeneratedTypings')); // , 'copyTypings')); // Default task gulp.task('default', gulp.series('build')); diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index 82aa66bafc..98ac6273b0 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -30,6 +30,8 @@ describe('admin', () => { it('does not load RTDB by default', () => { const rtdb = require.cache[require.resolve('@firebase/database')]; expect(rtdb).to.be.undefined; + const rtdbInternal = require.cache[require.resolve('../../lib/database/database-internal')]; + expect(rtdbInternal).to.be.undefined; }); it('loads RTDB when calling admin.database', () => { From 05f5f6b650cb21fe17843accaa73dbdaa590a4fa Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 18:51:25 -0400 Subject: [PATCH 08/46] Small modifications --- gulpfile.js | 2 +- src/database/database-internal.ts | 16 ++++++++++++++++ test/integration/database.spec.ts | 1 - 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index b016d25081..0fd2e55911 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -164,7 +164,7 @@ gulp.task('watch', function() { gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings')); // Build typings -gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeAutogeneratedTypings')); // , 'copyTypings')); +gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeAutogeneratedTypings')); // Default task gulp.task('default', gulp.series('build')); diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts index 97c3301b20..1939e7b6a4 100644 --- a/src/database/database-internal.ts +++ b/src/database/database-internal.ts @@ -1,3 +1,19 @@ +/*! + * Copyright 2020 Google Inc. + * + * 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 { URL } from 'url'; import * as path from 'path'; diff --git a/test/integration/database.spec.ts b/test/integration/database.spec.ts index 71efcbc185..7683e29c81 100644 --- a/test/integration/database.spec.ts +++ b/test/integration/database.spec.ts @@ -169,7 +169,6 @@ describe('admin.database', () => { }); it('admin.database().getRulesJSON() returns currently defined rules as an object', () => { - return admin.database().getRulesJSON().then((result) => { return expect(result).to.be.not.undefined; }); From eb8e85c5257ea78e0254f55217d5a5ddba8cec93 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 18:59:45 -0400 Subject: [PATCH 09/46] Add gulp-filter to dependencies --- package-lock.json | 90 +++++++++++++++++++++++++++++++++-------------- package.json | 1 + 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index faacfd5dbf..623482b748 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "8.13.0", + "version": "9.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -572,8 +572,7 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/minimist": { "version": "1.2.0", @@ -795,7 +794,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, "requires": { "ansi-wrap": "^0.1.0" } @@ -841,8 +839,7 @@ "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" }, "any-promise": { "version": "1.3.0", @@ -912,8 +909,7 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-filter": { "version": "1.1.2", @@ -942,8 +938,7 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-differ": { "version": "1.0.0", @@ -1047,8 +1042,7 @@ "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "optional": true + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" }, "asn1": { "version": "0.2.4", @@ -1074,8 +1068,7 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "astral-regex": { "version": "1.0.0", @@ -1163,8 +1156,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -1289,7 +1281,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1724,8 +1715,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "2.0.0", @@ -2709,7 +2699,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -2719,7 +2708,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -3532,6 +3520,16 @@ } } }, + "gulp-filter": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-6.0.0.tgz", + "integrity": "sha512-veQFW93kf6jBdWdF/RxMEIlDK2mkjHyPftM381DID2C9ImTVngwYpyyThxm4/EpgcNOT37BLefzMOjEKbyYg0Q==", + "requires": { + "multimatch": "^4.0.0", + "plugin-error": "^1.0.1", + "streamfilter": "^3.0.0" + } + }, "gulp-header": { "version": "1.8.12", "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", @@ -4348,7 +4346,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -4438,8 +4435,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isstream": { "version": "0.1.2", @@ -5248,7 +5244,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5387,6 +5382,30 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "multimatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "requires": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==" + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + } + } + }, "multipipe": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", @@ -6334,7 +6353,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, "requires": { "ansi-colors": "^1.0.1", "arr-diff": "^4.0.0", @@ -7393,6 +7411,26 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "streamfilter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", + "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", + "requires": { + "readable-stream": "^3.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", diff --git a/package.json b/package.json index e6a96ef8e4..5db0af0385 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@firebase/database": "^0.6.0", "@types/node": "^10.10.0", "dicer": "^0.3.0", + "gulp-filter": "^6.0.0", "jsonwebtoken": "^8.5.1", "node-forge": "^0.9.1" }, From f143097a122a0a5fa827fb39323b62b6e341c61f Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 22 Jul 2020 19:00:53 -0400 Subject: [PATCH 10/46] Add gulp-filter to dependencies --- package-lock.json | 41 +++++++++++++++++++++++++++++++---------- package.json | 2 +- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 623482b748..bc50b9c831 100644 --- a/package-lock.json +++ b/package-lock.json @@ -572,7 +572,8 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true }, "@types/minimist": { "version": "1.2.0", @@ -794,6 +795,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, "requires": { "ansi-wrap": "^0.1.0" } @@ -839,7 +841,8 @@ "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true }, "any-promise": { "version": "1.3.0", @@ -909,7 +912,8 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "arr-filter": { "version": "1.1.2", @@ -938,7 +942,8 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true }, "array-differ": { "version": "1.0.0", @@ -1068,7 +1073,8 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true }, "astral-regex": { "version": "1.0.0", @@ -1156,7 +1162,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -1281,6 +1288,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1715,7 +1723,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "2.0.0", @@ -2699,6 +2708,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -2708,6 +2718,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -3524,6 +3535,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-6.0.0.tgz", "integrity": "sha512-veQFW93kf6jBdWdF/RxMEIlDK2mkjHyPftM381DID2C9ImTVngwYpyyThxm4/EpgcNOT37BLefzMOjEKbyYg0Q==", + "dev": true, "requires": { "multimatch": "^4.0.0", "plugin-error": "^1.0.1", @@ -4346,6 +4358,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, "requires": { "isobject": "^3.0.1" } @@ -4435,7 +4448,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "isstream": { "version": "0.1.2", @@ -5244,6 +5258,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5386,6 +5401,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "dev": true, "requires": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", @@ -5397,12 +5413,14 @@ "array-differ": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==" + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true } } }, @@ -6353,6 +6371,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, "requires": { "ansi-colors": "^1.0.1", "arr-diff": "^4.0.0", @@ -7415,6 +7434,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", + "dev": true, "requires": { "readable-stream": "^3.0.6" }, @@ -7423,6 +7443,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", diff --git a/package.json b/package.json index 5db0af0385..ba6f1f5089 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "@firebase/database": "^0.6.0", "@types/node": "^10.10.0", "dicer": "^0.3.0", - "gulp-filter": "^6.0.0", "jsonwebtoken": "^8.5.1", "node-forge": "^0.9.1" }, @@ -95,6 +94,7 @@ "eslint": "^6.8.0", "firebase-token-generator": "^2.0.0", "gulp": "^4.0.2", + "gulp-filter": "^6.0.0", "gulp-header": "^1.8.8", "gulp-replace": "^0.5.4", "gulp-typescript": "^5.0.1", From d78f3e5917865673e0ab78c8126ddc5feb0edb79 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 10:28:49 -0400 Subject: [PATCH 11/46] Address broken unit tests --- test/resources/mocks.ts | 3 ++- test/unit/firebase-app.spec.ts | 3 ++- test/unit/utils/index.spec.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index cb2be41a8f..2515a9c58d 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -26,7 +26,8 @@ import * as jwt from 'jsonwebtoken'; import { FirebaseNamespace } from '../../src/firebase-namespace'; import { FirebaseServiceInterface } from '../../src/firebase-service'; -import { FirebaseApp, FirebaseAppOptions } from '../../src/firebase-app'; +import { FirebaseApp } from '../../src/firebase-app'; +import { FirebaseAppOptions } from '../../src/firebase-app-internal'; import { Credential, GoogleOAuthAccessToken, ServiceAccountCredential } from '../../src/auth/credential'; const ALGORITHM = 'RS256'; diff --git a/test/unit/firebase-app.spec.ts b/test/unit/firebase-app.spec.ts index dc89b6383c..de76ba2bf6 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/firebase-app.spec.ts @@ -27,7 +27,8 @@ import * as mocks from '../resources/mocks'; import { GoogleOAuthAccessToken, ServiceAccountCredential } from '../../src/auth/credential'; import { FirebaseServiceInterface } from '../../src/firebase-service'; -import { FirebaseApp, FirebaseAccessToken } from '../../src/firebase-app'; +import { FirebaseApp } from '../../src/firebase-app'; +import { FirebaseAccessToken } from '../../src/firebase-app-internal'; import { FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR } from '../../src/firebase-namespace'; import { Auth } from '../../src/auth/auth'; diff --git a/test/unit/utils/index.spec.ts b/test/unit/utils/index.spec.ts index acb76f074a..226b56fc55 100644 --- a/test/unit/utils/index.spec.ts +++ b/test/unit/utils/index.spec.ts @@ -24,7 +24,8 @@ import { toWebSafeBase64, formatString, generateUpdateMask, } from '../../../src/utils/index'; import { isNonEmptyString } from '../../../src/utils/validator'; -import { FirebaseApp, FirebaseAppOptions } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseAppOptions } from '../../../src/firebase-app-internal'; import { ComputeEngineCredential } from '../../../src/auth/credential'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; From bc939c143265e0dd0d751bf190cf0e48e8e92a1f Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 11:09:20 -0400 Subject: [PATCH 12/46] Rename gulpfile task name for autogenerated typings --- gulpfile.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 0fd2e55911..fd3b00f256 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -106,8 +106,8 @@ gulp.task('compile_autogen_typing', function() { // Compile Typescript into .js and .d.ts files .pipe(buildAutogenTyping()) - // Exclude typings that are unintended (only RTDB is expected to produce - // typings). + // Exclude typings that are unintended (only RTDB and FirebaseApp are + // expected to produce typings). .pipe(filter([ 'lib/src/**/*.js', '!lib/src/index.d.ts', @@ -149,7 +149,7 @@ gulp.task('copyTypings', function() { .pipe(gulp.dest(paths.build)) }); -gulp.task('removeAutogeneratedTypings', function() { +gulp.task('removeManuallyCuratedTypings', function() { return del(paths.autoGeneratedTypings); }); @@ -164,7 +164,7 @@ gulp.task('watch', function() { gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings')); // Build typings -gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeAutogeneratedTypings')); +gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeManuallyCuratedTypings')); // Default task gulp.task('default', gulp.series('build')); From 339268f02fdad8605b8be972c80706f722e41e20 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 12:21:18 -0400 Subject: [PATCH 13/46] Minor fixes --- gulpfile.js | 2 ++ src/database/index.ts | 62 ++++++++---------------------------- test/integration/app.spec.ts | 8 ++--- 3 files changed, 20 insertions(+), 52 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index fd3b00f256..4a10d82adf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -110,6 +110,7 @@ gulp.task('compile_autogen_typing', function() { // expected to produce typings). .pipe(filter([ 'lib/src/**/*.js', + 'lib/src/**/*.d.ts', '!lib/src/index.d.ts', '!lib/src/default-namespace.d.ts', '!lib/src/firebase-namespace.d.ts', @@ -165,6 +166,7 @@ gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTyping // Build typings gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeManuallyCuratedTypings')); +// gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings')); // Default task gulp.task('default', gulp.series('build')); diff --git a/src/database/index.ts b/src/database/index.ts index 39669d1166..017e45e02f 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -15,61 +15,27 @@ */ import { FirebaseApp } from '../firebase-app'; -import * as firebaseDbApi from '@firebase/database'; -import * as adminDbApi from './database'; +import * as firebaseRtdbApi from '@firebase/database'; +import * as adminRtdbApi from './database'; -/** - * Gets the {@link admin.database.Database `Database`} service for the default - * app or a given app. - * - * `admin.database()` can be called with no arguments to access the default - * app's {@link admin.database.Database `Database`} service or as - * `admin.database(app)` to access the - * {@link admin.database.Database `Database`} service associated with a specific - * app. - * - * `admin.database` is also a namespace that can be used to access global - * constants and methods associated with the `Database` service. - * - * @example - * ```javascript - * // Get the Database service for the default app - * var defaultDatabase = admin.database(); - * ``` - * - * @example - * ```javascript - * // Get the Database service for a specific app - * var otherDatabase = admin.database(app); - * ``` - * - * @param App whose `Database` service to - * return. If not provided, the default `Database` service will be returned. - * - * @return The default `Database` service if no app - * is provided or the `Database` service associated with the provided app. - */ -export function database(app: FirebaseApp): firebaseDbApi.Database { +export function database(app: FirebaseApp): firebaseRtdbApi.Database { return app.database(); } -// This is unfortunate. But it seems we must define a namespace to make -// the typings work correctly. Otherwise `admin.database()` cannot be called -// like a function. It would be great if we can find an alternative. +// We must define a namespace to make the typings work correctly. +// Otherwise `admin.database()` cannot be called like a function. /* eslint-disable @typescript-eslint/no-namespace */ export namespace database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ // For context: github.com/typescript-eslint/typescript-eslint/issues/363 - export import Database = firebaseDbApi.Database; - export import DataSnapshot = firebaseDbApi.DataSnapshot; - export import OnDisconnect = firebaseDbApi.OnDisconnect; - export import EventType = adminDbApi.EventType; - export import Query = firebaseDbApi.Query; - export import Reference = firebaseDbApi.Reference; - export import ThenableReference = adminDbApi.ThenableReference; - export import enableLogging = firebaseDbApi.enableLogging; - export import ServerValue = firebaseDbApi.ServerValue; + export import Database = firebaseRtdbApi.Database; + export import DataSnapshot = firebaseRtdbApi.DataSnapshot; + export import OnDisconnect = firebaseRtdbApi.OnDisconnect; + export import EventType = adminRtdbApi.EventType; + export import Query = firebaseRtdbApi.Query; + export import Reference = firebaseRtdbApi.Reference; + export import ThenableReference = adminRtdbApi.ThenableReference; + export import enableLogging = firebaseRtdbApi.enableLogging; + export import ServerValue = firebaseRtdbApi.ServerValue; } - - diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index 98ac6273b0..cce88a52aa 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -28,8 +28,8 @@ describe('admin', () => { }); it('does not load RTDB by default', () => { - const rtdb = require.cache[require.resolve('@firebase/database')]; - expect(rtdb).to.be.undefined; + const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + expect(firebaseRtdb).to.be.undefined; const rtdbInternal = require.cache[require.resolve('../../lib/database/database-internal')]; expect(rtdbInternal).to.be.undefined; }); @@ -37,8 +37,8 @@ describe('admin', () => { it('loads RTDB when calling admin.database', () => { const rtdbNamespace = admin.database; expect(rtdbNamespace).to.not.be.null; - const rtdb = require.cache[require.resolve('@firebase/database')]; - expect(rtdb).to.not.be.undefined; + const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + expect(firebaseRtdb).to.not.be.undefined; }); it('does not load Firestore by default', () => { From d7cd5631282ec034195869abe5b9c3eeedd9ea1e Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 12:59:58 -0400 Subject: [PATCH 14/46] Add extra level of nesting in the namespace export --- gulpfile.js | 1 - src/database/index.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 4a10d82adf..93a3c00adc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -166,7 +166,6 @@ gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTyping // Build typings gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeManuallyCuratedTypings')); -// gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings')); // Default task gulp.task('default', gulp.series('build')); diff --git a/src/database/index.ts b/src/database/index.ts index 017e45e02f..c9467ff940 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -25,7 +25,7 @@ export function database(app: FirebaseApp): firebaseRtdbApi.Database { // We must define a namespace to make the typings work correctly. // Otherwise `admin.database()` cannot be called like a function. /* eslint-disable @typescript-eslint/no-namespace */ -export namespace database { +export namespace admin.database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ // For context: github.com/typescript-eslint/typescript-eslint/issues/363 From 4908ee4fa4e2961ba2d34368d72b3b64398aa278 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 15:32:21 -0400 Subject: [PATCH 15/46] Add separate import for types --- src/database/index.ts | 5 +++-- test/integration/database.spec.ts | 9 ++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/database/index.ts b/src/database/index.ts index c9467ff940..27577eff0a 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -16,6 +16,7 @@ import { FirebaseApp } from '../firebase-app'; import * as firebaseRtdbApi from '@firebase/database'; +import * as firebaseRtdbTypesApi from '@firebase/database-types'; import * as adminRtdbApi from './database'; export function database(app: FirebaseApp): firebaseRtdbApi.Database { @@ -30,12 +31,12 @@ export namespace admin.database { /* eslint-disable @typescript-eslint/no-unused-vars */ // For context: github.com/typescript-eslint/typescript-eslint/issues/363 export import Database = firebaseRtdbApi.Database; - export import DataSnapshot = firebaseRtdbApi.DataSnapshot; + export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; export import OnDisconnect = firebaseRtdbApi.OnDisconnect; export import EventType = adminRtdbApi.EventType; export import Query = firebaseRtdbApi.Query; export import Reference = firebaseRtdbApi.Reference; export import ThenableReference = adminRtdbApi.ThenableReference; export import enableLogging = firebaseRtdbApi.enableLogging; - export import ServerValue = firebaseRtdbApi.ServerValue; + export import ServerValue = firebaseRtdbTypesApi.ServerValue; } diff --git a/test/integration/database.spec.ts b/test/integration/database.spec.ts index 7683e29c81..e8d0a746a4 100644 --- a/test/integration/database.spec.ts +++ b/test/integration/database.spec.ts @@ -29,11 +29,6 @@ const expect = chai.expect; const path = 'adminNodeSdkManualTest'; -interface DatabaseRecord { - success: boolean; - timestamp: string; -} - describe('admin.database', () => { before(() => { @@ -102,7 +97,7 @@ describe('admin.database', () => { it('once() returns the current value of the reference', () => { return ref.once('value') .then((snapshot) => { - const value = snapshot.val() as DatabaseRecord; + const value = snapshot.val(); expect(value.success).to.be.true; expect(typeof value.timestamp).to.equal('number'); }); @@ -144,7 +139,7 @@ describe('admin.database', () => { it('once() returns the current value of the reference', () => { return refWithUrl.once('value') .then((snapshot) => { - const value = snapshot.val() as DatabaseRecord; + const value = snapshot.val(); expect(value.success).to.be.true; expect(typeof value.timestamp).to.equal('number'); }); From 4671dbc5ed1b44cca5a91a192d7884cb070018ef Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 15:43:02 -0400 Subject: [PATCH 16/46] Make ServerValue also use RTDB --- src/database/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/index.ts b/src/database/index.ts index 27577eff0a..e8999ad442 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -38,5 +38,5 @@ export namespace admin.database { export import Reference = firebaseRtdbApi.Reference; export import ThenableReference = adminRtdbApi.ThenableReference; export import enableLogging = firebaseRtdbApi.enableLogging; - export import ServerValue = firebaseRtdbTypesApi.ServerValue; + export import ServerValue = firebaseRtdbApi.ServerValue; } From 7e35aac9e96947060ef0c1ff93c040333cd94c7f Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 19:23:38 -0400 Subject: [PATCH 17/46] Address some of the comments --- gulpfile.js | 52 ++++++++++++++++++++++------------------ src/database/database.ts | 7 ------ src/database/index.ts | 6 ++--- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 93a3c00adc..29c20e720f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -52,7 +52,7 @@ var paths = { build: 'lib/', - autoGeneratedTypings: ['lib/database.d.ts'], + curatedTypings: ['lib/database.d.ts'], }; // Create a separate project for buildProject that overrides the rootDir @@ -62,7 +62,7 @@ var buildProject = ts.createProject('tsconfig.json', { rootDir: 'src' }); var buildTest = ts.createProject('tsconfig.json'); -var buildAutogenTyping = ts.createProject('tsconfig.json', { declaration: true }); +var buildAutogenTyping = ts.createProject('tsconfig.json', { rootDir: 'src', declaration: true }); var banner = `/*! firebase-admin v${pkg.version} */\n`; @@ -99,33 +99,39 @@ gulp.task('compile_test', function() { }); /** - * Task only used to verify TypeScript autogenerated typings. + * Task used to verify TypeScript auto-generated typings. First, it builds + * the project generating both *.js and *.d.ts files. After, it removes + * declarations for all files terminating in -internal.d.ts because we do not + * want to expose internally exposed types to developers. As auto-generated + * typings are a work-in-progress, we remove the *.d.ts files for classes + * which we do not intend to auto-generate typings for yet. */ gulp.task('compile_autogen_typing', function() { return gulp.src(paths.src) // Compile Typescript into .js and .d.ts files .pipe(buildAutogenTyping()) - // Exclude typings that are unintended (only RTDB and FirebaseApp are - // expected to produce typings). + // Exclude typings that are unintended (only RTDB is expected to produce + // typings so far). .pipe(filter([ - 'lib/src/**/*.js', - 'lib/src/**/*.d.ts', - '!lib/src/index.d.ts', - '!lib/src/default-namespace.d.ts', - '!lib/src/firebase-namespace.d.ts', - '!lib/src/firebase-service.d.ts', - '!lib/**/*-internal.d.ts', - '!lib/src/auth/*.d.ts', - '!lib/src/firestore/*.d.ts', - '!lib/src/instance-id/*.d.ts', - '!lib/src/machine-learning/*.d.ts', - '!lib/src/messaging/*.d.ts', - '!lib/src/project-management/*.d.ts', - '!lib/src/remote-config/*.d.ts', - '!lib/src/security-rules/*.d.ts', - '!lib/src/storage/*.d.ts', - '!lib/src/utils/*.d.ts'])) + 'lib/**/*.js', + 'lib/**/*.d.ts', + '!lib/*-internal.d.ts', + '!lib/index.d.ts', + '!lib/default-namespace.d.ts', + '!lib/firebase-namespace.d.ts', + '!lib/firebase-app.d.ts', + '!lib/firebase-service.d.ts', + '!lib/auth/*.d.ts', + '!lib/firestore/*.d.ts', + '!lib/instance-id/*.d.ts', + '!lib/machine-learning/*.d.ts', + '!lib/messaging/*.d.ts', + '!lib/project-management/*.d.ts', + '!lib/remote-config/*.d.ts', + '!lib/security-rules/*.d.ts', + '!lib/storage/*.d.ts', + '!lib/utils/*.d.ts'])) // Write to build directory .pipe(gulp.dest(paths.build)) @@ -151,7 +157,7 @@ gulp.task('copyTypings', function() { }); gulp.task('removeManuallyCuratedTypings', function() { - return del(paths.autoGeneratedTypings); + return del(paths.curatedTypings); }); gulp.task('compile_all', gulp.series('compile', 'copyDatabase', 'copyTypings', 'compile_test')); diff --git a/src/database/database.ts b/src/database/database.ts index c4dcda850d..87c96b2a53 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -15,16 +15,9 @@ */ import { Reference } from '@firebase/database'; -import { FirebaseApp } from '../firebase-app'; -import { Database } from '@firebase/database'; export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; - -export function database(app: FirebaseApp): Database { - return app.database(); -} - /** * @extends {Reference} */ diff --git a/src/database/index.ts b/src/database/index.ts index e8999ad442..613008e0d7 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -26,17 +26,17 @@ export function database(app: FirebaseApp): firebaseRtdbApi.Database { // We must define a namespace to make the typings work correctly. // Otherwise `admin.database()` cannot be called like a function. /* eslint-disable @typescript-eslint/no-namespace */ -export namespace admin.database { +export namespace database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ // For context: github.com/typescript-eslint/typescript-eslint/issues/363 export import Database = firebaseRtdbApi.Database; export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; export import OnDisconnect = firebaseRtdbApi.OnDisconnect; - export import EventType = adminRtdbApi.EventType; + export import EventType = firebaseRtdbTypesApi.EventType; export import Query = firebaseRtdbApi.Query; export import Reference = firebaseRtdbApi.Reference; export import ThenableReference = adminRtdbApi.ThenableReference; export import enableLogging = firebaseRtdbApi.enableLogging; - export import ServerValue = firebaseRtdbApi.ServerValue; + export import ServerValue = firebaseRtdbTypesApi.ServerValue; } From adf7126c2dd12b0e6a68309450ff7f6dcaadf24e Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 20:25:44 -0400 Subject: [PATCH 18/46] Undo changes to remove FirebaseApp split --- src/database/database.ts | 9 -- src/database/index.ts | 11 +- src/firebase-app-internal.ts | 225 --------------------------------- src/firebase-app.ts | 210 +++++++++++++++++++++++++++++- src/firebase-namespace.ts | 3 +- src/utils/index.ts | 3 +- test/resources/mocks.ts | 3 +- test/unit/firebase-app.spec.ts | 3 +- test/unit/utils.ts | 3 +- test/unit/utils/index.spec.ts | 3 +- 10 files changed, 218 insertions(+), 255 deletions(-) delete mode 100644 src/firebase-app-internal.ts diff --git a/src/database/database.ts b/src/database/database.ts index 87c96b2a53..37db18627e 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -14,15 +14,6 @@ * limitations under the License. */ -import { Reference } from '@firebase/database'; - -export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; - -/** - * @extends {Reference} - */ -export interface ThenableReference extends Reference, Promise { } - declare module '@firebase/database' { interface Database { /** diff --git a/src/database/index.ts b/src/database/index.ts index 613008e0d7..883f2178ec 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -17,7 +17,6 @@ import { FirebaseApp } from '../firebase-app'; import * as firebaseRtdbApi from '@firebase/database'; import * as firebaseRtdbTypesApi from '@firebase/database-types'; -import * as adminRtdbApi from './database'; export function database(app: FirebaseApp): firebaseRtdbApi.Database { return app.database(); @@ -32,11 +31,11 @@ export namespace database { // For context: github.com/typescript-eslint/typescript-eslint/issues/363 export import Database = firebaseRtdbApi.Database; export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; - export import OnDisconnect = firebaseRtdbApi.OnDisconnect; + export import OnDisconnect = firebaseRtdbTypesApi.OnDisconnect; export import EventType = firebaseRtdbTypesApi.EventType; - export import Query = firebaseRtdbApi.Query; - export import Reference = firebaseRtdbApi.Reference; - export import ThenableReference = adminRtdbApi.ThenableReference; - export import enableLogging = firebaseRtdbApi.enableLogging; + export import Query = firebaseRtdbTypesApi.Query; + export import Reference = firebaseRtdbTypesApi.Reference; + export import ThenableReference = firebaseRtdbTypesApi.ThenableReference; + export import enableLogging = firebaseRtdbTypesApi.enableLogging; export import ServerValue = firebaseRtdbTypesApi.ServerValue; } diff --git a/src/firebase-app-internal.ts b/src/firebase-app-internal.ts deleted file mode 100644 index 03a131b317..0000000000 --- a/src/firebase-app-internal.ts +++ /dev/null @@ -1,225 +0,0 @@ -/*! - * Copyright 2020 Google Inc. - * - * 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 { FirebaseApp } from './firebase-app'; -import { Credential, GoogleOAuthAccessToken } from './auth/credential'; -import * as validator from './utils/validator'; -import { AppErrorCodes, FirebaseAppError } from './utils/error'; - -import { Agent } from 'http'; - -/** - * Type representing a callback which is called every time an app lifecycle event occurs. - */ -export type AppHook = (event: string, app: FirebaseApp) => void; - -/** - * Type representing the options object passed into initializeApp(). - */ -export interface FirebaseAppOptions { - credential?: Credential; - databaseAuthVariableOverride?: object | null; - databaseURL?: string; - serviceAccountId?: string; - storageBucket?: string; - projectId?: string; - httpAgent?: Agent; -} - -/** - * Type representing a Firebase OAuth access token (derived from a Google OAuth2 access token) which - * can be used to authenticate to Firebase services such as the Realtime Database and Auth. - */ -export interface FirebaseAccessToken { - accessToken: string; - expirationTime: number; -} - -/** - * Internals of a FirebaseApp instance. - */ -export class FirebaseAppInternals { - private isDeleted_ = false; - private cachedToken_: FirebaseAccessToken; - private cachedTokenPromise_: Promise | null; - private tokenListeners_: Array<(token: string) => void>; - private tokenRefreshTimeout_: NodeJS.Timer; - - constructor(private credential_: Credential) { - this.tokenListeners_ = []; - } - - /** - * Gets an auth token for the associated app. - * - * @param {boolean} forceRefresh Whether or not to force a token refresh. - * @return {Promise} A Promise that will be fulfilled with the current or - * new token. - */ - public getToken(forceRefresh?: boolean): Promise { - const expired = this.cachedToken_ && this.cachedToken_.expirationTime < Date.now(); - if (this.cachedTokenPromise_ && !forceRefresh && !expired) { - return this.cachedTokenPromise_ - .catch((error) => { - // Update the cached token promise to avoid caching errors. Set it to resolve with the - // cached token if we have one (and return that promise since the token has still not - // expired). - if (this.cachedToken_) { - this.cachedTokenPromise_ = Promise.resolve(this.cachedToken_); - return this.cachedTokenPromise_; - } - - // Otherwise, set the cached token promise to null so that it will force a refresh next - // time getToken() is called. - this.cachedTokenPromise_ = null; - - // And re-throw the caught error. - throw error; - }); - } else { - // Clear the outstanding token refresh timeout. This is a noop if the timeout is undefined. - clearTimeout(this.tokenRefreshTimeout_); - - // this.credential_ may be an external class; resolving it in a promise helps us - // protect against exceptions and upgrades the result to a promise in all cases. - this.cachedTokenPromise_ = Promise.resolve(this.credential_.getAccessToken()) - .then((result: GoogleOAuthAccessToken) => { - // Since the developer can provide the credential implementation, we want to weakly verify - // the return type until the type is properly exported. - if (!validator.isNonNullObject(result) || - typeof result.expires_in !== 'number' || - typeof result.access_token !== 'string') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_CREDENTIAL, - `Invalid access token generated: "${JSON.stringify(result)}". Valid access ` + - 'tokens must be an object with the "expires_in" (number) and "access_token" ' + - '(string) properties.', - ); - } - - const token: FirebaseAccessToken = { - accessToken: result.access_token, - expirationTime: Date.now() + (result.expires_in * 1000), - }; - - const hasAccessTokenChanged = (this.cachedToken_ && this.cachedToken_.accessToken !== token.accessToken); - const hasExpirationChanged = (this.cachedToken_ && this.cachedToken_.expirationTime !== token.expirationTime); - if (!this.cachedToken_ || hasAccessTokenChanged || hasExpirationChanged) { - this.cachedToken_ = token; - this.tokenListeners_.forEach((listener) => { - listener(token.accessToken); - }); - } - - // Establish a timeout to proactively refresh the token every minute starting at five - // minutes before it expires. Once a token refresh succeeds, no further retries are - // needed; if it fails, retry every minute until the token expires (resulting in a total - // of four retries: at 4, 3, 2, and 1 minutes). - let refreshTimeInSeconds = (result.expires_in - (5 * 60)); - let numRetries = 4; - - // In the rare cases the token is short-lived (that is, it expires in less than five - // minutes from when it was fetched), establish the timeout to refresh it after the - // current minute ends and update the number of retries that should be attempted before - // the token expires. - if (refreshTimeInSeconds <= 0) { - refreshTimeInSeconds = result.expires_in % 60; - numRetries = Math.floor(result.expires_in / 60) - 1; - } - - // The token refresh timeout keeps the Node.js process alive, so only create it if this - // instance has not already been deleted. - if (numRetries && !this.isDeleted_) { - this.setTokenRefreshTimeout(refreshTimeInSeconds * 1000, numRetries); - } - - return token; - }) - .catch((error) => { - let errorMessage = (typeof error === 'string') ? error : error.message; - - errorMessage = 'Credential implementation provided to initializeApp() via the ' + - '"credential" property failed to fetch a valid Google OAuth2 access token with the ' + - `following error: "${errorMessage}".`; - - if (errorMessage.indexOf('invalid_grant') !== -1) { - errorMessage += ' There are two likely causes: (1) your server time is not properly ' + - 'synced or (2) your certificate key file has been revoked. To solve (1), re-sync the ' + - 'time on your server. To solve (2), make sure the key ID for your key file is still ' + - 'present at https://console.firebase.google.com/iam-admin/serviceaccounts/project. If ' + - 'not, generate a new key file at ' + - 'https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk.'; - } - - throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage); - }); - - return this.cachedTokenPromise_; - } - } - - /** - * Adds a listener that is called each time a token changes. - * - * @param {function(string)} listener The listener that will be called with each new token. - */ - public addAuthTokenListener(listener: (token: string) => void): void { - this.tokenListeners_.push(listener); - if (this.cachedToken_) { - listener(this.cachedToken_.accessToken); - } - } - - /** - * Removes a token listener. - * - * @param {function(string)} listener The listener to remove. - */ - public removeAuthTokenListener(listener: (token: string) => void): void { - this.tokenListeners_ = this.tokenListeners_.filter((other) => other !== listener); - } - - /** - * Deletes the FirebaseAppInternals instance. - */ - public delete(): void { - this.isDeleted_ = true; - - // Clear the token refresh timeout so it doesn't keep the Node.js process alive. - clearTimeout(this.tokenRefreshTimeout_); - } - - /** - * Establishes timeout to refresh the Google OAuth2 access token used by the SDK. - * - * @param {number} delayInMilliseconds The delay to use for the timeout. - * @param {number} numRetries The number of times to retry fetching a new token if the prior fetch - * failed. - */ - private setTokenRefreshTimeout(delayInMilliseconds: number, numRetries: number): void { - this.tokenRefreshTimeout_ = setTimeout(() => { - this.getToken(/* forceRefresh */ true) - .catch(() => { - // Ignore the error since this might just be an intermittent failure. If we really cannot - // refresh the token, an error will be logged once the existing token expires and we try - // to fetch a fresh one. - if (numRetries > 0) { - this.setTokenRefreshTimeout(60 * 1000, numRetries - 1); - } - }); - }, delayInMilliseconds); - } -} diff --git a/src/firebase-app.ts b/src/firebase-app.ts index 373216aafe..364b4f5b26 100644 --- a/src/firebase-app.ts +++ b/src/firebase-app.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2020 Google Inc. + * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getApplicationDefault } from './auth/credential'; +import { Credential, GoogleOAuthAccessToken, getApplicationDefault } from './auth/credential'; import * as validator from './utils/validator'; import { deepCopy, deepExtend } from './utils/deep-copy'; import { FirebaseServiceInterface } from './firebase-service'; @@ -35,7 +35,211 @@ import { ProjectManagement } from './project-management/project-management'; import { SecurityRules } from './security-rules/security-rules'; import { RemoteConfig } from './remote-config/remote-config'; -import { FirebaseAppInternals, FirebaseAppOptions } from './firebase-app-internal'; +import { Agent } from 'http'; + +/** + * Type representing a callback which is called every time an app lifecycle event occurs. + */ +export type AppHook = (event: string, app: FirebaseApp) => void; + +/** + * Type representing the options object passed into initializeApp(). + */ +export interface FirebaseAppOptions { + credential?: Credential; + databaseAuthVariableOverride?: object | null; + databaseURL?: string; + serviceAccountId?: string; + storageBucket?: string; + projectId?: string; + httpAgent?: Agent; +} + +/** + * Type representing a Firebase OAuth access token (derived from a Google OAuth2 access token) which + * can be used to authenticate to Firebase services such as the Realtime Database and Auth. + */ +export interface FirebaseAccessToken { + accessToken: string; + expirationTime: number; +} + +/** + * Internals of a FirebaseApp instance. + */ +export class FirebaseAppInternals { + private isDeleted_ = false; + private cachedToken_: FirebaseAccessToken; + private cachedTokenPromise_: Promise | null; + private tokenListeners_: Array<(token: string) => void>; + private tokenRefreshTimeout_: NodeJS.Timer; + + constructor(private credential_: Credential) { + this.tokenListeners_ = []; + } + + /** + * Gets an auth token for the associated app. + * + * @param {boolean} forceRefresh Whether or not to force a token refresh. + * @return {Promise} A Promise that will be fulfilled with the current or + * new token. + */ + public getToken(forceRefresh?: boolean): Promise { + const expired = this.cachedToken_ && this.cachedToken_.expirationTime < Date.now(); + if (this.cachedTokenPromise_ && !forceRefresh && !expired) { + return this.cachedTokenPromise_ + .catch((error) => { + // Update the cached token promise to avoid caching errors. Set it to resolve with the + // cached token if we have one (and return that promise since the token has still not + // expired). + if (this.cachedToken_) { + this.cachedTokenPromise_ = Promise.resolve(this.cachedToken_); + return this.cachedTokenPromise_; + } + + // Otherwise, set the cached token promise to null so that it will force a refresh next + // time getToken() is called. + this.cachedTokenPromise_ = null; + + // And re-throw the caught error. + throw error; + }); + } else { + // Clear the outstanding token refresh timeout. This is a noop if the timeout is undefined. + clearTimeout(this.tokenRefreshTimeout_); + + // this.credential_ may be an external class; resolving it in a promise helps us + // protect against exceptions and upgrades the result to a promise in all cases. + this.cachedTokenPromise_ = Promise.resolve(this.credential_.getAccessToken()) + .then((result: GoogleOAuthAccessToken) => { + // Since the developer can provide the credential implementation, we want to weakly verify + // the return type until the type is properly exported. + if (!validator.isNonNullObject(result) || + typeof result.expires_in !== 'number' || + typeof result.access_token !== 'string') { + throw new FirebaseAppError( + AppErrorCodes.INVALID_CREDENTIAL, + `Invalid access token generated: "${JSON.stringify(result)}". Valid access ` + + 'tokens must be an object with the "expires_in" (number) and "access_token" ' + + '(string) properties.', + ); + } + + const token: FirebaseAccessToken = { + accessToken: result.access_token, + expirationTime: Date.now() + (result.expires_in * 1000), + }; + + const hasAccessTokenChanged = (this.cachedToken_ && this.cachedToken_.accessToken !== token.accessToken); + const hasExpirationChanged = (this.cachedToken_ && this.cachedToken_.expirationTime !== token.expirationTime); + if (!this.cachedToken_ || hasAccessTokenChanged || hasExpirationChanged) { + this.cachedToken_ = token; + this.tokenListeners_.forEach((listener) => { + listener(token.accessToken); + }); + } + + // Establish a timeout to proactively refresh the token every minute starting at five + // minutes before it expires. Once a token refresh succeeds, no further retries are + // needed; if it fails, retry every minute until the token expires (resulting in a total + // of four retries: at 4, 3, 2, and 1 minutes). + let refreshTimeInSeconds = (result.expires_in - (5 * 60)); + let numRetries = 4; + + // In the rare cases the token is short-lived (that is, it expires in less than five + // minutes from when it was fetched), establish the timeout to refresh it after the + // current minute ends and update the number of retries that should be attempted before + // the token expires. + if (refreshTimeInSeconds <= 0) { + refreshTimeInSeconds = result.expires_in % 60; + numRetries = Math.floor(result.expires_in / 60) - 1; + } + + // The token refresh timeout keeps the Node.js process alive, so only create it if this + // instance has not already been deleted. + if (numRetries && !this.isDeleted_) { + this.setTokenRefreshTimeout(refreshTimeInSeconds * 1000, numRetries); + } + + return token; + }) + .catch((error) => { + let errorMessage = (typeof error === 'string') ? error : error.message; + + errorMessage = 'Credential implementation provided to initializeApp() via the ' + + '"credential" property failed to fetch a valid Google OAuth2 access token with the ' + + `following error: "${errorMessage}".`; + + if (errorMessage.indexOf('invalid_grant') !== -1) { + errorMessage += ' There are two likely causes: (1) your server time is not properly ' + + 'synced or (2) your certificate key file has been revoked. To solve (1), re-sync the ' + + 'time on your server. To solve (2), make sure the key ID for your key file is still ' + + 'present at https://console.firebase.google.com/iam-admin/serviceaccounts/project. If ' + + 'not, generate a new key file at ' + + 'https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk.'; + } + + throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage); + }); + + return this.cachedTokenPromise_; + } + } + + /** + * Adds a listener that is called each time a token changes. + * + * @param {function(string)} listener The listener that will be called with each new token. + */ + public addAuthTokenListener(listener: (token: string) => void): void { + this.tokenListeners_.push(listener); + if (this.cachedToken_) { + listener(this.cachedToken_.accessToken); + } + } + + /** + * Removes a token listener. + * + * @param {function(string)} listener The listener to remove. + */ + public removeAuthTokenListener(listener: (token: string) => void): void { + this.tokenListeners_ = this.tokenListeners_.filter((other) => other !== listener); + } + + /** + * Deletes the FirebaseAppInternals instance. + */ + public delete(): void { + this.isDeleted_ = true; + + // Clear the token refresh timeout so it doesn't keep the Node.js process alive. + clearTimeout(this.tokenRefreshTimeout_); + } + + /** + * Establishes timeout to refresh the Google OAuth2 access token used by the SDK. + * + * @param {number} delayInMilliseconds The delay to use for the timeout. + * @param {number} numRetries The number of times to retry fetching a new token if the prior fetch + * failed. + */ + private setTokenRefreshTimeout(delayInMilliseconds: number, numRetries: number): void { + this.tokenRefreshTimeout_ = setTimeout(() => { + this.getToken(/* forceRefresh */ true) + .catch(() => { + // Ignore the error since this might just be an intermittent failure. If we really cannot + // refresh the token, an error will be logged once the existing token expires and we try + // to fetch a fresh one. + if (numRetries > 0) { + this.setTokenRefreshTimeout(60 * 1000, numRetries - 1); + } + }); + }, delayInMilliseconds); + } +} + /** diff --git a/src/firebase-namespace.ts b/src/firebase-namespace.ts index d4c81ca668..568d1299d8 100644 --- a/src/firebase-namespace.ts +++ b/src/firebase-namespace.ts @@ -18,8 +18,7 @@ import fs = require('fs'); import { Agent } from 'http'; import { deepExtend } from './utils/deep-copy'; import { AppErrorCodes, FirebaseAppError } from './utils/error'; -import { FirebaseApp } from './firebase-app'; -import { AppHook, FirebaseAppOptions } from './firebase-app-internal'; +import { FirebaseApp, AppHook, FirebaseAppOptions } from './firebase-app'; import { FirebaseServiceFactory, FirebaseServiceInterface } from './firebase-service'; import { Credential, diff --git a/src/utils/index.ts b/src/utils/index.ts index 53911b75e6..8c3a540dc0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; -import { FirebaseAppOptions } from '../firebase-app-internal'; +import { FirebaseApp, FirebaseAppOptions } from '../firebase-app'; import { ServiceAccountCredential, ComputeEngineCredential } from '../auth/credential'; import * as validator from './validator'; diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index 2515a9c58d..cb2be41a8f 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -26,8 +26,7 @@ import * as jwt from 'jsonwebtoken'; import { FirebaseNamespace } from '../../src/firebase-namespace'; import { FirebaseServiceInterface } from '../../src/firebase-service'; -import { FirebaseApp } from '../../src/firebase-app'; -import { FirebaseAppOptions } from '../../src/firebase-app-internal'; +import { FirebaseApp, FirebaseAppOptions } from '../../src/firebase-app'; import { Credential, GoogleOAuthAccessToken, ServiceAccountCredential } from '../../src/auth/credential'; const ALGORITHM = 'RS256'; diff --git a/test/unit/firebase-app.spec.ts b/test/unit/firebase-app.spec.ts index de76ba2bf6..dc89b6383c 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/firebase-app.spec.ts @@ -27,8 +27,7 @@ import * as mocks from '../resources/mocks'; import { GoogleOAuthAccessToken, ServiceAccountCredential } from '../../src/auth/credential'; import { FirebaseServiceInterface } from '../../src/firebase-service'; -import { FirebaseApp } from '../../src/firebase-app'; -import { FirebaseAccessToken } from '../../src/firebase-app-internal'; +import { FirebaseApp, FirebaseAccessToken } from '../../src/firebase-app'; import { FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR } from '../../src/firebase-namespace'; import { Auth } from '../../src/auth/auth'; diff --git a/test/unit/utils.ts b/test/unit/utils.ts index a3f0fd1719..fa655dd4e8 100644 --- a/test/unit/utils.ts +++ b/test/unit/utils.ts @@ -20,8 +20,7 @@ import * as sinon from 'sinon'; import * as mocks from '../resources/mocks'; import { FirebaseNamespace } from '../../src/firebase-namespace'; -import { FirebaseApp } from '../../src/firebase-app'; -import { FirebaseAppOptions, FirebaseAppInternals, FirebaseAccessToken } from '../../src/firebase-app-internal'; +import { FirebaseApp, FirebaseAppOptions, FirebaseAppInternals, FirebaseAccessToken } from '../../src/firebase-app'; import { HttpError, HttpResponse } from '../../src/utils/api-request'; /** diff --git a/test/unit/utils/index.spec.ts b/test/unit/utils/index.spec.ts index 226b56fc55..acb76f074a 100644 --- a/test/unit/utils/index.spec.ts +++ b/test/unit/utils/index.spec.ts @@ -24,8 +24,7 @@ import { toWebSafeBase64, formatString, generateUpdateMask, } from '../../../src/utils/index'; import { isNonEmptyString } from '../../../src/utils/validator'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { FirebaseAppOptions } from '../../../src/firebase-app-internal'; +import { FirebaseApp, FirebaseAppOptions } from '../../../src/firebase-app'; import { ComputeEngineCredential } from '../../../src/auth/credential'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; From 52c626896767a2f24778be271073be1f41f96f00 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 21:03:33 -0400 Subject: [PATCH 19/46] Fix broken tests --- src/database/database.ts | 1 + src/firebase-namespace.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/database.ts b/src/database/database.ts index 37db18627e..2e8cd598ea 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import '@firebase/database'; declare module '@firebase/database' { interface Database { diff --git a/src/firebase-namespace.ts b/src/firebase-namespace.ts index 568d1299d8..916132ebbf 100644 --- a/src/firebase-namespace.ts +++ b/src/firebase-namespace.ts @@ -18,7 +18,7 @@ import fs = require('fs'); import { Agent } from 'http'; import { deepExtend } from './utils/deep-copy'; import { AppErrorCodes, FirebaseAppError } from './utils/error'; -import { FirebaseApp, AppHook, FirebaseAppOptions } from './firebase-app'; +import { AppHook, FirebaseApp, FirebaseAppOptions } from './firebase-app'; import { FirebaseServiceFactory, FirebaseServiceInterface } from './firebase-service'; import { Credential, From fe96c44e71f2183990fd40da1e33f09e20dbbc72 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 23 Jul 2020 21:28:48 -0400 Subject: [PATCH 20/46] Make database have an optional app parameter --- src/database/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/database/index.ts b/src/database/index.ts index 883f2178ec..0c1a5bcac6 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -15,10 +15,14 @@ */ import { FirebaseApp } from '../firebase-app'; +import * as admin from '../index'; import * as firebaseRtdbApi from '@firebase/database'; import * as firebaseRtdbTypesApi from '@firebase/database-types'; -export function database(app: FirebaseApp): firebaseRtdbApi.Database { +export function database(app?: FirebaseApp): firebaseRtdbApi.Database { + if (typeof(app) === 'undefined') { + app = admin.app(); + } return app.database(); } From 41d3c46adf591787e03c20eee0b6c330241a841a Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 24 Jul 2020 10:26:31 -0400 Subject: [PATCH 21/46] Add working hacky fix --- gulpfile.js | 6 ++++-- package.json | 1 + src/database/database.ts | 30 ++++++++++++++++++++++++++++++ src/database/index.ts | 21 ++++++++++++--------- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 29c20e720f..0f0510c627 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -111,8 +111,10 @@ gulp.task('compile_autogen_typing', function() { // Compile Typescript into .js and .d.ts files .pipe(buildAutogenTyping()) - // Exclude typings that are unintended (only RTDB is expected to produce - // typings so far). + // Exclude typings that are unintended (only database is expected to + // produce typings so far). Moreover, all *-internal.d.ts typings + // should not be exposed to developers as it denotes internally used + // types. .pipe(filter([ 'lib/**/*.js', 'lib/**/*.d.ts', diff --git a/package.json b/package.json index ba6f1f5089..03befe5316 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "types": "./lib/index.d.ts", "dependencies": { "@firebase/database": "^0.6.0", + "@firebase/database-types": "^0.5.1", "@types/node": "^10.10.0", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", diff --git a/src/database/database.ts b/src/database/database.ts index 2e8cd598ea..b0a395a8c7 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -43,3 +43,33 @@ declare module '@firebase/database' { setRules(source: string | Buffer | object): Promise; } } + + +declare module '@firebase/database-types' { + interface FirebaseDatabase { + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @return A promise fulfilled with the rules as a raw string. + */ + getRules(): Promise; + + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @return A promise fulfilled with the parsed rules object. + */ + getRulesJSON(): Promise; + + /** + * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param source Source of the rules to apply. Must not be `null` or empty. + * @return Resolves when the rules are set on the Realtime Database. + */ + setRules(source: string | Buffer | object): Promise; + } +} diff --git a/src/database/index.ts b/src/database/index.ts index 0c1a5bcac6..d0ed79e2f3 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -15,25 +15,28 @@ */ import { FirebaseApp } from '../firebase-app'; -import * as admin from '../index'; -import * as firebaseRtdbApi from '@firebase/database'; +import * as firebaseAdmin from '../index'; import * as firebaseRtdbTypesApi from '@firebase/database-types'; -export function database(app?: FirebaseApp): firebaseRtdbApi.Database { +export function database(app?: FirebaseApp): firebaseRtdbTypesApi.FirebaseDatabase { if (typeof(app) === 'undefined') { - app = admin.app(); + app = firebaseAdmin.app(); } return app.database(); } -// We must define a namespace to make the typings work correctly. -// Otherwise `admin.database()` cannot be called like a function. +/** + * We must define a namespace to make the typings work correctly. Otherwise + * `admin.database()` cannot be called like a function. Temporarily, + * admin.database is used as the namespace name because we cannot barrel + * re-export the contents from @firebase/database-types. + */ /* eslint-disable @typescript-eslint/no-namespace */ -export namespace database { +export namespace admin.database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ - // For context: github.com/typescript-eslint/typescript-eslint/issues/363 - export import Database = firebaseRtdbApi.Database; + // See https://github.com/typescript-eslint/typescript-eslint/issues/363 + export import Database = firebaseRtdbTypesApi.FirebaseDatabase; export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; export import OnDisconnect = firebaseRtdbTypesApi.OnDisconnect; export import EventType = firebaseRtdbTypesApi.EventType; From 70f687a0c4ec83075e19ab628c5fccabc083e73b Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 24 Jul 2020 12:09:41 -0400 Subject: [PATCH 22/46] Set the return type to be Database --- src/database/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/index.ts b/src/database/index.ts index d0ed79e2f3..62a1f1278f 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -16,9 +16,10 @@ import { FirebaseApp } from '../firebase-app'; import * as firebaseAdmin from '../index'; +import * as firebaseRtdbApi from '@firebase/database'; import * as firebaseRtdbTypesApi from '@firebase/database-types'; -export function database(app?: FirebaseApp): firebaseRtdbTypesApi.FirebaseDatabase { +export function database(app?: FirebaseApp): firebaseRtdbApi.Database { if (typeof(app) === 'undefined') { app = firebaseAdmin.app(); } From 9fce5a64532f5ff68d6e8bfb1d3233b8de445d7e Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 24 Jul 2020 13:52:17 -0400 Subject: [PATCH 23/46] Add comment relating to the issue flagged for JS SDK --- src/database/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/database/index.ts b/src/database/index.ts index 62a1f1278f..7761c7e45d 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -30,14 +30,13 @@ export function database(app?: FirebaseApp): firebaseRtdbApi.Database { * We must define a namespace to make the typings work correctly. Otherwise * `admin.database()` cannot be called like a function. Temporarily, * admin.database is used as the namespace name because we cannot barrel - * re-export the contents from @firebase/database-types. + * re-export the contents from @firebase/database-types. */ /* eslint-disable @typescript-eslint/no-namespace */ export namespace admin.database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ // See https://github.com/typescript-eslint/typescript-eslint/issues/363 - export import Database = firebaseRtdbTypesApi.FirebaseDatabase; export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; export import OnDisconnect = firebaseRtdbTypesApi.OnDisconnect; export import EventType = firebaseRtdbTypesApi.EventType; @@ -46,4 +45,8 @@ export namespace admin.database { export import ThenableReference = firebaseRtdbTypesApi.ThenableReference; export import enableLogging = firebaseRtdbTypesApi.enableLogging; export import ServerValue = firebaseRtdbTypesApi.ServerValue; + // There is a known bug where @firebase/database-types FirebaseDatabase + // cannot be used as an interface for @firebase/database Database. + // See https://github.com/firebase/firebase-js-sdk/issues/3476 + export import Database = firebaseRtdbTypesApi.FirebaseDatabase; } From c1a9fceba7cfd797c5cc3a9fd44d43ac141e10a0 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 24 Jul 2020 16:12:32 -0400 Subject: [PATCH 24/46] Address review comments --- gulpfile.js | 7 +++---- package.json | 1 - src/database/database.ts | 38 ++++++++++---------------------------- src/database/index.ts | 23 ++++++++++++----------- 4 files changed, 25 insertions(+), 44 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 0f0510c627..3339f215c9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -103,7 +103,7 @@ gulp.task('compile_test', function() { * the project generating both *.js and *.d.ts files. After, it removes * declarations for all files terminating in -internal.d.ts because we do not * want to expose internally exposed types to developers. As auto-generated - * typings are a work-in-progress, we remove the *.d.ts files for classes + * typings are a work-in-progress, we remove the *.d.ts files for modules * which we do not intend to auto-generate typings for yet. */ gulp.task('compile_autogen_typing', function() { @@ -119,7 +119,6 @@ gulp.task('compile_autogen_typing', function() { 'lib/**/*.js', 'lib/**/*.d.ts', '!lib/*-internal.d.ts', - '!lib/index.d.ts', '!lib/default-namespace.d.ts', '!lib/firebase-namespace.d.ts', '!lib/firebase-app.d.ts', @@ -158,7 +157,7 @@ gulp.task('copyTypings', function() { .pipe(gulp.dest(paths.build)) }); -gulp.task('removeManuallyCuratedTypings', function() { +gulp.task('removeCuratedTypings', function() { return del(paths.curatedTypings); }); @@ -173,7 +172,7 @@ gulp.task('watch', function() { gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings')); // Build typings -gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeManuallyCuratedTypings')); +gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeCuratedTypings')); // Default task gulp.task('default', gulp.series('build')); diff --git a/package.json b/package.json index 03befe5316..ba6f1f5089 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "types": "./lib/index.d.ts", "dependencies": { "@firebase/database": "^0.6.0", - "@firebase/database-types": "^0.5.1", "@types/node": "^10.10.0", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", diff --git a/src/database/database.ts b/src/database/database.ts index b0a395a8c7..2f63a75d11 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Reference } from '@firebase/database'; import '@firebase/database'; declare module '@firebase/database' { @@ -44,32 +45,13 @@ declare module '@firebase/database' { } } +// EventType and ThenableReference are temporarily defined here because they're +// not exposed in @firebase/database. +// See: https://github.com/firebase/firebase-js-sdk/issues/3479 +export type EventType = 'value' | 'child_added' | 'child_changed' + | 'child_moved' | 'child_removed'; -declare module '@firebase/database-types' { - interface FirebaseDatabase { - /** - * Gets the currently applied security rules as a string. The return value consists of - * the rules source including comments. - * - * @return A promise fulfilled with the rules as a raw string. - */ - getRules(): Promise; - - /** - * Gets the currently applied security rules as a parsed JSON object. Any comments in - * the original source are stripped away. - * - * @return A promise fulfilled with the parsed rules object. - */ - getRulesJSON(): Promise; - - /** - * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is - * specified as a string or a Buffer, it may include comments. - * - * @param source Source of the rules to apply. Must not be `null` or empty. - * @return Resolves when the rules are set on the Realtime Database. - */ - setRules(source: string | Buffer | object): Promise; - } -} +/** + * @extends {Reference} + */ +export interface ThenableReference extends Reference, Promise { } diff --git a/src/database/index.ts b/src/database/index.ts index 7761c7e45d..00ba33bfe9 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -16,8 +16,8 @@ import { FirebaseApp } from '../firebase-app'; import * as firebaseAdmin from '../index'; +import * as adminRtdbApi from './database'; import * as firebaseRtdbApi from '@firebase/database'; -import * as firebaseRtdbTypesApi from '@firebase/database-types'; export function database(app?: FirebaseApp): firebaseRtdbApi.Database { if (typeof(app) === 'undefined') { @@ -30,23 +30,24 @@ export function database(app?: FirebaseApp): firebaseRtdbApi.Database { * We must define a namespace to make the typings work correctly. Otherwise * `admin.database()` cannot be called like a function. Temporarily, * admin.database is used as the namespace name because we cannot barrel - * re-export the contents from @firebase/database-types. + * re-export the contents from @firebase/database-types, and we want it to + * match the namespacing in the re-export inside src/index.d.ts */ /* eslint-disable @typescript-eslint/no-namespace */ export namespace admin.database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ // See https://github.com/typescript-eslint/typescript-eslint/issues/363 - export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; - export import OnDisconnect = firebaseRtdbTypesApi.OnDisconnect; - export import EventType = firebaseRtdbTypesApi.EventType; - export import Query = firebaseRtdbTypesApi.Query; - export import Reference = firebaseRtdbTypesApi.Reference; - export import ThenableReference = firebaseRtdbTypesApi.ThenableReference; - export import enableLogging = firebaseRtdbTypesApi.enableLogging; - export import ServerValue = firebaseRtdbTypesApi.ServerValue; + export import DataSnapshot = firebaseRtdbApi.DataSnapshot; + export import OnDisconnect = firebaseRtdbApi.OnDisconnect; + export import EventType = adminRtdbApi.EventType; + export import Query = firebaseRtdbApi.Query; + export import Reference = firebaseRtdbApi.Reference; + export import ThenableReference = adminRtdbApi.ThenableReference; + export import enableLogging = firebaseRtdbApi.enableLogging; + export import ServerValue = firebaseRtdbApi.ServerValue; // There is a known bug where @firebase/database-types FirebaseDatabase // cannot be used as an interface for @firebase/database Database. // See https://github.com/firebase/firebase-js-sdk/issues/3476 - export import Database = firebaseRtdbTypesApi.FirebaseDatabase; + export import Database = firebaseRtdbApi.Database; } From 23456540e2678f2491d6f55cc0200ad1143d9cbb Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 24 Jul 2020 16:16:51 -0400 Subject: [PATCH 25/46] Remove redundent import --- src/database/database.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/database/database.ts b/src/database/database.ts index 2f63a75d11..a0a4da973d 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -14,7 +14,6 @@ * limitations under the License. */ import { Reference } from '@firebase/database'; -import '@firebase/database'; declare module '@firebase/database' { interface Database { From 391ee40805b566252c4f8fb6e0014cc4c4cb4446 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 24 Jul 2020 17:33:15 -0400 Subject: [PATCH 26/46] Add environment variable based override for builds --- gulpfile.js | 14 ++++++++++---- package.json | 2 -- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 3339f215c9..f6017a3009 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -168,11 +168,17 @@ gulp.task('watch', function() { gulp.watch(paths.src.concat(paths.test), { ignoreInitial: false }, gulp.series('compile_all')); }); -// Build task -gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings')); +// If the environment variable TYPE_GENERATION_MODE is set to AUTO then the +// typings are automatically generated for services that support it. +let buildSeries; +if (process.env.TYPE_GENERATION_MODE == 'AUTO') { + buildSeries = gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeCuratedTypings'); +} else { + buildSeries = gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings'); +} -// Build typings -gulp.task('build_typings', gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeCuratedTypings')); +// Build task +gulp.task('build', buildSeries); // Default task gulp.task('default', gulp.series('build')); diff --git a/package.json b/package.json index ba6f1f5089..3207fc710c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "scripts": { "build": "gulp build", "build:tests": "gulp compile_test", - "build:typing": "gulp build_typings", "prepare": "npm run build", "lint": "run-p lint:src lint:test", "test": "run-s lint test:unit", @@ -19,7 +18,6 @@ "test:unit": "mocha test/unit/*.spec.ts --require ts-node/register", "test:integration": "mocha test/integration/*.ts --slow 5000 --timeout 20000 --require ts-node/register", "test:coverage": "nyc npm run test:unit", - "test:typing": "run-s build:typing test:unit test:integration", "lint:src": "eslint src/ --ext .ts", "lint:test": "eslint test/ --ext .ts", "apidocs": "node docgen/generate-docs.js --api node" From 8c7301e1923a3323b3fa328fd41581bb5793b346 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 24 Jul 2020 17:38:38 -0400 Subject: [PATCH 27/46] Do not caps lock auto --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index f6017a3009..f8598783f2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -171,7 +171,7 @@ gulp.task('watch', function() { // If the environment variable TYPE_GENERATION_MODE is set to AUTO then the // typings are automatically generated for services that support it. let buildSeries; -if (process.env.TYPE_GENERATION_MODE == 'AUTO') { +if (process.env.TYPE_GENERATION_MODE == 'auto') { buildSeries = gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeCuratedTypings'); } else { buildSeries = gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings'); From 9a984628eed483c8c37721d7d617843b8bbc986f Mon Sep 17 00:00:00 2001 From: MathBunny Date: Mon, 27 Jul 2020 09:36:37 -0400 Subject: [PATCH 28/46] Use lowercase auto --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index f8598783f2..c09159172b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -168,7 +168,7 @@ gulp.task('watch', function() { gulp.watch(paths.src.concat(paths.test), { ignoreInitial: false }, gulp.series('compile_all')); }); -// If the environment variable TYPE_GENERATION_MODE is set to AUTO then the +// If the environment variable TYPE_GENERATION_MODE is set to auto then the // typings are automatically generated for services that support it. let buildSeries; if (process.env.TYPE_GENERATION_MODE == 'auto') { From 7abc87644d2385265bbea6a1ed5dea9704ed965b Mon Sep 17 00:00:00 2001 From: MathBunny Date: Mon, 27 Jul 2020 17:28:02 -0400 Subject: [PATCH 29/46] Address gulpfile comments --- gulpfile.js | 99 ++++++++++++++++++++++------------------------------- 1 file changed, 41 insertions(+), 58 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index c09159172b..9bd07ece1f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -52,18 +52,17 @@ var paths = { build: 'lib/', - curatedTypings: ['lib/database.d.ts'], + curatedTypings: ['src/**/*.d.ts', '!src/database.d.ts'], }; // Create a separate project for buildProject that overrides the rootDir // This ensures that the generated production files are in their own root // rather than including both src and test in the lib dir. -var buildProject = ts.createProject('tsconfig.json', { rootDir: 'src' }); +const declaration = process.env.TYPE_GENERATION_MODE === 'auto'; +var buildProject = ts.createProject('tsconfig.json', { rootDir: 'src', declaration }); var buildTest = ts.createProject('tsconfig.json'); -var buildAutogenTyping = ts.createProject('tsconfig.json', { rootDir: 'src', declaration: true }); - var banner = `/*! firebase-admin v${pkg.version} */\n`; /***********/ @@ -76,49 +75,31 @@ gulp.task('cleanup', function() { ]); }); -gulp.task('compile', function() { - return gulp.src(paths.src) - // Compile Typescript into .js and .d.ts files - .pipe(buildProject()) - - // Add header - .pipe(header(banner)) - - // Write to build directory - .pipe(gulp.dest(paths.build)) -}); - -/** - * Task only used to capture typescript compilation errors in the test suite. - * Output is discarded. - */ -gulp.task('compile_test', function() { - return gulp.src(paths.test) - // Compile Typescript into .js and .d.ts files - .pipe(buildTest()) -}); - /** - * Task used to verify TypeScript auto-generated typings. First, it builds - * the project generating both *.js and *.d.ts files. After, it removes - * declarations for all files terminating in -internal.d.ts because we do not - * want to expose internally exposed types to developers. As auto-generated + * Task used to compile the TypeScript project. If automatic typings + * are set to be generated (determined by TYPE_GENERATION_MODE), declarations + * for files terminating in -internal.d.ts are removed because we do not + * want to expose internally used types to developers. As auto-generated * typings are a work-in-progress, we remove the *.d.ts files for modules * which we do not intend to auto-generate typings for yet. */ -gulp.task('compile_autogen_typing', function() { - return gulp.src(paths.src) +gulp.task('compile', function() { + let workflow = gulp.src(paths.src) // Compile Typescript into .js and .d.ts files - .pipe(buildAutogenTyping()) + .pipe(buildProject()) - // Exclude typings that are unintended (only database is expected to - // produce typings so far). Moreover, all *-internal.d.ts typings - // should not be exposed to developers as it denotes internally used - // types. - .pipe(filter([ + // Add header + .pipe(header(banner)); + + // Exclude typings that are unintended (only database is expected to + // produce typings so far). Moreover, all *-internal.d.ts typings + // should not be exposed to developers as it denotes internally used + // types. + if (declaration) { + workflow = workflow.pipe(filter([ 'lib/**/*.js', 'lib/**/*.d.ts', - '!lib/*-internal.d.ts', + '!lib/**/*-internal.d.ts', '!lib/default-namespace.d.ts', '!lib/firebase-namespace.d.ts', '!lib/firebase-app.d.ts', @@ -133,9 +114,20 @@ gulp.task('compile_autogen_typing', function() { '!lib/security-rules/*.d.ts', '!lib/storage/*.d.ts', '!lib/utils/*.d.ts'])) + } - // Write to build directory - .pipe(gulp.dest(paths.build)) + // Write to build directory + return workflow.pipe(gulp.dest(paths.build)) +}); + +/** + * Task only used to capture typescript compilation errors in the test suite. + * Output is discarded. + */ +gulp.task('compile_test', function() { + return gulp.src(paths.test) + // Compile Typescript into .js and .d.ts files + .pipe(buildTest()) }); gulp.task('copyDatabase', function() { @@ -149,16 +141,16 @@ gulp.task('copyDatabase', function() { }); gulp.task('copyTypings', function() { - return gulp.src('src/*.d.ts') + let workflow = gulp.src('src/*.d.ts') // Add header - .pipe(header(banner)) + .pipe(header(banner)); - // Write to build directory - .pipe(gulp.dest(paths.build)) -}); + if (declaration) { + workflow = workflow.pipe(filter(paths.curatedTypings)) + } -gulp.task('removeCuratedTypings', function() { - return del(paths.curatedTypings); + // Write to build directory + return workflow.pipe(gulp.dest(paths.build)) }); gulp.task('compile_all', gulp.series('compile', 'copyDatabase', 'copyTypings', 'compile_test')); @@ -168,17 +160,8 @@ gulp.task('watch', function() { gulp.watch(paths.src.concat(paths.test), { ignoreInitial: false }, gulp.series('compile_all')); }); -// If the environment variable TYPE_GENERATION_MODE is set to auto then the -// typings are automatically generated for services that support it. -let buildSeries; -if (process.env.TYPE_GENERATION_MODE == 'auto') { - buildSeries = gulp.series('cleanup', 'compile_autogen_typing', 'copyTypings', 'removeCuratedTypings'); -} else { - buildSeries = gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings'); -} - // Build task -gulp.task('build', buildSeries); +gulp.task('build', gulp.series('cleanup', 'compile', 'copyDatabase', 'copyTypings')); // Default task gulp.task('default', gulp.series('build')); From 09434f2e42f9e38d2bc854cec0340993b7321e37 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 28 Jul 2020 14:42:20 -0400 Subject: [PATCH 30/46] Use database-types instead of database --- package.json | 1 + src/database/database-internal.ts | 9 +++++---- src/database/database.ts | 4 ++-- src/database/index.ts | 24 ++++++++++++------------ src/firebase-app.ts | 4 ++-- src/firebase-namespace.ts | 6 +++--- test/unit/firebase-app.spec.ts | 18 +++++++++--------- 7 files changed, 34 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 3207fc710c..d1321d287c 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "types": "./lib/index.d.ts", "dependencies": { "@firebase/database": "^0.6.0", + "@firebase/database-types": "^0.5.1", "@types/node": "^10.10.0", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts index 1939e7b6a4..414a7eee99 100644 --- a/src/database/database-internal.ts +++ b/src/database/database-internal.ts @@ -21,6 +21,7 @@ import { FirebaseApp } from '../firebase-app'; import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { Database } from '@firebase/database'; +import { FirebaseDatabase } from '@firebase/database-types'; import './database'; import * as validator from '../utils/validator'; @@ -34,7 +35,7 @@ import { getSdkVersion } from '../utils/index'; class DatabaseInternals implements FirebaseServiceInternalsInterface { public databases: { - [dbUrl: string]: Database; + [dbUrl: string]: FirebaseDatabase; } = {}; /** @@ -44,7 +45,7 @@ class DatabaseInternals implements FirebaseServiceInternalsInterface { */ public delete(): Promise { for (const dbUrl of Object.keys(this.databases)) { - const db: Database = this.databases[dbUrl]; + const db: Database = ((this.databases[dbUrl] as any) as Database); db.INTERNAL.delete(); } return Promise.resolve(undefined); @@ -76,7 +77,7 @@ export class DatabaseService implements FirebaseServiceInterface { return this.appInternal; } - public getDatabase(url?: string): Database { + public getDatabase(url?: string): FirebaseDatabase { const dbUrl: string = this.ensureUrl(url); if (!validator.isNonEmptyString(dbUrl)) { throw new FirebaseDatabaseError({ @@ -85,7 +86,7 @@ export class DatabaseService implements FirebaseServiceInterface { }); } - let db: Database = this.INTERNAL.databases[dbUrl]; + let db: FirebaseDatabase = this.INTERNAL.databases[dbUrl]; if (typeof db === 'undefined') { const rtdb = require('@firebase/database'); // eslint-disable-line @typescript-eslint/no-var-requires db = rtdb.initStandalone(this.appInternal, dbUrl, getSdkVersion()).instance; diff --git a/src/database/database.ts b/src/database/database.ts index a0a4da973d..4b37fa0d04 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -15,8 +15,8 @@ */ import { Reference } from '@firebase/database'; -declare module '@firebase/database' { - interface Database { +declare module '@firebase/database-types' { + interface FirebaseDatabase { /** * Gets the currently applied security rules as a string. The return value consists of * the rules source including comments. diff --git a/src/database/index.ts b/src/database/index.ts index 00ba33bfe9..e29d3f56d7 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -15,11 +15,11 @@ */ import { FirebaseApp } from '../firebase-app'; +import { ServerValue as sv } from '@firebase/database'; +import * as firebaseRtdbTypesApi from '@firebase/database-types'; import * as firebaseAdmin from '../index'; -import * as adminRtdbApi from './database'; -import * as firebaseRtdbApi from '@firebase/database'; -export function database(app?: FirebaseApp): firebaseRtdbApi.Database { +export function database(app?: FirebaseApp): firebaseRtdbTypesApi.FirebaseDatabase { if (typeof(app) === 'undefined') { app = firebaseAdmin.app(); } @@ -38,16 +38,16 @@ export namespace admin.database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ // See https://github.com/typescript-eslint/typescript-eslint/issues/363 - export import DataSnapshot = firebaseRtdbApi.DataSnapshot; - export import OnDisconnect = firebaseRtdbApi.OnDisconnect; - export import EventType = adminRtdbApi.EventType; - export import Query = firebaseRtdbApi.Query; - export import Reference = firebaseRtdbApi.Reference; - export import ThenableReference = adminRtdbApi.ThenableReference; - export import enableLogging = firebaseRtdbApi.enableLogging; - export import ServerValue = firebaseRtdbApi.ServerValue; + export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; + export import OnDisconnect = firebaseRtdbTypesApi.OnDisconnect; + export import EventType = firebaseRtdbTypesApi.EventType; + export import Query = firebaseRtdbTypesApi.Query; + export import Reference = firebaseRtdbTypesApi.Reference; + export import ThenableReference = firebaseRtdbTypesApi.ThenableReference; + export import enableLogging = firebaseRtdbTypesApi.enableLogging; + export const ServerValue: firebaseRtdbTypesApi.ServerValue = sv; // There is a known bug where @firebase/database-types FirebaseDatabase // cannot be used as an interface for @firebase/database Database. // See https://github.com/firebase/firebase-js-sdk/issues/3476 - export import Database = firebaseRtdbApi.Database; + export import Database = firebaseRtdbTypesApi.FirebaseDatabase; } diff --git a/src/firebase-app.ts b/src/firebase-app.ts index 364b4f5b26..bccd99583f 100644 --- a/src/firebase-app.ts +++ b/src/firebase-app.ts @@ -25,7 +25,7 @@ import { Auth } from './auth/auth'; import { MachineLearning } from './machine-learning/machine-learning'; import { Messaging } from './messaging/messaging'; import { Storage } from './storage/storage'; -import { Database } from '@firebase/database'; +import { FirebaseDatabase } from '@firebase/database-types'; import { DatabaseService } from './database/database-internal'; import { Firestore } from '@google-cloud/firestore'; import { FirestoreService } from './firestore/firestore'; @@ -305,7 +305,7 @@ export class FirebaseApp { * * @return {Database} The Database service instance of this app. */ - public database(url?: string): Database { + public database(url?: string): FirebaseDatabase { const service: DatabaseService = this.ensureService_('database', () => { const dbService: typeof DatabaseService = require('./database/database-internal').DatabaseService; return new dbService(this); diff --git a/src/firebase-namespace.ts b/src/firebase-namespace.ts index 916132ebbf..5c0083478a 100644 --- a/src/firebase-namespace.ts +++ b/src/firebase-namespace.ts @@ -31,7 +31,7 @@ import { Auth } from './auth/auth'; import { MachineLearning } from './machine-learning/machine-learning'; import { Messaging } from './messaging/messaging'; import { Storage } from './storage/storage'; -import { Database } from '@firebase/database'; +import { FirebaseDatabase } from '@firebase/database-types'; import { Firestore } from '@google-cloud/firestore'; import { InstanceId } from './instance-id/instance-id'; import { ProjectManagement } from './project-management/project-management'; @@ -339,8 +339,8 @@ export class FirebaseNamespace { * Gets the `Database` service namespace. The returned namespace can be used to get the * `Database` service for the default app or an explicitly specified app. */ - get database(): FirebaseServiceNamespace { - const fn: FirebaseServiceNamespace = (app?: FirebaseApp) => { + get database(): FirebaseServiceNamespace { + const fn: FirebaseServiceNamespace = (app?: FirebaseApp) => { return this.ensureApp(app).database(); }; diff --git a/test/unit/firebase-app.spec.ts b/test/unit/firebase-app.spec.ts index dc89b6383c..d384fd6a0d 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/firebase-app.spec.ts @@ -35,7 +35,7 @@ import { Messaging } from '../../src/messaging/messaging'; import { MachineLearning } from '../../src/machine-learning/machine-learning'; import { Storage } from '../../src/storage/storage'; import { Firestore } from '@google-cloud/firestore'; -import { Database } from '@firebase/database'; +import { FirebaseDatabase } from '@firebase/database-types'; import { InstanceId } from '../../src/instance-id/instance-id'; import { ProjectManagement } from '../../src/project-management/project-management'; import { SecurityRules } from '../../src/security-rules/security-rules'; @@ -444,15 +444,15 @@ describe('FirebaseApp', () => { it('should return the Database', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - const db: Database = app.database(); + const db: FirebaseDatabase = app.database(); expect(db).not.be.null; }); it('should return the Database for different apps', () => { const app1 = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); const app2 = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName + '-other'); - const db1: Database = app1.database(); - const db2: Database = app2.database(); + const db1: FirebaseDatabase = app1.database(); + const db2: FirebaseDatabase = app2.database(); expect(db1).to.not.deep.equal(db2); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); expect(db2.ref().toString()).to.equal('https://databasename.firebaseio.com/'); @@ -468,9 +468,9 @@ describe('FirebaseApp', () => { it('should return a cached version of Database on subsequent calls', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - const db1: Database = app.database(); - const db2: Database = app.database(); - const db3: Database = app.database(mocks.appOptions.databaseURL); + const db1: FirebaseDatabase = app.database(); + const db2: FirebaseDatabase = app.database(); + const db3: FirebaseDatabase = app.database(mocks.appOptions.databaseURL); expect(db1).to.equal(db2); expect(db1).to.equal(db3); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); @@ -478,8 +478,8 @@ describe('FirebaseApp', () => { it('should return a Database instance for the specified URL', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - const db1: Database = app.database(); - const db2: Database = app.database('https://other-database.firebaseio.com'); + const db1: FirebaseDatabase = app.database(); + const db2: FirebaseDatabase = app.database('https://other-database.firebaseio.com'); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); expect(db2.ref().toString()).to.equal('https://other-database.firebaseio.com/'); }); From 9f20af2ad2b0e43355430e7bbfa775dcb9ff092d Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 28 Jul 2020 14:58:50 -0400 Subject: [PATCH 31/46] Fix unit test failures --- test/unit/database/database.spec.ts | 60 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index bae9320fc4..5d55b93545 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -23,7 +23,7 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/firebase-app'; import { DatabaseService } from '../../../src/database/database-internal'; -import { Database } from '@firebase/database'; +import { FirebaseDatabase } from '@firebase/database-types'; import * as utils from '../utils'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; @@ -82,25 +82,25 @@ describe('Database', () => { describe('getDatabase', () => { it('should return the default Database namespace', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); expect(db.ref().toString()).to.be.equal('https://databasename.firebaseio.com/'); }); it('should return the Database namespace', () => { - const db: Database = database.getDatabase(mockApp.options.databaseURL); + const db: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); expect(db.ref().toString()).to.be.equal('https://databasename.firebaseio.com/'); }); it('should return a cached version of Database on subsequent calls', () => { - const db1: Database = database.getDatabase(mockApp.options.databaseURL); - const db2: Database = database.getDatabase(mockApp.options.databaseURL); + const db1: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); + const db2: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); expect(db1).to.equal(db2); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); }); it('should return a Database instance for the specified URL', () => { - const db1: Database = database.getDatabase(mockApp.options.databaseURL); - const db2: Database = database.getDatabase('https://other-database.firebaseio.com'); + const db1: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); + const db2: FirebaseDatabase = database.getDatabase('https://other-database.firebaseio.com'); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); expect(db2.ref().toString()).to.equal('https://other-database.firebaseio.com/'); }); @@ -187,7 +187,7 @@ describe('Database', () => { describe('getRules', () => { it('should return the rules fetched from the database', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = stubSuccessfulResponse(rules); return db.getRules().then((result) => { expect(result).to.equal(rulesString); @@ -197,7 +197,7 @@ describe('Database', () => { }); it('should return the rules fetched from the database including comments', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = stubSuccessfulResponse(rulesWithComments); return db.getRules().then((result) => { expect(result).to.equal(rulesWithComments); @@ -207,7 +207,7 @@ describe('Database', () => { }); it('should return the rules fetched from the explicitly specified database', () => { - const db: Database = database.getDatabase('https://custom.firebaseio.com'); + const db: FirebaseDatabase = database.getDatabase('https://custom.firebaseio.com'); const stub = stubSuccessfulResponse(rules); return db.getRules().then((result) => { expect(result).to.equal(rulesString); @@ -217,7 +217,7 @@ describe('Database', () => { }); it('should return the rules fetched from the custom URL with query params', () => { - const db: Database = database.getDatabase('http://localhost:9000?ns=foo'); + const db: FirebaseDatabase = database.getDatabase('http://localhost:9000?ns=foo'); const stub = stubSuccessfulResponse(rules); return db.getRules().then((result) => { expect(result).to.equal(rulesString); @@ -227,21 +227,21 @@ describe('Database', () => { }); it('should throw if the server responds with a well-formed error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); stubErrorResponse({ error: 'test error' }); return db.getRules().should.eventually.be.rejectedWith( 'Error while accessing security rules: test error'); }); it('should throw if the server responds with an error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); stubErrorResponse('error text'); return db.getRules().should.eventually.be.rejectedWith( 'Error while accessing security rules: error text'); }); it('should throw in the event of an I/O error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = sinon.stub(HttpClient.prototype, 'send').rejects( new Error('network error')); stubs.push(stub); @@ -251,7 +251,7 @@ describe('Database', () => { describe('getRulesWithJSON', () => { it('should return the rules fetched from the database', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = stubSuccessfulResponse(rules); return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); @@ -261,7 +261,7 @@ describe('Database', () => { }); it('should return the rules fetched from the explicitly specified database', () => { - const db: Database = database.getDatabase('https://custom.firebaseio.com'); + const db: FirebaseDatabase = database.getDatabase('https://custom.firebaseio.com'); const stub = stubSuccessfulResponse(rules); return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); @@ -271,7 +271,7 @@ describe('Database', () => { }); it('should return the rules fetched from the custom URL with query params', () => { - const db: Database = database.getDatabase('http://localhost:9000?ns=foo'); + const db: FirebaseDatabase = database.getDatabase('http://localhost:9000?ns=foo'); const stub = stubSuccessfulResponse(rules); return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); @@ -281,21 +281,21 @@ describe('Database', () => { }); it('should throw if the server responds with a well-formed error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); stubErrorResponse({ error: 'test error' }); return db.getRulesJSON().should.eventually.be.rejectedWith( 'Error while accessing security rules: test error'); }); it('should throw if the server responds with an error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); stubErrorResponse('error text'); return db.getRulesJSON().should.eventually.be.rejectedWith( 'Error while accessing security rules: error text'); }); it('should throw in the event of an I/O error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = sinon.stub(HttpClient.prototype, 'send').rejects( new Error('network error')); stubs.push(stub); @@ -321,7 +321,7 @@ describe('Database', () => { describe('setRules', () => { it('should set the rules when specified as a string', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = stubSuccessfulResponse({}); return db.setRules(rulesString).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -330,7 +330,7 @@ describe('Database', () => { }); it('should set the rules when specified as a Buffer', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = stubSuccessfulResponse({}); const buffer = Buffer.from(rulesString); return db.setRules(buffer).then(() => { @@ -340,7 +340,7 @@ describe('Database', () => { }); it('should set the rules when specified as an object', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.app.database(); const stub = stubSuccessfulResponse({}); return db.setRules(rules).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -349,7 +349,7 @@ describe('Database', () => { }); it('should set the rules with comments when specified as a string', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = stubSuccessfulResponse({}); return db.setRules(rulesWithComments).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -358,7 +358,7 @@ describe('Database', () => { }); it('should set the rules in the explicitly specified database', () => { - const db: Database = database.getDatabase('https://custom.firebaseio.com'); + const db: FirebaseDatabase = database.getDatabase('https://custom.firebaseio.com'); const stub = stubSuccessfulResponse({}); return db.setRules(rulesString).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -367,7 +367,7 @@ describe('Database', () => { }); it('should set the rules using the custom URL with query params', () => { - const db: Database = database.getDatabase('http://localhost:9000?ns=foo'); + const db: FirebaseDatabase = database.getDatabase('http://localhost:9000?ns=foo'); const stub = stubSuccessfulResponse({}); return db.setRules(rulesString).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -378,28 +378,28 @@ describe('Database', () => { const invalidSources: any[] = [null, '', undefined, true, false, 1]; invalidSources.forEach((invalidSource) => { it(`should throw if the source is ${JSON.stringify(invalidSource)}`, () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); return db.setRules(invalidSource).should.eventually.be.rejectedWith( 'Source must be a non-empty string, Buffer or an object.'); }); }); it('should throw if the server responds with a well-formed error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); stubErrorResponse({ error: 'test error' }); return db.setRules(rules).should.eventually.be.rejectedWith( 'Error while accessing security rules: test error'); }); it('should throw if the server responds with an error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); stubErrorResponse('error text'); return db.setRules(rules).should.eventually.be.rejectedWith( 'Error while accessing security rules: error text'); }); it('should throw in the event of an I/O error', () => { - const db: Database = database.getDatabase(); + const db: FirebaseDatabase = database.getDatabase(); const stub = sinon.stub(HttpClient.prototype, 'send').rejects( new Error('network error')); stubs.push(stub); From 401fb04a5b74ceb1fa63dc3c3ea07a9b57a5cbb1 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 28 Jul 2020 15:08:49 -0400 Subject: [PATCH 32/46] Fix unit test failures --- test/unit/firebase-namespace.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/firebase-namespace.spec.ts index 60b26ff91d..f75892405e 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/firebase-namespace.spec.ts @@ -36,6 +36,7 @@ import { Reference, ServerValue, } from '@firebase/database'; +import { FirebaseDatabase } from '@firebase/database-types'; import { Messaging } from '../../src/messaging/messaging'; import { MachineLearning } from '../../src/machine-learning/machine-learning'; import { Storage } from '../../src/storage/storage'; @@ -404,14 +405,14 @@ describe('FirebaseNamespace', () => { it('should return a valid namespace when the default app is initialized', () => { const app: FirebaseApp = firebaseNamespace.initializeApp(mocks.appOptions); - const db: Database = firebaseNamespace.database(); + const db: FirebaseDatabase = firebaseNamespace.database(); expect(db.app).to.be.deep.equal(app); return app.delete(); }); it('should return a valid namespace when the named app is initialized', () => { const app: FirebaseApp = firebaseNamespace.initializeApp(mocks.appOptions, 'testApp'); - const db: Database = firebaseNamespace.database(app); + const db: FirebaseDatabase = firebaseNamespace.database(app); expect(db.app).to.be.deep.equal(app); return app.delete(); }); From b060e6eb12f04ddc523f5ee2ca6d595ccfe9654c Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 28 Jul 2020 15:38:44 -0400 Subject: [PATCH 33/46] Remove ThenableReference and EventType --- src/database/database.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/database/database.ts b/src/database/database.ts index 4b37fa0d04..3e12e28d98 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Reference } from '@firebase/database'; + +// Required to perform module augmentation to FirebaseDatabase interface. +import '@firebase/database-types'; declare module '@firebase/database-types' { interface FirebaseDatabase { @@ -43,14 +45,3 @@ declare module '@firebase/database-types' { setRules(source: string | Buffer | object): Promise; } } - -// EventType and ThenableReference are temporarily defined here because they're -// not exposed in @firebase/database. -// See: https://github.com/firebase/firebase-js-sdk/issues/3479 -export type EventType = 'value' | 'child_added' | 'child_changed' - | 'child_moved' | 'child_removed'; - -/** - * @extends {Reference} - */ -export interface ThenableReference extends Reference, Promise { } From 89b8fc842514d10901b4d882076ac418eca99271 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 28 Jul 2020 17:01:03 -0400 Subject: [PATCH 34/46] Fix unit test failures --- src/database/database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/database.ts b/src/database/database.ts index 3e12e28d98..8d3126b519 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -15,7 +15,7 @@ */ // Required to perform module augmentation to FirebaseDatabase interface. -import '@firebase/database-types'; +import '@firebase/database'; declare module '@firebase/database-types' { interface FirebaseDatabase { From f756136a4ecaacbab3363bb93b64d6f27983a19f Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 29 Jul 2020 22:16:01 -0400 Subject: [PATCH 35/46] Add attempt --- src/database/database-internal.ts | 33 ++++++++++++++++++++++++++++--- src/database/database.ts | 2 +- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts index 414a7eee99..8480fad890 100644 --- a/src/database/database-internal.ts +++ b/src/database/database-internal.ts @@ -22,13 +22,12 @@ import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { Database } from '@firebase/database'; import { FirebaseDatabase } from '@firebase/database-types'; -import './database'; +// import './database'; import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { getSdkVersion } from '../utils/index'; - /** * Internals of a Database instance. */ @@ -98,7 +97,7 @@ export class DatabaseService implements FirebaseServiceInterface { db.getRulesJSON = () => { return rulesClient.getRulesJSON(); }; - db.setRules = (source) => { + db.setRules = (source: string) => { return rulesClient.setRules(source); }; @@ -242,3 +241,31 @@ class DatabaseRulesClient { } } +declare module '@firebase/database-types' { + interface FirebaseDatabase { + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @return A promise fulfilled with the rules as a raw string. + */ + getRules(): Promise; + + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @return A promise fulfilled with the parsed rules object. + */ + getRulesJSON(): Promise; + + /** + * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param source Source of the rules to apply. Must not be `null` or empty. + * @return Resolves when the rules are set on the Realtime Database. + */ + setRules(source: string | Buffer | object): Promise; + } +} diff --git a/src/database/database.ts b/src/database/database.ts index 8d3126b519..3e12e28d98 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -15,7 +15,7 @@ */ // Required to perform module augmentation to FirebaseDatabase interface. -import '@firebase/database'; +import '@firebase/database-types'; declare module '@firebase/database-types' { interface FirebaseDatabase { From 62515c5366e16fd93d310b34e091b36668aa967c Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 30 Jul 2020 09:35:42 -0400 Subject: [PATCH 36/46] Address feedback from offline conversation --- src/database/database-internal.ts | 12 ++++++------ src/database/database.ts | 4 +++- src/database/index.ts | 25 +++++++++++++------------ src/firebase-app.ts | 4 ++-- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts index 8480fad890..0d4c529add 100644 --- a/src/database/database-internal.ts +++ b/src/database/database-internal.ts @@ -20,8 +20,8 @@ import * as path from 'path'; import { FirebaseApp } from '../firebase-app'; import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; -import { Database } from '@firebase/database'; -import { FirebaseDatabase } from '@firebase/database-types'; +import { Database as DatabaseImpl } from '@firebase/database'; +import { Database } from './database'; // import './database'; import * as validator from '../utils/validator'; @@ -34,7 +34,7 @@ import { getSdkVersion } from '../utils/index'; class DatabaseInternals implements FirebaseServiceInternalsInterface { public databases: { - [dbUrl: string]: FirebaseDatabase; + [dbUrl: string]: Database; } = {}; /** @@ -44,7 +44,7 @@ class DatabaseInternals implements FirebaseServiceInternalsInterface { */ public delete(): Promise { for (const dbUrl of Object.keys(this.databases)) { - const db: Database = ((this.databases[dbUrl] as any) as Database); + const db: DatabaseImpl = ((this.databases[dbUrl] as any) as DatabaseImpl); db.INTERNAL.delete(); } return Promise.resolve(undefined); @@ -76,7 +76,7 @@ export class DatabaseService implements FirebaseServiceInterface { return this.appInternal; } - public getDatabase(url?: string): FirebaseDatabase { + public getDatabase(url?: string): Database { const dbUrl: string = this.ensureUrl(url); if (!validator.isNonEmptyString(dbUrl)) { throw new FirebaseDatabaseError({ @@ -85,7 +85,7 @@ export class DatabaseService implements FirebaseServiceInterface { }); } - let db: FirebaseDatabase = this.INTERNAL.databases[dbUrl]; + let db: Database = this.INTERNAL.databases[dbUrl]; if (typeof db === 'undefined') { const rtdb = require('@firebase/database'); // eslint-disable-line @typescript-eslint/no-var-requires db = rtdb.initStandalone(this.appInternal, dbUrl, getSdkVersion()).instance; diff --git a/src/database/database.ts b/src/database/database.ts index 3e12e28d98..474426748f 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -15,7 +15,7 @@ */ // Required to perform module augmentation to FirebaseDatabase interface. -import '@firebase/database-types'; +import { FirebaseDatabase } from '@firebase/database-types'; declare module '@firebase/database-types' { interface FirebaseDatabase { @@ -45,3 +45,5 @@ declare module '@firebase/database-types' { setRules(source: string | Buffer | object): Promise; } } + +export { FirebaseDatabase as Database } diff --git a/src/database/index.ts b/src/database/index.ts index e29d3f56d7..9f102fcc20 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -16,10 +16,11 @@ import { FirebaseApp } from '../firebase-app'; import { ServerValue as sv } from '@firebase/database'; -import * as firebaseRtdbTypesApi from '@firebase/database-types'; +import * as adminDb from './database'; +import * as firebaseDbTypesApi from '@firebase/database-types'; import * as firebaseAdmin from '../index'; -export function database(app?: FirebaseApp): firebaseRtdbTypesApi.FirebaseDatabase { +export function database(app?: FirebaseApp): adminDb.Database { if (typeof(app) === 'undefined') { app = firebaseAdmin.app(); } @@ -29,7 +30,7 @@ export function database(app?: FirebaseApp): firebaseRtdbTypesApi.FirebaseDataba /** * We must define a namespace to make the typings work correctly. Otherwise * `admin.database()` cannot be called like a function. Temporarily, - * admin.database is used as the namespace name because we cannot barrel + * admin.database is used as the namespace name because we cannot barrel * re-export the contents from @firebase/database-types, and we want it to * match the namespacing in the re-export inside src/index.d.ts */ @@ -38,16 +39,16 @@ export namespace admin.database { // See https://github.com/microsoft/TypeScript/issues/4336 /* eslint-disable @typescript-eslint/no-unused-vars */ // See https://github.com/typescript-eslint/typescript-eslint/issues/363 - export import DataSnapshot = firebaseRtdbTypesApi.DataSnapshot; - export import OnDisconnect = firebaseRtdbTypesApi.OnDisconnect; - export import EventType = firebaseRtdbTypesApi.EventType; - export import Query = firebaseRtdbTypesApi.Query; - export import Reference = firebaseRtdbTypesApi.Reference; - export import ThenableReference = firebaseRtdbTypesApi.ThenableReference; - export import enableLogging = firebaseRtdbTypesApi.enableLogging; - export const ServerValue: firebaseRtdbTypesApi.ServerValue = sv; + export import DataSnapshot = firebaseDbTypesApi.DataSnapshot; + export import OnDisconnect = firebaseDbTypesApi.OnDisconnect; + export import EventType = firebaseDbTypesApi.EventType; + export import Query = firebaseDbTypesApi.Query; + export import Reference = firebaseDbTypesApi.Reference; + export import ThenableReference = firebaseDbTypesApi.ThenableReference; + export import enableLogging = firebaseDbTypesApi.enableLogging; + export const ServerValue: firebaseDbTypesApi.ServerValue = sv; // There is a known bug where @firebase/database-types FirebaseDatabase // cannot be used as an interface for @firebase/database Database. // See https://github.com/firebase/firebase-js-sdk/issues/3476 - export import Database = firebaseRtdbTypesApi.FirebaseDatabase; + export import Database = adminDb.Database; } diff --git a/src/firebase-app.ts b/src/firebase-app.ts index bccd99583f..04475e5cff 100644 --- a/src/firebase-app.ts +++ b/src/firebase-app.ts @@ -25,7 +25,7 @@ import { Auth } from './auth/auth'; import { MachineLearning } from './machine-learning/machine-learning'; import { Messaging } from './messaging/messaging'; import { Storage } from './storage/storage'; -import { FirebaseDatabase } from '@firebase/database-types'; +import { Database } from './database/database'; import { DatabaseService } from './database/database-internal'; import { Firestore } from '@google-cloud/firestore'; import { FirestoreService } from './firestore/firestore'; @@ -305,7 +305,7 @@ export class FirebaseApp { * * @return {Database} The Database service instance of this app. */ - public database(url?: string): FirebaseDatabase { + public database(url?: string): Database { const service: DatabaseService = this.ensureService_('database', () => { const dbService: typeof DatabaseService = require('./database/database-internal').DatabaseService; return new dbService(this); From 91beb054eaacb00385ec61862afcc7819af381f0 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Thu, 30 Jul 2020 11:21:34 -0400 Subject: [PATCH 37/46] Update unit test as well --- test/unit/database/database.spec.ts | 60 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index 5d55b93545..75558267f5 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -23,7 +23,7 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/firebase-app'; import { DatabaseService } from '../../../src/database/database-internal'; -import { FirebaseDatabase } from '@firebase/database-types'; +import { Database } from '../../../src/database/database'; import * as utils from '../utils'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; @@ -82,25 +82,25 @@ describe('Database', () => { describe('getDatabase', () => { it('should return the default Database namespace', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); expect(db.ref().toString()).to.be.equal('https://databasename.firebaseio.com/'); }); it('should return the Database namespace', () => { - const db: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); + const db: Database = database.getDatabase(mockApp.options.databaseURL); expect(db.ref().toString()).to.be.equal('https://databasename.firebaseio.com/'); }); it('should return a cached version of Database on subsequent calls', () => { - const db1: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); - const db2: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); + const db1: Database = database.getDatabase(mockApp.options.databaseURL); + const db2: Database = database.getDatabase(mockApp.options.databaseURL); expect(db1).to.equal(db2); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); }); it('should return a Database instance for the specified URL', () => { - const db1: FirebaseDatabase = database.getDatabase(mockApp.options.databaseURL); - const db2: FirebaseDatabase = database.getDatabase('https://other-database.firebaseio.com'); + const db1: Database = database.getDatabase(mockApp.options.databaseURL); + const db2: Database = database.getDatabase('https://other-database.firebaseio.com'); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); expect(db2.ref().toString()).to.equal('https://other-database.firebaseio.com/'); }); @@ -187,7 +187,7 @@ describe('Database', () => { describe('getRules', () => { it('should return the rules fetched from the database', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = stubSuccessfulResponse(rules); return db.getRules().then((result) => { expect(result).to.equal(rulesString); @@ -197,7 +197,7 @@ describe('Database', () => { }); it('should return the rules fetched from the database including comments', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = stubSuccessfulResponse(rulesWithComments); return db.getRules().then((result) => { expect(result).to.equal(rulesWithComments); @@ -207,7 +207,7 @@ describe('Database', () => { }); it('should return the rules fetched from the explicitly specified database', () => { - const db: FirebaseDatabase = database.getDatabase('https://custom.firebaseio.com'); + const db: Database = database.getDatabase('https://custom.firebaseio.com'); const stub = stubSuccessfulResponse(rules); return db.getRules().then((result) => { expect(result).to.equal(rulesString); @@ -217,7 +217,7 @@ describe('Database', () => { }); it('should return the rules fetched from the custom URL with query params', () => { - const db: FirebaseDatabase = database.getDatabase('http://localhost:9000?ns=foo'); + const db: Database = database.getDatabase('http://localhost:9000?ns=foo'); const stub = stubSuccessfulResponse(rules); return db.getRules().then((result) => { expect(result).to.equal(rulesString); @@ -227,21 +227,21 @@ describe('Database', () => { }); it('should throw if the server responds with a well-formed error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); stubErrorResponse({ error: 'test error' }); return db.getRules().should.eventually.be.rejectedWith( 'Error while accessing security rules: test error'); }); it('should throw if the server responds with an error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); stubErrorResponse('error text'); return db.getRules().should.eventually.be.rejectedWith( 'Error while accessing security rules: error text'); }); it('should throw in the event of an I/O error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = sinon.stub(HttpClient.prototype, 'send').rejects( new Error('network error')); stubs.push(stub); @@ -251,7 +251,7 @@ describe('Database', () => { describe('getRulesWithJSON', () => { it('should return the rules fetched from the database', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = stubSuccessfulResponse(rules); return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); @@ -261,7 +261,7 @@ describe('Database', () => { }); it('should return the rules fetched from the explicitly specified database', () => { - const db: FirebaseDatabase = database.getDatabase('https://custom.firebaseio.com'); + const db: Database = database.getDatabase('https://custom.firebaseio.com'); const stub = stubSuccessfulResponse(rules); return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); @@ -271,7 +271,7 @@ describe('Database', () => { }); it('should return the rules fetched from the custom URL with query params', () => { - const db: FirebaseDatabase = database.getDatabase('http://localhost:9000?ns=foo'); + const db: Database = database.getDatabase('http://localhost:9000?ns=foo'); const stub = stubSuccessfulResponse(rules); return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); @@ -281,21 +281,21 @@ describe('Database', () => { }); it('should throw if the server responds with a well-formed error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); stubErrorResponse({ error: 'test error' }); return db.getRulesJSON().should.eventually.be.rejectedWith( 'Error while accessing security rules: test error'); }); it('should throw if the server responds with an error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); stubErrorResponse('error text'); return db.getRulesJSON().should.eventually.be.rejectedWith( 'Error while accessing security rules: error text'); }); it('should throw in the event of an I/O error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = sinon.stub(HttpClient.prototype, 'send').rejects( new Error('network error')); stubs.push(stub); @@ -321,7 +321,7 @@ describe('Database', () => { describe('setRules', () => { it('should set the rules when specified as a string', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = stubSuccessfulResponse({}); return db.setRules(rulesString).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -330,7 +330,7 @@ describe('Database', () => { }); it('should set the rules when specified as a Buffer', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = stubSuccessfulResponse({}); const buffer = Buffer.from(rulesString); return db.setRules(buffer).then(() => { @@ -340,7 +340,7 @@ describe('Database', () => { }); it('should set the rules when specified as an object', () => { - const db: FirebaseDatabase = database.app.database(); + const db: Database = database.app.database(); const stub = stubSuccessfulResponse({}); return db.setRules(rules).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -349,7 +349,7 @@ describe('Database', () => { }); it('should set the rules with comments when specified as a string', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = stubSuccessfulResponse({}); return db.setRules(rulesWithComments).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -358,7 +358,7 @@ describe('Database', () => { }); it('should set the rules in the explicitly specified database', () => { - const db: FirebaseDatabase = database.getDatabase('https://custom.firebaseio.com'); + const db: Database = database.getDatabase('https://custom.firebaseio.com'); const stub = stubSuccessfulResponse({}); return db.setRules(rulesString).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -367,7 +367,7 @@ describe('Database', () => { }); it('should set the rules using the custom URL with query params', () => { - const db: FirebaseDatabase = database.getDatabase('http://localhost:9000?ns=foo'); + const db: Database = database.getDatabase('http://localhost:9000?ns=foo'); const stub = stubSuccessfulResponse({}); return db.setRules(rulesString).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith( @@ -378,28 +378,28 @@ describe('Database', () => { const invalidSources: any[] = [null, '', undefined, true, false, 1]; invalidSources.forEach((invalidSource) => { it(`should throw if the source is ${JSON.stringify(invalidSource)}`, () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); return db.setRules(invalidSource).should.eventually.be.rejectedWith( 'Source must be a non-empty string, Buffer or an object.'); }); }); it('should throw if the server responds with a well-formed error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); stubErrorResponse({ error: 'test error' }); return db.setRules(rules).should.eventually.be.rejectedWith( 'Error while accessing security rules: test error'); }); it('should throw if the server responds with an error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); stubErrorResponse('error text'); return db.setRules(rules).should.eventually.be.rejectedWith( 'Error while accessing security rules: error text'); }); it('should throw in the event of an I/O error', () => { - const db: FirebaseDatabase = database.getDatabase(); + const db: Database = database.getDatabase(); const stub = sinon.stub(HttpClient.prototype, 'send').rejects( new Error('network error')); stubs.push(stub); From eaf7d223e8c4102af7f2bbab496532c9c6da2307 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 7 Aug 2020 14:08:15 -0400 Subject: [PATCH 38/46] Add progress --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c2d93aee6c..4e010fe722 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ ], "types": "./lib/index.d.ts", "dependencies": { + "@firebase/auth-interop-types": "^0.1.5", "@firebase/database": "^0.6.10", "@firebase/database-types": "^0.5.2", "@types/node": "^10.10.0", From c30a9094213bfeae49dbfce27cc238346a2b7f3e Mon Sep 17 00:00:00 2001 From: MathBunny Date: Mon, 10 Aug 2020 18:58:41 -0400 Subject: [PATCH 39/46] Fix broken dependencies --- package-lock.json | 89 +++++++++++++++++++++++++++++++---------------- package.json | 5 ++- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index cef6f8ac94..73f047671e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,15 +156,15 @@ } }, "@firebase/app": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.7.tgz", - "integrity": "sha512-6NpIZ3iMrCR2XOShK5oi3YYB0GXX5yxVD8p3+2N+X4CF5cERyIrDRf8+YXOFgr+bDHSbVcIyzpWv6ijhg4MJlw==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.9.tgz", + "integrity": "sha512-X2riRgK49IK8LCQ3j7BKLu3zqHDTJSaT6YgcLewtHuOVwtpHfGODiS1cL5VMvKm3ogxP84GA70tN3sdoL/vTog==", "dev": true, "requires": { "@firebase/app-types": "0.6.1", - "@firebase/component": "0.1.15", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.50", + "@firebase/component": "0.1.17", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.0", "dom-storage": "2.1.0", "tslib": "^1.11.1", "xmlhttprequest": "1.8.0" @@ -176,12 +176,20 @@ "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" }, "@firebase/auth": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.13.6.tgz", - "integrity": "sha512-ERlda/t5RimNw5Err+5HJATC/qFkC64zR40G+4nK5b9eFJEm0MB+/DaismCwp6J6GoVL3NmejoVbuWU7sV4G1w==", + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.9.tgz", + "integrity": "sha512-PxYa2r5qUEdheXTvqROFrMstK8W4uPiP7NVfp+2Bec+AjY5PxZapCx/YFDLkU0D7YBI82H74PtZrzdJZw7TJ4w==", "dev": true, "requires": { - "@firebase/auth-types": "0.9.6" + "@firebase/auth-types": "0.10.1" + }, + "dependencies": { + "@firebase/auth-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", + "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", + "dev": true + } } }, "@firebase/auth-interop-types": { @@ -196,12 +204,12 @@ "dev": true }, "@firebase/component": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.15.tgz", - "integrity": "sha512-HqFb1qQl1vtlUMIzPM15plNz27jqM8DWjuQQuGeDfG+4iRRflwKfgNw1BOyoP4kQ8vOBCL7t/71yPXSomNdJdQ==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.17.tgz", + "integrity": "sha512-/tN5iLcFp9rdpTfCJPfQ/o2ziGHlDxOzNx6XD2FoHlu4pG/PPGu+59iRfQXIowBGhxcTGD/l7oJhZEY/PVg0KQ==", "dev": true, "requires": { - "@firebase/util": "0.2.50", + "@firebase/util": "0.3.0", "tslib": "^1.11.1" } }, @@ -252,15 +260,15 @@ } }, "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", "dev": true }, "@firebase/util": { - "version": "0.2.50", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.50.tgz", - "integrity": "sha512-vFE6+Jfc25u0ViSpFxxq0q5s+XmuJ/y7CL3ud79RQe+WLFFg+j0eH1t23k0yNSG9vZNM7h3uHRIXbV97sYLAyw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.0.tgz", + "integrity": "sha512-GTwC+FSLeCPc44/TXCDReNQ5FPRIS5cb8Gr1XcD1TgiNBOvmyx61Om2YLwHp2GnN++6m6xmwmXARm06HOukATA==", "dev": true, "requires": { "tslib": "^1.11.1" @@ -599,7 +607,8 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true }, "@types/minimist": { "version": "1.2.0", @@ -821,6 +830,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, "requires": { "ansi-wrap": "^0.1.0" } @@ -866,7 +876,8 @@ "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true }, "any-promise": { "version": "1.3.0", @@ -936,7 +947,8 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "arr-filter": { "version": "1.1.2", @@ -965,7 +977,8 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true }, "array-differ": { "version": "1.0.0", @@ -1095,7 +1108,8 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true }, "astral-regex": { "version": "1.0.0", @@ -1183,7 +1197,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -1308,6 +1323,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1742,7 +1758,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "2.0.0", @@ -2726,6 +2743,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -2735,6 +2753,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -3551,6 +3570,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-6.0.0.tgz", "integrity": "sha512-veQFW93kf6jBdWdF/RxMEIlDK2mkjHyPftM381DID2C9ImTVngwYpyyThxm4/EpgcNOT37BLefzMOjEKbyYg0Q==", + "dev": true, "requires": { "multimatch": "^4.0.0", "plugin-error": "^1.0.1", @@ -4373,6 +4393,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, "requires": { "isobject": "^3.0.1" } @@ -4462,7 +4483,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "isstream": { "version": "0.1.2", @@ -5271,6 +5293,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5413,6 +5436,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "dev": true, "requires": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", @@ -5424,12 +5448,14 @@ "array-differ": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==" + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true } } }, @@ -6380,6 +6406,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, "requires": { "ansi-colors": "^1.0.1", "arr-diff": "^4.0.0", @@ -7442,6 +7469,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", + "dev": true, "requires": { "readable-stream": "^3.0.6" }, @@ -7450,6 +7478,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", diff --git a/package.json b/package.json index 4e010fe722..246286a0e2 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "@firebase/database-types": "^0.5.2", "@types/node": "^10.10.0", "dicer": "^0.3.0", - "gulp-filter": "^6.0.0", "jsonwebtoken": "^8.5.1", "node-forge": "^0.9.1" }, @@ -68,8 +67,8 @@ "@google-cloud/storage": "^5.0.0" }, "devDependencies": { - "@firebase/app": "^0.6.1", - "@firebase/auth": "^0.13.3", + "@firebase/app": "^0.6.9", + "@firebase/auth": "^0.14.9", "@firebase/auth-types": "^0.9.3", "@types/bcrypt": "^2.0.0", "@types/chai": "^4.0.0", From 5ea35ba5fcf0969e69f3a2f9f82d55ec11478098 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Mon, 10 Aug 2020 19:11:19 -0400 Subject: [PATCH 40/46] Bump auth-types --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73f047671e..77678dded8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -198,9 +198,9 @@ "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" }, "@firebase/auth-types": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.9.6.tgz", - "integrity": "sha512-HB1yXe5hgiwPMukLBEfC3TQX22U9qKczj8kEclKhL7rnds3FKZWMM0+EpKbcJREbU9Sj/rgwgaio7ovSN4ZQFA==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", + "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", "dev": true }, "@firebase/component": { diff --git a/package.json b/package.json index 246286a0e2..6394167284 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "devDependencies": { "@firebase/app": "^0.6.9", "@firebase/auth": "^0.14.9", - "@firebase/auth-types": "^0.9.3", + "@firebase/auth-types": "^0.10.1", "@types/bcrypt": "^2.0.0", "@types/chai": "^4.0.0", "@types/chai-as-promised": "^7.1.0", From 07a8f9fc93a2eb78c99cb5b8aa5c3595b46eafba Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 11 Aug 2020 10:48:36 -0400 Subject: [PATCH 41/46] Remove second module augmentation --- src/database/database-internal.ts | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts index 0d4c529add..fa955a9b7e 100644 --- a/src/database/database-internal.ts +++ b/src/database/database-internal.ts @@ -22,7 +22,6 @@ import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { Database as DatabaseImpl } from '@firebase/database'; import { Database } from './database'; -// import './database'; import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; @@ -240,32 +239,3 @@ class DatabaseRulesClient { return `${intro}: ${err.response.text}`; } } - -declare module '@firebase/database-types' { - interface FirebaseDatabase { - /** - * Gets the currently applied security rules as a string. The return value consists of - * the rules source including comments. - * - * @return A promise fulfilled with the rules as a raw string. - */ - getRules(): Promise; - - /** - * Gets the currently applied security rules as a parsed JSON object. Any comments in - * the original source are stripped away. - * - * @return A promise fulfilled with the parsed rules object. - */ - getRulesJSON(): Promise; - - /** - * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is - * specified as a string or a Buffer, it may include comments. - * - * @param source Source of the rules to apply. Must not be `null` or empty. - * @return Resolves when the rules are set on the Realtime Database. - */ - setRules(source: string | Buffer | object): Promise; - } -} From d791c2179fc36d41756389b9bfe4363a2c186e81 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 11 Aug 2020 10:59:36 -0400 Subject: [PATCH 42/46] fix: Remove database from typing excludes in gulpfile --- gulpfile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index c9112a2ccf..a77bb47652 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -68,7 +68,6 @@ const TEMPORARY_TYPING_EXCLUDES = [ '!lib/firebase-app.d.ts', '!lib/firebase-service.d.ts', '!lib/auth/*.d.ts', - '!lib/database/*.d.ts', '!lib/firestore/*.d.ts', '!lib/machine-learning/*.d.ts', '!lib/remote-config/*.d.ts', From a18067df180cb0e45d921c570af67c19f5826c71 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 12 Aug 2020 10:04:26 -0400 Subject: [PATCH 43/46] fix: Address feedback --- package.json | 1 - src/database/index.ts | 8 +++----- src/firebase-namespace.ts | 2 +- test/unit/firebase-app.spec.ts | 2 +- test/unit/firebase-namespace.spec.ts | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6394167284..41f7b63281 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ ], "types": "./lib/index.d.ts", "dependencies": { - "@firebase/auth-interop-types": "^0.1.5", "@firebase/database": "^0.6.10", "@firebase/database-types": "^0.5.2", "@types/node": "^10.10.0", diff --git a/src/database/index.ts b/src/database/index.ts index 9f102fcc20..30c6b1143a 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -40,15 +40,13 @@ export namespace admin.database { /* eslint-disable @typescript-eslint/no-unused-vars */ // See https://github.com/typescript-eslint/typescript-eslint/issues/363 export import DataSnapshot = firebaseDbTypesApi.DataSnapshot; - export import OnDisconnect = firebaseDbTypesApi.OnDisconnect; + export import Database = adminDb.Database; export import EventType = firebaseDbTypesApi.EventType; + export import OnDisconnect = firebaseDbTypesApi.OnDisconnect; export import Query = firebaseDbTypesApi.Query; export import Reference = firebaseDbTypesApi.Reference; export import ThenableReference = firebaseDbTypesApi.ThenableReference; export import enableLogging = firebaseDbTypesApi.enableLogging; + export const ServerValue: firebaseDbTypesApi.ServerValue = sv; - // There is a known bug where @firebase/database-types FirebaseDatabase - // cannot be used as an interface for @firebase/database Database. - // See https://github.com/firebase/firebase-js-sdk/issues/3476 - export import Database = adminDb.Database; } diff --git a/src/firebase-namespace.ts b/src/firebase-namespace.ts index 5c0083478a..89bd79928f 100644 --- a/src/firebase-namespace.ts +++ b/src/firebase-namespace.ts @@ -31,7 +31,7 @@ import { Auth } from './auth/auth'; import { MachineLearning } from './machine-learning/machine-learning'; import { Messaging } from './messaging/messaging'; import { Storage } from './storage/storage'; -import { FirebaseDatabase } from '@firebase/database-types'; +import { Database as FirebaseDatabase } from './database/database'; import { Firestore } from '@google-cloud/firestore'; import { InstanceId } from './instance-id/instance-id'; import { ProjectManagement } from './project-management/project-management'; diff --git a/test/unit/firebase-app.spec.ts b/test/unit/firebase-app.spec.ts index d384fd6a0d..849e0b8618 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/firebase-app.spec.ts @@ -35,7 +35,7 @@ import { Messaging } from '../../src/messaging/messaging'; import { MachineLearning } from '../../src/machine-learning/machine-learning'; import { Storage } from '../../src/storage/storage'; import { Firestore } from '@google-cloud/firestore'; -import { FirebaseDatabase } from '@firebase/database-types'; +import { Database as FirebaseDatabase } from '../../src/database/database'; import { InstanceId } from '../../src/instance-id/instance-id'; import { ProjectManagement } from '../../src/project-management/project-management'; import { SecurityRules } from '../../src/security-rules/security-rules'; diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/firebase-namespace.spec.ts index f75892405e..4cbc98df5e 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/firebase-namespace.spec.ts @@ -36,7 +36,7 @@ import { Reference, ServerValue, } from '@firebase/database'; -import { FirebaseDatabase } from '@firebase/database-types'; +import { Database as FirebaseDatabase } from '../../src/database/database'; import { Messaging } from '../../src/messaging/messaging'; import { MachineLearning } from '../../src/machine-learning/machine-learning'; import { Storage } from '../../src/storage/storage'; From 200408bef6aa6adf4fe15582b5b6eed408f9dd39 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 12 Aug 2020 10:17:15 -0400 Subject: [PATCH 44/46] fix: Do not alias Database to FirebaseDatabase --- test/unit/firebase-app.spec.ts | 18 +++++++++--------- test/unit/firebase-namespace.spec.ts | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/unit/firebase-app.spec.ts b/test/unit/firebase-app.spec.ts index 849e0b8618..2b3b9591d8 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/firebase-app.spec.ts @@ -35,7 +35,7 @@ import { Messaging } from '../../src/messaging/messaging'; import { MachineLearning } from '../../src/machine-learning/machine-learning'; import { Storage } from '../../src/storage/storage'; import { Firestore } from '@google-cloud/firestore'; -import { Database as FirebaseDatabase } from '../../src/database/database'; +import { Database } from '../../src/database/database'; import { InstanceId } from '../../src/instance-id/instance-id'; import { ProjectManagement } from '../../src/project-management/project-management'; import { SecurityRules } from '../../src/security-rules/security-rules'; @@ -444,15 +444,15 @@ describe('FirebaseApp', () => { it('should return the Database', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - const db: FirebaseDatabase = app.database(); + const db: Database = app.database(); expect(db).not.be.null; }); it('should return the Database for different apps', () => { const app1 = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); const app2 = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName + '-other'); - const db1: FirebaseDatabase = app1.database(); - const db2: FirebaseDatabase = app2.database(); + const db1: Database = app1.database(); + const db2: Database = app2.database(); expect(db1).to.not.deep.equal(db2); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); expect(db2.ref().toString()).to.equal('https://databasename.firebaseio.com/'); @@ -468,9 +468,9 @@ describe('FirebaseApp', () => { it('should return a cached version of Database on subsequent calls', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - const db1: FirebaseDatabase = app.database(); - const db2: FirebaseDatabase = app.database(); - const db3: FirebaseDatabase = app.database(mocks.appOptions.databaseURL); + const db1: Database = app.database(); + const db2: Database = app.database(); + const db3: Database = app.database(mocks.appOptions.databaseURL); expect(db1).to.equal(db2); expect(db1).to.equal(db3); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); @@ -478,8 +478,8 @@ describe('FirebaseApp', () => { it('should return a Database instance for the specified URL', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - const db1: FirebaseDatabase = app.database(); - const db2: FirebaseDatabase = app.database('https://other-database.firebaseio.com'); + const db1: Database = app.database(); + const db2: Database = app.database('https://other-database.firebaseio.com'); expect(db1.ref().toString()).to.equal('https://databasename.firebaseio.com/'); expect(db2.ref().toString()).to.equal('https://other-database.firebaseio.com/'); }); diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/firebase-namespace.spec.ts index 4cbc98df5e..8dd3c84105 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/firebase-namespace.spec.ts @@ -36,7 +36,7 @@ import { Reference, ServerValue, } from '@firebase/database'; -import { Database as FirebaseDatabase } from '../../src/database/database'; +import { Database } from '../../src/database/database'; import { Messaging } from '../../src/messaging/messaging'; import { MachineLearning } from '../../src/machine-learning/machine-learning'; import { Storage } from '../../src/storage/storage'; @@ -405,14 +405,14 @@ describe('FirebaseNamespace', () => { it('should return a valid namespace when the default app is initialized', () => { const app: FirebaseApp = firebaseNamespace.initializeApp(mocks.appOptions); - const db: FirebaseDatabase = firebaseNamespace.database(); + const db: Database = firebaseNamespace.database(); expect(db.app).to.be.deep.equal(app); return app.delete(); }); it('should return a valid namespace when the named app is initialized', () => { const app: FirebaseApp = firebaseNamespace.initializeApp(mocks.appOptions, 'testApp'); - const db: FirebaseDatabase = firebaseNamespace.database(app); + const db: Database = firebaseNamespace.database(app); expect(db.app).to.be.deep.equal(app); return app.delete(); }); From 8a202145574270c3dd3386105b685d26695156d4 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 12 Aug 2020 10:33:56 -0400 Subject: [PATCH 45/46] fix: Do not duplicate imports --- src/firebase-namespace.ts | 6 +++--- test/unit/firebase-namespace.spec.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/firebase-namespace.ts b/src/firebase-namespace.ts index 89bd79928f..b81b3c6b91 100644 --- a/src/firebase-namespace.ts +++ b/src/firebase-namespace.ts @@ -31,7 +31,7 @@ import { Auth } from './auth/auth'; import { MachineLearning } from './machine-learning/machine-learning'; import { Messaging } from './messaging/messaging'; import { Storage } from './storage/storage'; -import { Database as FirebaseDatabase } from './database/database'; +import { Database } from './database/database'; import { Firestore } from '@google-cloud/firestore'; import { InstanceId } from './instance-id/instance-id'; import { ProjectManagement } from './project-management/project-management'; @@ -339,8 +339,8 @@ export class FirebaseNamespace { * Gets the `Database` service namespace. The returned namespace can be used to get the * `Database` service for the default app or an explicitly specified app. */ - get database(): FirebaseServiceNamespace { - const fn: FirebaseServiceNamespace = (app?: FirebaseApp) => { + get database(): FirebaseServiceNamespace { + const fn: FirebaseServiceNamespace = (app?: FirebaseApp) => { return this.ensureApp(app).database(); }; diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/firebase-namespace.spec.ts index 8dd3c84105..4cbc98df5e 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/firebase-namespace.spec.ts @@ -36,7 +36,7 @@ import { Reference, ServerValue, } from '@firebase/database'; -import { Database } from '../../src/database/database'; +import { Database as FirebaseDatabase } from '../../src/database/database'; import { Messaging } from '../../src/messaging/messaging'; import { MachineLearning } from '../../src/machine-learning/machine-learning'; import { Storage } from '../../src/storage/storage'; @@ -405,14 +405,14 @@ describe('FirebaseNamespace', () => { it('should return a valid namespace when the default app is initialized', () => { const app: FirebaseApp = firebaseNamespace.initializeApp(mocks.appOptions); - const db: Database = firebaseNamespace.database(); + const db: FirebaseDatabase = firebaseNamespace.database(); expect(db.app).to.be.deep.equal(app); return app.delete(); }); it('should return a valid namespace when the named app is initialized', () => { const app: FirebaseApp = firebaseNamespace.initializeApp(mocks.appOptions, 'testApp'); - const db: Database = firebaseNamespace.database(app); + const db: FirebaseDatabase = firebaseNamespace.database(app); expect(db.app).to.be.deep.equal(app); return app.delete(); }); From dce5640bf573b0abb869a19115dbb8b051e1e94b Mon Sep 17 00:00:00 2001 From: MathBunny Date: Wed, 12 Aug 2020 10:38:31 -0400 Subject: [PATCH 46/46] fix: Add back getDatabase() call --- test/unit/database/database.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index 75558267f5..415ce4a94a 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -340,7 +340,7 @@ describe('Database', () => { }); it('should set the rules when specified as an object', () => { - const db: Database = database.app.database(); + const db: Database = database.getDatabase(); const stub = stubSuccessfulResponse({}); return db.setRules(rules).then(() => { return expect(stub).to.have.been.calledOnce.and.calledWith(