@firebase/database
Version:
This is the Firebase Realtime Database component of the Firebase JS SDK.
1,320 lines (1,305 loc) • 602 kB
JavaScript
import { getApp, _getProvider, _registerComponent, registerVersion, SDK_VERSION as SDK_VERSION$1 } from '@firebase/app';
import { Component, ComponentContainer, Provider } from '@firebase/component';
import { __spreadArray, __read, __values, __assign, __extends, __awaiter, __generator } from 'tslib';
import { stringify, jsonEval, contains, assert, isNodeSdk, stringToByteArray, Sha1, base64, deepCopy, base64Encode, isMobileCordova, stringLength, Deferred, safeGet, isAdmin, isValidFormat, isEmpty, isReactNative, assertionError, map, querystring, errorPrefix, getModularInstance, getDefaultEmulatorHostnameAndPort, createMockUserToken } from '@firebase/util';
import { Logger, LogLevel } from '@firebase/logger';
var name = "@firebase/database";
var version = "1.0.1";
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** The semver (www.semver.org) version of the SDK. */
var SDK_VERSION = '';
/**
* SDK_VERSION should be set before any database instance is created
* @internal
*/
function setSDKVersion(version) {
SDK_VERSION = version;
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Wraps a DOM Storage object and:
* - automatically encode objects as JSON strings before storing them to allow us to store arbitrary types.
* - prefixes names with "firebase:" to avoid collisions with app data.
*
* We automatically (see storage.js) create two such wrappers, one for sessionStorage,
* and one for localStorage.
*
*/
var DOMStorageWrapper = /** @class */ (function () {
/**
* @param domStorage_ - The underlying storage object (e.g. localStorage or sessionStorage)
*/
function DOMStorageWrapper(domStorage_) {
this.domStorage_ = domStorage_;
// Use a prefix to avoid collisions with other stuff saved by the app.
this.prefix_ = 'firebase:';
}
/**
* @param key - The key to save the value under
* @param value - The value being stored, or null to remove the key.
*/
DOMStorageWrapper.prototype.set = function (key, value) {
if (value == null) {
this.domStorage_.removeItem(this.prefixedName_(key));
}
else {
this.domStorage_.setItem(this.prefixedName_(key), stringify(value));
}
};
/**
* @returns The value that was stored under this key, or null
*/
DOMStorageWrapper.prototype.get = function (key) {
var storedVal = this.domStorage_.getItem(this.prefixedName_(key));
if (storedVal == null) {
return null;
}
else {
return jsonEval(storedVal);
}
};
DOMStorageWrapper.prototype.remove = function (key) {
this.domStorage_.removeItem(this.prefixedName_(key));
};
DOMStorageWrapper.prototype.prefixedName_ = function (name) {
return this.prefix_ + name;
};
DOMStorageWrapper.prototype.toString = function () {
return this.domStorage_.toString();
};
return DOMStorageWrapper;
}());
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* An in-memory storage implementation that matches the API of DOMStorageWrapper
* (TODO: create interface for both to implement).
*/
var MemoryStorage = /** @class */ (function () {
function MemoryStorage() {
this.cache_ = {};
this.isInMemoryStorage = true;
}
MemoryStorage.prototype.set = function (key, value) {
if (value == null) {
delete this.cache_[key];
}
else {
this.cache_[key] = value;
}
};
MemoryStorage.prototype.get = function (key) {
if (contains(this.cache_, key)) {
return this.cache_[key];
}
return null;
};
MemoryStorage.prototype.remove = function (key) {
delete this.cache_[key];
};
return MemoryStorage;
}());
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Helper to create a DOMStorageWrapper or else fall back to MemoryStorage.
* TODO: Once MemoryStorage and DOMStorageWrapper have a shared interface this method annotation should change
* to reflect this type
*
* @param domStorageName - Name of the underlying storage object
* (e.g. 'localStorage' or 'sessionStorage').
* @returns Turning off type information until a common interface is defined.
*/
var createStoragefor = function (domStorageName) {
try {
// NOTE: just accessing "localStorage" or "window['localStorage']" may throw a security exception,
// so it must be inside the try/catch.
if (typeof window !== 'undefined' &&
typeof window[domStorageName] !== 'undefined') {
// Need to test cache. Just because it's here doesn't mean it works
var domStorage = window[domStorageName];
domStorage.setItem('firebase:sentinel', 'cache');
domStorage.removeItem('firebase:sentinel');
return new DOMStorageWrapper(domStorage);
}
}
catch (e) { }
// Failed to create wrapper. Just return in-memory storage.
// TODO: log?
return new MemoryStorage();
};
/** A storage object that lasts across sessions */
var PersistentStorage = createStoragefor('localStorage');
/** A storage object that only lasts one session */
var SessionStorage = createStoragefor('sessionStorage');
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var logClient = new Logger('@firebase/database');
/**
* Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
*/
var LUIDGenerator = (function () {
var id = 1;
return function () {
return id++;
};
})();
/**
* Sha1 hash of the input string
* @param str - The string to hash
* @returns {!string} The resulting hash
*/
var sha1 = function (str) {
var utf8Bytes = stringToByteArray(str);
var sha1 = new Sha1();
sha1.update(utf8Bytes);
var sha1Bytes = sha1.digest();
return base64.encodeByteArray(sha1Bytes);
};
var buildLogMessage_ = function () {
var varArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
varArgs[_i] = arguments[_i];
}
var message = '';
for (var i = 0; i < varArgs.length; i++) {
var arg = varArgs[i];
if (Array.isArray(arg) ||
(arg &&
typeof arg === 'object' &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
typeof arg.length === 'number')) {
message += buildLogMessage_.apply(null, arg);
}
else if (typeof arg === 'object') {
message += stringify(arg);
}
else {
message += arg;
}
message += ' ';
}
return message;
};
/**
* Use this for all debug messages in Firebase.
*/
var logger = null;
/**
* Flag to check for log availability on first log message
*/
var firstLog_ = true;
/**
* The implementation of Firebase.enableLogging (defined here to break dependencies)
* @param logger_ - A flag to turn on logging, or a custom logger
* @param persistent - Whether or not to persist logging settings across refreshes
*/
var enableLogging$1 = function (logger_, persistent) {
assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
if (logger_ === true) {
logClient.logLevel = LogLevel.VERBOSE;
logger = logClient.log.bind(logClient);
if (persistent) {
SessionStorage.set('logging_enabled', true);
}
}
else if (typeof logger_ === 'function') {
logger = logger_;
}
else {
logger = null;
SessionStorage.remove('logging_enabled');
}
};
var log = function () {
var varArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
varArgs[_i] = arguments[_i];
}
if (firstLog_ === true) {
firstLog_ = false;
if (logger === null && SessionStorage.get('logging_enabled') === true) {
enableLogging$1(true);
}
}
if (logger) {
var message = buildLogMessage_.apply(null, varArgs);
logger(message);
}
};
var logWrapper = function (prefix) {
return function () {
var varArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
varArgs[_i] = arguments[_i];
}
log.apply(void 0, __spreadArray([prefix], __read(varArgs), false));
};
};
var error = function () {
var varArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
varArgs[_i] = arguments[_i];
}
var message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_.apply(void 0, __spreadArray([], __read(varArgs), false));
logClient.error(message);
};
var fatal = function () {
var varArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
varArgs[_i] = arguments[_i];
}
var message = "FIREBASE FATAL ERROR: ".concat(buildLogMessage_.apply(void 0, __spreadArray([], __read(varArgs), false)));
logClient.error(message);
throw new Error(message);
};
var warn = function () {
var varArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
varArgs[_i] = arguments[_i];
}
var message = 'FIREBASE WARNING: ' + buildLogMessage_.apply(void 0, __spreadArray([], __read(varArgs), false));
logClient.warn(message);
};
/**
* Logs a warning if the containing page uses https. Called when a call to new Firebase
* does not use https.
*/
var warnIfPageIsSecure = function () {
// Be very careful accessing browser globals. Who knows what may or may not exist.
if (typeof window !== 'undefined' &&
window.location &&
window.location.protocol &&
window.location.protocol.indexOf('https:') !== -1) {
warn('Insecure Firebase access from a secure page. ' +
'Please use https in calls to new Firebase().');
}
};
/**
* Returns true if data is NaN, or +/- Infinity.
*/
var isInvalidJSONNumber = function (data) {
return (typeof data === 'number' &&
(data !== data || // NaN
data === Number.POSITIVE_INFINITY ||
data === Number.NEGATIVE_INFINITY));
};
var executeWhenDOMReady = function (fn) {
if (isNodeSdk() || document.readyState === 'complete') {
fn();
}
else {
// Modeled after jQuery. Try DOMContentLoaded and onreadystatechange (which
// fire before onload), but fall back to onload.
var called_1 = false;
var wrappedFn_1 = function () {
if (!document.body) {
setTimeout(wrappedFn_1, Math.floor(10));
return;
}
if (!called_1) {
called_1 = true;
fn();
}
};
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', wrappedFn_1, false);
// fallback to onload.
window.addEventListener('load', wrappedFn_1, false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
else if (document.attachEvent) {
// IE.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
document.attachEvent('onreadystatechange', function () {
if (document.readyState === 'complete') {
wrappedFn_1();
}
});
// fallback to onload.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.attachEvent('onload', wrappedFn_1);
// jQuery has an extra hack for IE that we could employ (based on
// http://javascript.nwbox.com/IEContentLoaded/) But it looks really old.
// I'm hoping we don't need it.
}
}
};
/**
* Minimum key name. Invalid for actual data, used as a marker to sort before any valid names
*/
var MIN_NAME = '[MIN_NAME]';
/**
* Maximum key name. Invalid for actual data, used as a marker to sort above any valid names
*/
var MAX_NAME = '[MAX_NAME]';
/**
* Compares valid Firebase key names, plus min and max name
*/
var nameCompare = function (a, b) {
if (a === b) {
return 0;
}
else if (a === MIN_NAME || b === MAX_NAME) {
return -1;
}
else if (b === MIN_NAME || a === MAX_NAME) {
return 1;
}
else {
var aAsInt = tryParseInt(a), bAsInt = tryParseInt(b);
if (aAsInt !== null) {
if (bAsInt !== null) {
return aAsInt - bAsInt === 0 ? a.length - b.length : aAsInt - bAsInt;
}
else {
return -1;
}
}
else if (bAsInt !== null) {
return 1;
}
else {
return a < b ? -1 : 1;
}
}
};
/**
* @returns {!number} comparison result.
*/
var stringCompare = function (a, b) {
if (a === b) {
return 0;
}
else if (a < b) {
return -1;
}
else {
return 1;
}
};
var requireKey = function (key, obj) {
if (obj && key in obj) {
return obj[key];
}
else {
throw new Error('Missing required key (' + key + ') in object: ' + stringify(obj));
}
};
var ObjectToUniqueKey = function (obj) {
if (typeof obj !== 'object' || obj === null) {
return stringify(obj);
}
var keys = [];
// eslint-disable-next-line guard-for-in
for (var k in obj) {
keys.push(k);
}
// Export as json, but with the keys sorted.
keys.sort();
var key = '{';
for (var i = 0; i < keys.length; i++) {
if (i !== 0) {
key += ',';
}
key += stringify(keys[i]);
key += ':';
key += ObjectToUniqueKey(obj[keys[i]]);
}
key += '}';
return key;
};
/**
* Splits a string into a number of smaller segments of maximum size
* @param str - The string
* @param segsize - The maximum number of chars in the string.
* @returns The string, split into appropriately-sized chunks
*/
var splitStringBySize = function (str, segsize) {
var len = str.length;
if (len <= segsize) {
return [str];
}
var dataSegs = [];
for (var c = 0; c < len; c += segsize) {
if (c + segsize > len) {
dataSegs.push(str.substring(c, len));
}
else {
dataSegs.push(str.substring(c, c + segsize));
}
}
return dataSegs;
};
/**
* Apply a function to each (key, value) pair in an object or
* apply a function to each (index, value) pair in an array
* @param obj - The object or array to iterate over
* @param fn - The function to apply
*/
function each(obj, fn) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
fn(key, obj[key]);
}
}
}
/**
* Borrowed from http://hg.secondlife.com/llsd/src/tip/js/typedarray.js (MIT License)
* I made one modification at the end and removed the NaN / Infinity
* handling (since it seemed broken [caused an overflow] and we don't need it). See MJL comments.
* @param v - A double
*
*/
var doubleToIEEE754String = function (v) {
assert(!isInvalidJSONNumber(v), 'Invalid JSON number'); // MJL
var ebits = 11, fbits = 52;
var bias = (1 << (ebits - 1)) - 1;
var s, e, f, ln, i;
// Compute sign, exponent, fraction
// Skip NaN / Infinity handling --MJL.
if (v === 0) {
e = 0;
f = 0;
s = 1 / v === -Infinity ? 1 : 0;
}
else {
s = v < 0;
v = Math.abs(v);
if (v >= Math.pow(2, 1 - bias)) {
// Normalized
ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
e = ln + bias;
f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
}
else {
// Denormalized
e = 0;
f = Math.round(v / Math.pow(2, 1 - bias - fbits));
}
}
// Pack sign, exponent, fraction
var bits = [];
for (i = fbits; i; i -= 1) {
bits.push(f % 2 ? 1 : 0);
f = Math.floor(f / 2);
}
for (i = ebits; i; i -= 1) {
bits.push(e % 2 ? 1 : 0);
e = Math.floor(e / 2);
}
bits.push(s ? 1 : 0);
bits.reverse();
var str = bits.join('');
// Return the data as a hex string. --MJL
var hexByteString = '';
for (i = 0; i < 64; i += 8) {
var hexByte = parseInt(str.substr(i, 8), 2).toString(16);
if (hexByte.length === 1) {
hexByte = '0' + hexByte;
}
hexByteString = hexByteString + hexByte;
}
return hexByteString.toLowerCase();
};
/**
* Used to detect if we're in a Chrome content script (which executes in an
* isolated environment where long-polling doesn't work).
*/
var isChromeExtensionContentScript = function () {
return !!(typeof window === 'object' &&
window['chrome'] &&
window['chrome']['extension'] &&
!/^chrome/.test(window.location.href));
};
/**
* Used to detect if we're in a Windows 8 Store app.
*/
var isWindowsStoreApp = function () {
// Check for the presence of a couple WinRT globals
return typeof Windows === 'object' && typeof Windows.UI === 'object';
};
/**
* Converts a server error code to a Javascript Error
*/
function errorForServerCode(code, query) {
var reason = 'Unknown Error';
if (code === 'too_big') {
reason =
'The data requested exceeds the maximum size ' +
'that can be accessed with a single request.';
}
else if (code === 'permission_denied') {
reason = "Client doesn't have permission to access the desired data.";
}
else if (code === 'unavailable') {
reason = 'The service is unavailable';
}
var error = new Error(code + ' at ' + query._path.toString() + ': ' + reason);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error.code = code.toUpperCase();
return error;
}
/**
* Used to test for integer-looking strings
*/
var INTEGER_REGEXP_ = new RegExp('^-?(0*)\\d{1,10}$');
/**
* For use in keys, the minimum possible 32-bit integer.
*/
var INTEGER_32_MIN = -2147483648;
/**
* For use in kyes, the maximum possible 32-bit integer.
*/
var INTEGER_32_MAX = 2147483647;
/**
* If the string contains a 32-bit integer, return it. Else return null.
*/
var tryParseInt = function (str) {
if (INTEGER_REGEXP_.test(str)) {
var intVal = Number(str);
if (intVal >= INTEGER_32_MIN && intVal <= INTEGER_32_MAX) {
return intVal;
}
}
return null;
};
/**
* Helper to run some code but catch any exceptions and re-throw them later.
* Useful for preventing user callbacks from breaking internal code.
*
* Re-throwing the exception from a setTimeout is a little evil, but it's very
* convenient (we don't have to try to figure out when is a safe point to
* re-throw it), and the behavior seems reasonable:
*
* * If you aren't pausing on exceptions, you get an error in the console with
* the correct stack trace.
* * If you're pausing on all exceptions, the debugger will pause on your
* exception and then again when we rethrow it.
* * If you're only pausing on uncaught exceptions, the debugger will only pause
* on us re-throwing it.
*
* @param fn - The code to guard.
*/
var exceptionGuard = function (fn) {
try {
fn();
}
catch (e) {
// Re-throw exception when it's safe.
setTimeout(function () {
// It used to be that "throw e" would result in a good console error with
// relevant context, but as of Chrome 39, you just get the firebase.js
// file/line number where we re-throw it, which is useless. So we log
// e.stack explicitly.
var stack = e.stack || '';
warn('Exception was thrown by user callback.', stack);
throw e;
}, Math.floor(0));
}
};
/**
* @returns {boolean} true if we think we're currently being crawled.
*/
var beingCrawled = function () {
var userAgent = (typeof window === 'object' &&
window['navigator'] &&
window['navigator']['userAgent']) ||
'';
// For now we whitelist the most popular crawlers. We should refine this to be the set of crawlers we
// believe to support JavaScript/AJAX rendering.
// NOTE: Google Webmaster Tools doesn't really belong, but their "This is how a visitor to your website
// would have seen the page" is flaky if we don't treat it as a crawler.
return (userAgent.search(/googlebot|google webmaster tools|bingbot|yahoo! slurp|baiduspider|yandexbot|duckduckbot/i) >= 0);
};
/**
* Same as setTimeout() except on Node.JS it will /not/ prevent the process from exiting.
*
* It is removed with clearTimeout() as normal.
*
* @param fn - Function to run.
* @param time - Milliseconds to wait before running.
* @returns The setTimeout() return value.
*/
var setTimeoutNonBlocking = function (fn, time) {
var timeout = setTimeout(fn, time);
// Note: at the time of this comment, unrefTimer is under the unstable set of APIs. Run with --unstable to enable the API.
if (typeof timeout === 'number' &&
// @ts-ignore Is only defined in Deno environments.
typeof Deno !== 'undefined' &&
// @ts-ignore Deno and unrefTimer are only defined in Deno environments.
Deno['unrefTimer']) {
// @ts-ignore Deno and unrefTimer are only defined in Deno environments.
Deno.unrefTimer(timeout);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
else if (typeof timeout === 'object' && timeout['unref']) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
timeout['unref']();
}
return timeout;
};
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Abstraction around AppCheck's token fetching capabilities.
*/
var AppCheckTokenProvider = /** @class */ (function () {
function AppCheckTokenProvider(appName_, appCheckProvider) {
var _this = this;
this.appName_ = appName_;
this.appCheckProvider = appCheckProvider;
this.appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
if (!this.appCheck) {
appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.get().then(function (appCheck) { return (_this.appCheck = appCheck); });
}
}
AppCheckTokenProvider.prototype.getToken = function (forceRefresh) {
var _this = this;
if (!this.appCheck) {
return new Promise(function (resolve, reject) {
// Support delayed initialization of FirebaseAppCheck. This allows our
// customers to initialize the RTDB SDK before initializing Firebase
// AppCheck and ensures that all requests are authenticated if a token
// becomes available before the timoeout below expires.
setTimeout(function () {
if (_this.appCheck) {
_this.getToken(forceRefresh).then(resolve, reject);
}
else {
resolve(null);
}
}, 0);
});
}
return this.appCheck.getToken(forceRefresh);
};
AppCheckTokenProvider.prototype.addTokenChangeListener = function (listener) {
var _a;
(_a = this.appCheckProvider) === null || _a === void 0 ? void 0 : _a.get().then(function (appCheck) { return appCheck.addTokenListener(listener); });
};
AppCheckTokenProvider.prototype.notifyForInvalidToken = function () {
warn("Provided AppCheck credentials for the app named \"".concat(this.appName_, "\" ") +
'are invalid. This usually indicates your app was not initialized correctly.');
};
return AppCheckTokenProvider;
}());
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Abstraction around FirebaseApp's token fetching capabilities.
*/
var FirebaseAuthTokenProvider = /** @class */ (function () {
function FirebaseAuthTokenProvider(appName_, firebaseOptions_, authProvider_) {
var _this = this;
this.appName_ = appName_;
this.firebaseOptions_ = firebaseOptions_;
this.authProvider_ = authProvider_;
this.auth_ = null;
this.auth_ = authProvider_.getImmediate({ optional: true });
if (!this.auth_) {
authProvider_.onInit(function (auth) { return (_this.auth_ = auth); });
}
}
FirebaseAuthTokenProvider.prototype.getToken = function (forceRefresh) {
var _this = this;
if (!this.auth_) {
return new Promise(function (resolve, reject) {
// Support delayed initialization of FirebaseAuth. This allows our
// customers to initialize the RTDB SDK before initializing Firebase
// Auth and ensures that all requests are authenticated if a token
// becomes available before the timoeout below expires.
setTimeout(function () {
if (_this.auth_) {
_this.getToken(forceRefresh).then(resolve, reject);
}
else {
resolve(null);
}
}, 0);
});
}
return this.auth_.getToken(forceRefresh).catch(function (error) {
// TODO: Need to figure out all the cases this is raised and whether
// this makes sense.
if (error && error.code === 'auth/token-not-initialized') {
log('Got auth/token-not-initialized error. Treating as null token.');
return null;
}
else {
return Promise.reject(error);
}
});
};
FirebaseAuthTokenProvider.prototype.addTokenChangeListener = function (listener) {
// TODO: We might want to wrap the listener and call it with no args to
// avoid a leaky abstraction, but that makes removing the listener harder.
if (this.auth_) {
this.auth_.addAuthTokenListener(listener);
}
else {
this.authProvider_
.get()
.then(function (auth) { return auth.addAuthTokenListener(listener); });
}
};
FirebaseAuthTokenProvider.prototype.removeTokenChangeListener = function (listener) {
this.authProvider_
.get()
.then(function (auth) { return auth.removeAuthTokenListener(listener); });
};
FirebaseAuthTokenProvider.prototype.notifyForInvalidToken = function () {
var errorMessage = 'Provided authentication credentials for the app named "' +
this.appName_ +
'" are invalid. This usually indicates your app was not ' +
'initialized correctly. ';
if ('credential' in this.firebaseOptions_) {
errorMessage +=
'Make sure the "credential" property provided to initializeApp() ' +
'is authorized to access the specified "databaseURL" and is from the correct ' +
'project.';
}
else if ('serviceAccount' in this.firebaseOptions_) {
errorMessage +=
'Make sure the "serviceAccount" property provided to initializeApp() ' +
'is authorized to access the specified "databaseURL" and is from the correct ' +
'project.';
}
else {
errorMessage +=
'Make sure the "apiKey" and "databaseURL" properties provided to ' +
'initializeApp() match the values provided for your app at ' +
'https://console.firebase.google.com/.';
}
warn(errorMessage);
};
return FirebaseAuthTokenProvider;
}());
/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */
var EmulatorTokenProvider = /** @class */ (function () {
function EmulatorTokenProvider(accessToken) {
this.accessToken = accessToken;
}
EmulatorTokenProvider.prototype.getToken = function (forceRefresh) {
return Promise.resolve({
accessToken: this.accessToken
});
};
EmulatorTokenProvider.prototype.addTokenChangeListener = function (listener) {
// Invoke the listener immediately to match the behavior in Firebase Auth
// (see packages/auth/src/auth.js#L1807)
listener(this.accessToken);
};
EmulatorTokenProvider.prototype.removeTokenChangeListener = function (listener) { };
EmulatorTokenProvider.prototype.notifyForInvalidToken = function () { };
/** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */
EmulatorTokenProvider.OWNER = 'owner';
return EmulatorTokenProvider;
}());
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var PROTOCOL_VERSION = '5';
var VERSION_PARAM = 'v';
var TRANSPORT_SESSION_PARAM = 's';
var REFERER_PARAM = 'r';
var FORGE_REF = 'f';
// Matches console.firebase.google.com, firebase-console-*.corp.google.com and
// firebase.corp.google.com
var FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/;
var LAST_SESSION_PARAM = 'ls';
var APPLICATION_ID_PARAM = 'p';
var APP_CHECK_TOKEN_PARAM = 'ac';
var WEBSOCKET = 'websocket';
var LONG_POLLING = 'long_polling';
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A class that holds metadata about a Repo object
*/
var RepoInfo = /** @class */ (function () {
/**
* @param host - Hostname portion of the url for the repo
* @param secure - Whether or not this repo is accessed over ssl
* @param namespace - The namespace represented by the repo
* @param webSocketOnly - Whether to prefer websockets over all other transports (used by Nest).
* @param nodeAdmin - Whether this instance uses Admin SDK credentials
* @param persistenceKey - Override the default session persistence storage key
*/
function RepoInfo(host, secure, namespace, webSocketOnly, nodeAdmin, persistenceKey, includeNamespaceInQueryParams, isUsingEmulator) {
if (nodeAdmin === void 0) { nodeAdmin = false; }
if (persistenceKey === void 0) { persistenceKey = ''; }
if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
if (isUsingEmulator === void 0) { isUsingEmulator = false; }
this.secure = secure;
this.namespace = namespace;
this.webSocketOnly = webSocketOnly;
this.nodeAdmin = nodeAdmin;
this.persistenceKey = persistenceKey;
this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
this.isUsingEmulator = isUsingEmulator;
this._host = host.toLowerCase();
this._domain = this._host.substr(this._host.indexOf('.') + 1);
this.internalHost =
PersistentStorage.get('host:' + host) || this._host;
}
RepoInfo.prototype.isCacheableHost = function () {
return this.internalHost.substr(0, 2) === 's-';
};
RepoInfo.prototype.isCustomHost = function () {
return (this._domain !== 'firebaseio.com' &&
this._domain !== 'firebaseio-demo.com');
};
Object.defineProperty(RepoInfo.prototype, "host", {
get: function () {
return this._host;
},
set: function (newHost) {
if (newHost !== this.internalHost) {
this.internalHost = newHost;
if (this.isCacheableHost()) {
PersistentStorage.set('host:' + this._host, this.internalHost);
}
}
},
enumerable: false,
configurable: true
});
RepoInfo.prototype.toString = function () {
var str = this.toURLString();
if (this.persistenceKey) {
str += '<' + this.persistenceKey + '>';
}
return str;
};
RepoInfo.prototype.toURLString = function () {
var protocol = this.secure ? 'https://' : 'http://';
var query = this.includeNamespaceInQueryParams
? "?ns=".concat(this.namespace)
: '';
return "".concat(protocol).concat(this.host, "/").concat(query);
};
return RepoInfo;
}());
function repoInfoNeedsQueryParam(repoInfo) {
return (repoInfo.host !== repoInfo.internalHost ||
repoInfo.isCustomHost() ||
repoInfo.includeNamespaceInQueryParams);
}
/**
* Returns the websocket URL for this repo
* @param repoInfo - RepoInfo object
* @param type - of connection
* @param params - list
* @returns The URL for this repo
*/
function repoInfoConnectionURL(repoInfo, type, params) {
assert(typeof type === 'string', 'typeof type must == string');
assert(typeof params === 'object', 'typeof params must == object');
var connURL;
if (type === WEBSOCKET) {
connURL =
(repoInfo.secure ? 'wss://' : 'ws://') + repoInfo.internalHost + '/.ws?';
}
else if (type === LONG_POLLING) {
connURL =
(repoInfo.secure ? 'https://' : 'http://') +
repoInfo.internalHost +
'/.lp?';
}
else {
throw new Error('Unknown connection type: ' + type);
}
if (repoInfoNeedsQueryParam(repoInfo)) {
params['ns'] = repoInfo.namespace;
}
var pairs = [];
each(params, function (key, value) {
pairs.push(key + '=' + value);
});
return connURL + pairs.join('&');
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Tracks a collection of stats.
*/
var StatsCollection = /** @class */ (function () {
function StatsCollection() {
this.counters_ = {};
}
StatsCollection.prototype.incrementCounter = function (name, amount) {
if (amount === void 0) { amount = 1; }
if (!contains(this.counters_, name)) {
this.counters_[name] = 0;
}
this.counters_[name] += amount;
};
StatsCollection.prototype.get = function () {
return deepCopy(this.counters_);
};
return StatsCollection;
}());
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var collections = {};
var reporters = {};
function statsManagerGetCollection(repoInfo) {
var hashString = repoInfo.toString();
if (!collections[hashString]) {
collections[hashString] = new StatsCollection();
}
return collections[hashString];
}
function statsManagerGetOrCreateReporter(repoInfo, creatorFunction) {
var hashString = repoInfo.toString();
if (!reporters[hashString]) {
reporters[hashString] = creatorFunction();
}
return reporters[hashString];
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This class ensures the packets from the server arrive in order
* This class takes data from the server and ensures it gets passed into the callbacks in order.
*/
var PacketReceiver = /** @class */ (function () {
/**
* @param onMessage_
*/
function PacketReceiver(onMessage_) {
this.onMessage_ = onMessage_;
this.pendingResponses = [];
this.currentResponseNum = 0;
this.closeAfterResponse = -1;
this.onClose = null;
}
PacketReceiver.prototype.closeAfter = function (responseNum, callback) {
this.closeAfterResponse = responseNum;
this.onClose = callback;
if (this.closeAfterResponse < this.currentResponseNum) {
this.onClose();
this.onClose = null;
}
};
/**
* Each message from the server comes with a response number, and an array of data. The responseNumber
* allows us to ensure that we process them in the right order, since we can't be guaranteed that all
* browsers will respond in the same order as the requests we sent
*/
PacketReceiver.prototype.handleResponse = function (requestNum, data) {
var _this = this;
this.pendingResponses[requestNum] = data;
var _loop_1 = function () {
var toProcess = this_1.pendingResponses[this_1.currentResponseNum];
delete this_1.pendingResponses[this_1.currentResponseNum];
var _loop_2 = function (i) {
if (toProcess[i]) {
exceptionGuard(function () {
_this.onMessage_(toProcess[i]);
});
}
};
for (var i = 0; i < toProcess.length; ++i) {
_loop_2(i);
}
if (this_1.currentResponseNum === this_1.closeAfterResponse) {
if (this_1.onClose) {
this_1.onClose();
this_1.onClose = null;
}
return "break";
}
this_1.currentResponseNum++;
};
var this_1 = this;
while (this.pendingResponses[this.currentResponseNum]) {
var state_1 = _loop_1();
if (state_1 === "break")
break;
}
};
return PacketReceiver;
}());
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// URL query parameters associated with longpolling
var FIREBASE_LONGPOLL_START_PARAM = 'start';
var FIREBASE_LONGPOLL_CLOSE_COMMAND = 'close';
var FIREBASE_LONGPOLL_COMMAND_CB_NAME = 'pLPCommand';
var FIREBASE_LONGPOLL_DATA_CB_NAME = 'pRTLPCB';
var FIREBASE_LONGPOLL_ID_PARAM = 'id';
var FIREBASE_LONGPOLL_PW_PARAM = 'pw';
var FIREBASE_LONGPOLL_SERIAL_PARAM = 'ser';
var FIREBASE_LONGPOLL_CALLBACK_ID_PARAM = 'cb';
var FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM = 'seg';
var FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET = 'ts';
var FIREBASE_LONGPOLL_DATA_PARAM = 'd';
var FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM = 'dframe';
//Data size constants.
//TODO: Perf: the maximum length actually differs from browser to browser.
// We should check what browser we're on and set accordingly.
var MAX_URL_DATA_SIZE = 1870;
var SEG_HEADER_SIZE = 30; //ie: &seg=8299234&ts=982389123&d=
var MAX_PAYLOAD_SIZE = MAX_URL_DATA_SIZE - SEG_HEADER_SIZE;
/**
* Keepalive period
* send a fresh request at minimum every 25 seconds. Opera has a maximum request
* length of 30 seconds that we can't exceed.
*/
var KEEPALIVE_REQUEST_INTERVAL = 25000;
/**
* How long to wait before aborting a long-polling connection attempt.
*/
var LP_CONNECT_TIMEOUT = 30000;
/**
* This class manages a single long-polling connection.
*/
var BrowserPollConnection = /** @class */ (function () {
/**
* @param connId An identifier for this connection, used for logging
* @param repoInfo The info for the endpoint to send data to.
* @param applicationId The Firebase App ID for this project.
* @param appCheckToken The AppCheck token for this client.
* @param authToken The AuthToken to use for this connection.
* @param transportSessionId Optional transportSessionid if we are
* reconnecting for an existing transport session
* @param lastSessionId Optional lastSessionId if the PersistentConnection has
* already created a connection previously
*/
function BrowserPollConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
var _this = this;
this.connId = connId;
this.repoInfo = repoInfo;
this.applicationId = applicationId;
this.appCheckToken = appCheckToken;
this.authToken = authToken;
this.transportSessionId = transportSessionId;
this.lastSessionId = lastSessionId;
this.bytesSent = 0;
this.bytesReceived = 0;
this.everConnected_ = false;
this.log_ = logWrapper(connId);
this.stats_ = statsManagerGetCollection(repoInfo);
this.urlFn = function (params) {
// Always add the token if we have one.
if (_this.appCheckToken) {
params[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
}
return repoInfoConnectionURL(repoInfo, LONG_POLLING, params);
};
}
/**
* @param onMessage - Callback when messages arrive
* @param onDisconnect - Callback with connection lost.
*/
BrowserPollConnection.prototype.open = function (onMessage, onDisconnect) {
var _this = this;
this.curSegmentNum = 0;
this.onDisconnect_ = onDisconnect;
this.myPacketOrderer = new PacketReceiver(onMessage);
this.isClosed_ = false;
this.connectTimeoutTimer_ = setTimeout(function () {
_this.log_('Timed out trying to connect.');
// Make sure we clear the host cache
_this.onClosed_();
_this.connectTimeoutTimer_ = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}, Math.floor(LP_CONNECT_TIMEOUT));
// Ensure we delay the creation of the iframe until the DOM is loaded.
executeWhenDOMReady(function () {
if (_this.isClosed_) {
return;
}
//Set up a callback that gets triggered once a connection is set up.
_this.scriptTagHolder = new FirebaseIFrameScriptHolder(function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var _a = __read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2]; _a[3]; _a[4];
_this.incrementIncomingBytes_(args);