123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- /*! firebase-admin v12.1.1 */
- "use strict";
- /*!
- * 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.
- */
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.DatabaseService = void 0;
- const url_1 = require("url");
- const path = require("path");
- const error_1 = require("../utils/error");
- const validator = require("../utils/validator");
- const api_request_1 = require("../utils/api-request");
- const index_1 = require("../utils/index");
- const TOKEN_REFRESH_THRESHOLD_MILLIS = 5 * 60 * 1000;
- class DatabaseService {
- constructor(app) {
- this.databases = {};
- if (!validator.isNonNullObject(app) || !('options' in app)) {
- throw new error_1.FirebaseDatabaseError({
- code: 'invalid-argument',
- message: 'First argument passed to admin.database() must be a valid Firebase app instance.',
- });
- }
- this.appInternal = app;
- }
- get firebaseApp() {
- return this.app;
- }
- /**
- * @internal
- */
- delete() {
- if (this.tokenListener) {
- this.firebaseApp.INTERNAL.removeAuthTokenListener(this.tokenListener);
- clearTimeout(this.tokenRefreshTimeout);
- }
- const promises = [];
- for (const dbUrl of Object.keys(this.databases)) {
- const db = this.databases[dbUrl];
- promises.push(db.INTERNAL.delete());
- }
- return Promise.all(promises).then(() => {
- this.databases = {};
- });
- }
- /**
- * Returns the app associated with this DatabaseService instance.
- *
- * @returns The app associated with this DatabaseService instance.
- */
- get app() {
- return this.appInternal;
- }
- getDatabase(url) {
- const dbUrl = this.ensureUrl(url);
- if (!validator.isNonEmptyString(dbUrl)) {
- throw new error_1.FirebaseDatabaseError({
- code: 'invalid-argument',
- message: 'Database URL must be a valid, non-empty URL string.',
- });
- }
- let db = this.databases[dbUrl];
- if (typeof db === 'undefined') {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const rtdb = require('@firebase/database-compat/standalone');
- db = rtdb.initStandalone(this.appInternal, dbUrl, (0, index_1.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.databases[dbUrl] = db;
- }
- if (!this.tokenListener) {
- this.tokenListener = this.onTokenChange.bind(this);
- this.firebaseApp.INTERNAL.addAuthTokenListener(this.tokenListener);
- }
- return db;
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onTokenChange(_) {
- const token = this.firebaseApp.INTERNAL.getCachedToken();
- if (token) {
- const delayMillis = token.expirationTime - TOKEN_REFRESH_THRESHOLD_MILLIS - Date.now();
- // If the new token is set to expire soon (unlikely), do nothing. Somebody will eventually
- // notice and refresh the token, at which point this callback will fire again.
- if (delayMillis > 0) {
- this.scheduleTokenRefresh(delayMillis);
- }
- }
- }
- scheduleTokenRefresh(delayMillis) {
- clearTimeout(this.tokenRefreshTimeout);
- this.tokenRefreshTimeout = setTimeout(() => {
- this.firebaseApp.INTERNAL.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.
- });
- }, delayMillis);
- }
- ensureUrl(url) {
- if (typeof url !== 'undefined') {
- return url;
- }
- else if (typeof this.appInternal.options.databaseURL !== 'undefined') {
- return this.appInternal.options.databaseURL;
- }
- throw new error_1.FirebaseDatabaseError({
- code: 'invalid-argument',
- message: 'Can\'t determine Firebase Database URL.',
- });
- }
- }
- exports.DatabaseService = DatabaseService;
- const RULES_URL_PATH = '.settings/rules.json';
- /**
- * A helper client for managing RTDB security rules.
- */
- class DatabaseRulesClient {
- constructor(app, dbUrl) {
- let parsedUrl = new url_1.URL(dbUrl);
- const emulatorHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST;
- if (emulatorHost) {
- const namespace = extractNamespace(parsedUrl);
- parsedUrl = new url_1.URL(`http://${emulatorHost}?ns=${namespace}`);
- }
- parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH);
- this.dbUrl = parsedUrl.toString();
- this.httpClient = new api_request_1.AuthorizedHttpClient(app);
- }
- /**
- * Gets the currently applied security rules as a string. The return value consists of
- * the rules source including comments.
- *
- * @returns A promise fulfilled with the rules as a raw string.
- */
- getRules() {
- const req = {
- method: 'GET',
- url: this.dbUrl,
- };
- return this.httpClient.send(req)
- .then((resp) => {
- if (!resp.text) {
- throw new error_1.FirebaseAppError(error_1.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.
- *
- * @returns {Promise<object>} A promise fulfilled with the parsed rules source.
- */
- getRulesJSON() {
- const req = {
- 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.
- * @returns {Promise<void>} Resolves when the rules are set on the Database.
- */
- setRules(source) {
- if (!validator.isNonEmptyString(source) &&
- !validator.isBuffer(source) &&
- !validator.isNonNullObject(source)) {
- const error = new error_1.FirebaseDatabaseError({
- code: 'invalid-argument',
- message: 'Source must be a non-empty string, Buffer or an object.',
- });
- return Promise.reject(error);
- }
- const req = {
- 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);
- });
- }
- handleError(err) {
- if (err instanceof api_request_1.HttpError) {
- return new error_1.FirebaseDatabaseError({
- code: error_1.AppErrorCodes.INTERNAL_ERROR,
- message: this.getErrorMessage(err),
- });
- }
- return err;
- }
- getErrorMessage(err) {
- const intro = 'Error while accessing security rules';
- try {
- const body = err.response.data;
- if (body && body.error) {
- return `${intro}: ${body.error.trim()}`;
- }
- }
- catch {
- // Ignore parsing errors
- }
- return `${intro}: ${err.response.text}`;
- }
- }
- function extractNamespace(parsedUrl) {
- const ns = parsedUrl.searchParams.get('ns');
- if (ns) {
- return ns;
- }
- const hostname = parsedUrl.hostname;
- const dotIndex = hostname.indexOf('.');
- return hostname.substring(0, dotIndex).toLowerCase();
- }
|