UNPKG

10.3 kBJavaScriptView Raw
1/*! firebase-admin v10.0.0 */
2"use strict";
3/*!
4 * Copyright 2020 Google Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.DatabaseService = void 0;
20var url_1 = require("url");
21var path = require("path");
22var error_1 = require("../utils/error");
23var validator = require("../utils/validator");
24var api_request_1 = require("../utils/api-request");
25var index_1 = require("../utils/index");
26var TOKEN_REFRESH_THRESHOLD_MILLIS = 5 * 60 * 1000;
27var DatabaseService = /** @class */ (function () {
28 function DatabaseService(app) {
29 this.databases = {};
30 if (!validator.isNonNullObject(app) || !('options' in app)) {
31 throw new error_1.FirebaseDatabaseError({
32 code: 'invalid-argument',
33 message: 'First argument passed to admin.database() must be a valid Firebase app instance.',
34 });
35 }
36 this.appInternal = app;
37 }
38 Object.defineProperty(DatabaseService.prototype, "firebaseApp", {
39 get: function () {
40 return this.app;
41 },
42 enumerable: false,
43 configurable: true
44 });
45 /**
46 * @internal
47 */
48 DatabaseService.prototype.delete = function () {
49 var _this = this;
50 if (this.tokenListener) {
51 this.firebaseApp.INTERNAL.removeAuthTokenListener(this.tokenListener);
52 clearTimeout(this.tokenRefreshTimeout);
53 }
54 var promises = [];
55 for (var _i = 0, _a = Object.keys(this.databases); _i < _a.length; _i++) {
56 var dbUrl = _a[_i];
57 var db = this.databases[dbUrl];
58 promises.push(db.INTERNAL.delete());
59 }
60 return Promise.all(promises).then(function () {
61 _this.databases = {};
62 });
63 };
64 Object.defineProperty(DatabaseService.prototype, "app", {
65 /**
66 * Returns the app associated with this DatabaseService instance.
67 *
68 * @returns The app associated with this DatabaseService instance.
69 */
70 get: function () {
71 return this.appInternal;
72 },
73 enumerable: false,
74 configurable: true
75 });
76 DatabaseService.prototype.getDatabase = function (url) {
77 var dbUrl = this.ensureUrl(url);
78 if (!validator.isNonEmptyString(dbUrl)) {
79 throw new error_1.FirebaseDatabaseError({
80 code: 'invalid-argument',
81 message: 'Database URL must be a valid, non-empty URL string.',
82 });
83 }
84 var db = this.databases[dbUrl];
85 if (typeof db === 'undefined') {
86 // eslint-disable-next-line @typescript-eslint/no-var-requires
87 var rtdb = require('@firebase/database-compat/standalone');
88 db = rtdb.initStandalone(this.appInternal, dbUrl, index_1.getSdkVersion()).instance;
89 var rulesClient_1 = new DatabaseRulesClient(this.app, dbUrl);
90 db.getRules = function () {
91 return rulesClient_1.getRules();
92 };
93 db.getRulesJSON = function () {
94 return rulesClient_1.getRulesJSON();
95 };
96 db.setRules = function (source) {
97 return rulesClient_1.setRules(source);
98 };
99 this.databases[dbUrl] = db;
100 }
101 if (!this.tokenListener) {
102 this.tokenListener = this.onTokenChange.bind(this);
103 this.firebaseApp.INTERNAL.addAuthTokenListener(this.tokenListener);
104 }
105 return db;
106 };
107 // eslint-disable-next-line @typescript-eslint/no-unused-vars
108 DatabaseService.prototype.onTokenChange = function (_) {
109 var token = this.firebaseApp.INTERNAL.getCachedToken();
110 if (token) {
111 var delayMillis = token.expirationTime - TOKEN_REFRESH_THRESHOLD_MILLIS - Date.now();
112 // If the new token is set to expire soon (unlikely), do nothing. Somebody will eventually
113 // notice and refresh the token, at which point this callback will fire again.
114 if (delayMillis > 0) {
115 this.scheduleTokenRefresh(delayMillis);
116 }
117 }
118 };
119 DatabaseService.prototype.scheduleTokenRefresh = function (delayMillis) {
120 var _this = this;
121 clearTimeout(this.tokenRefreshTimeout);
122 this.tokenRefreshTimeout = setTimeout(function () {
123 _this.firebaseApp.INTERNAL.getToken(/*forceRefresh=*/ true)
124 .catch(function () {
125 // Ignore the error since this might just be an intermittent failure. If we really cannot
126 // refresh the token, an error will be logged once the existing token expires and we try
127 // to fetch a fresh one.
128 });
129 }, delayMillis);
130 };
131 DatabaseService.prototype.ensureUrl = function (url) {
132 if (typeof url !== 'undefined') {
133 return url;
134 }
135 else if (typeof this.appInternal.options.databaseURL !== 'undefined') {
136 return this.appInternal.options.databaseURL;
137 }
138 throw new error_1.FirebaseDatabaseError({
139 code: 'invalid-argument',
140 message: 'Can\'t determine Firebase Database URL.',
141 });
142 };
143 return DatabaseService;
144}());
145exports.DatabaseService = DatabaseService;
146var RULES_URL_PATH = '.settings/rules.json';
147/**
148 * A helper client for managing RTDB security rules.
149 */
150var DatabaseRulesClient = /** @class */ (function () {
151 function DatabaseRulesClient(app, dbUrl) {
152 var parsedUrl = new url_1.URL(dbUrl);
153 var emulatorHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST;
154 if (emulatorHost) {
155 var namespace = extractNamespace(parsedUrl);
156 parsedUrl = new url_1.URL("http://" + emulatorHost + "?ns=" + namespace);
157 }
158 parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH);
159 this.dbUrl = parsedUrl.toString();
160 this.httpClient = new api_request_1.AuthorizedHttpClient(app);
161 }
162 /**
163 * Gets the currently applied security rules as a string. The return value consists of
164 * the rules source including comments.
165 *
166 * @returns A promise fulfilled with the rules as a raw string.
167 */
168 DatabaseRulesClient.prototype.getRules = function () {
169 var _this = this;
170 var req = {
171 method: 'GET',
172 url: this.dbUrl,
173 };
174 return this.httpClient.send(req)
175 .then(function (resp) {
176 if (!resp.text) {
177 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INTERNAL_ERROR, 'HTTP response missing data.');
178 }
179 return resp.text;
180 })
181 .catch(function (err) {
182 throw _this.handleError(err);
183 });
184 };
185 /**
186 * Gets the currently applied security rules as a parsed JSON object. Any comments in
187 * the original source are stripped away.
188 *
189 * @returns {Promise<object>} A promise fulfilled with the parsed rules source.
190 */
191 DatabaseRulesClient.prototype.getRulesJSON = function () {
192 var _this = this;
193 var req = {
194 method: 'GET',
195 url: this.dbUrl,
196 data: { format: 'strict' },
197 };
198 return this.httpClient.send(req)
199 .then(function (resp) {
200 return resp.data;
201 })
202 .catch(function (err) {
203 throw _this.handleError(err);
204 });
205 };
206 /**
207 * Sets the specified rules on the Firebase Database instance. If the rules source is
208 * specified as a string or a Buffer, it may include comments.
209 *
210 * @param {string|Buffer|object} source Source of the rules to apply. Must not be `null`
211 * or empty.
212 * @returns {Promise<void>} Resolves when the rules are set on the Database.
213 */
214 DatabaseRulesClient.prototype.setRules = function (source) {
215 var _this = this;
216 if (!validator.isNonEmptyString(source) &&
217 !validator.isBuffer(source) &&
218 !validator.isNonNullObject(source)) {
219 var error = new error_1.FirebaseDatabaseError({
220 code: 'invalid-argument',
221 message: 'Source must be a non-empty string, Buffer or an object.',
222 });
223 return Promise.reject(error);
224 }
225 var req = {
226 method: 'PUT',
227 url: this.dbUrl,
228 data: source,
229 headers: {
230 'content-type': 'application/json; charset=utf-8',
231 },
232 };
233 return this.httpClient.send(req)
234 .then(function () {
235 return;
236 })
237 .catch(function (err) {
238 throw _this.handleError(err);
239 });
240 };
241 DatabaseRulesClient.prototype.handleError = function (err) {
242 if (err instanceof api_request_1.HttpError) {
243 return new error_1.FirebaseDatabaseError({
244 code: error_1.AppErrorCodes.INTERNAL_ERROR,
245 message: this.getErrorMessage(err),
246 });
247 }
248 return err;
249 };
250 DatabaseRulesClient.prototype.getErrorMessage = function (err) {
251 var intro = 'Error while accessing security rules';
252 try {
253 var body = err.response.data;
254 if (body && body.error) {
255 return intro + ": " + body.error.trim();
256 }
257 }
258 catch (_a) {
259 // Ignore parsing errors
260 }
261 return intro + ": " + err.response.text;
262 };
263 return DatabaseRulesClient;
264}());
265function extractNamespace(parsedUrl) {
266 var ns = parsedUrl.searchParams.get('ns');
267 if (ns) {
268 return ns;
269 }
270 var hostname = parsedUrl.hostname;
271 var dotIndex = hostname.indexOf('.');
272 return hostname.substring(0, dotIndex).toLowerCase();
273}