Files
nirvana-server/node_modules/@firebase/database/dist/index.node.cjs.js
T
2021-12-29 01:57:42 -08:00

14521 lines
572 KiB
JavaScript

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var Websocket = require('faye-websocket');
var util = require('@firebase/util');
var tslib = require('tslib');
var logger$1 = require('@firebase/logger');
var app = require('@firebase/app');
var component = require('@firebase/component');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var Websocket__default = /*#__PURE__*/_interopDefaultLegacy(Websocket);
/**
* @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.
*/
/**
* 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), util.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 util.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 (util.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$1.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 = util.stringToByteArray(str);
var sha1 = new util.Sha1();
sha1.update(utf8Bytes);
var sha1Bytes = sha1.digest();
return util.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 += util.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) {
util.assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
if (logger_ === true) {
logClient.logLevel = logger$1.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, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
};
};
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, tslib.__spreadArray([], tslib.__read(varArgs)));
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: " + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
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, tslib.__spreadArray([], tslib.__read(varArgs)));
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 (util.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: ' + util.stringify(obj));
}
};
var ObjectToUniqueKey = function (obj) {
if (typeof obj !== 'object' || obj === null) {
return util.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 += util.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) {
util.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);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (typeof timeout === 'object' && timeout['unref']) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
timeout['unref']();
}
return timeout;
};
/**
* @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) {
if (nodeAdmin === void 0) { nodeAdmin = false; }
if (persistenceKey === void 0) { persistenceKey = ''; }
if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
this.secure = secure;
this.namespace = namespace;
this.webSocketOnly = webSocketOnly;
this.nodeAdmin = nodeAdmin;
this.persistenceKey = persistenceKey;
this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
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=" + this.namespace
: '';
return "" + protocol + this.host + "/" + 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) {
util.assert(typeof type === 'string', 'typeof type must == string');
util.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 (!util.contains(this.counters_, name)) {
this.counters_[name] = 0;
}
this.counters_[name] += amount;
};
StatsCollection.prototype.get = function () {
return util.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 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.
*/
var WEBSOCKET_MAX_FRAME_SIZE = 16384;
var WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
var WebSocketImpl = null;
if (typeof MozWebSocket !== 'undefined') {
WebSocketImpl = MozWebSocket;
}
else if (typeof WebSocket !== 'undefined') {
WebSocketImpl = WebSocket;
}
function setWebSocketImpl(impl) {
WebSocketImpl = impl;
}
/**
* Create a new websocket connection with the given callbacks.
*/
var WebSocketConnection = /** @class */ (function () {
/**
* @param connId identifier for this transport
* @param repoInfo The info for the websocket endpoint.
* @param applicationId The Firebase App ID for this project.
* @param appCheckToken The App Check Token for this client.
* @param authToken The Auth Token for this client.
* @param transportSessionId Optional transportSessionId if this is connecting
* to an existing transport session
* @param lastSessionId Optional lastSessionId if there was a previous
* connection
*/
function WebSocketConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
this.connId = connId;
this.applicationId = applicationId;
this.appCheckToken = appCheckToken;
this.authToken = authToken;
this.keepaliveTimer = null;
this.frames = null;
this.totalFrames = 0;
this.bytesSent = 0;
this.bytesReceived = 0;
this.log_ = logWrapper(this.connId);
this.stats_ = statsManagerGetCollection(repoInfo);
this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId, appCheckToken);
this.nodeAdmin = repoInfo.nodeAdmin;
}
/**
* @param repoInfo - The info for the websocket endpoint.
* @param transportSessionId - Optional transportSessionId if this is connecting to an existing transport
* session
* @param lastSessionId - Optional lastSessionId if there was a previous connection
* @returns connection url
*/
WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId, appCheckToken) {
var urlParams = {};
urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
if (!util.isNodeSdk() &&
typeof location !== 'undefined' &&
location.hostname &&
FORGE_DOMAIN_RE.test(location.hostname)) {
urlParams[REFERER_PARAM] = FORGE_REF;
}
if (transportSessionId) {
urlParams[TRANSPORT_SESSION_PARAM] = transportSessionId;
}
if (lastSessionId) {
urlParams[LAST_SESSION_PARAM] = lastSessionId;
}
if (appCheckToken) {
urlParams[APP_CHECK_TOKEN_PARAM] = appCheckToken;
}
return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
};
/**
* @param onMessage - Callback when messages arrive
* @param onDisconnect - Callback with connection lost.
*/
WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
var _this = this;
this.onDisconnect = onDisconnect;
this.onMessage = onMessage;
this.log_('Websocket connecting to ' + this.connURL);
this.everConnected_ = false;
// Assume failure until proven otherwise.
PersistentStorage.set('previous_websocket_failure', true);
try {
if (util.isNodeSdk()) {
var device = this.nodeAdmin ? 'AdminNode' : 'Node';
// UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
var options = {
headers: {
'User-Agent': "Firebase/" + PROTOCOL_VERSION + "/" + SDK_VERSION + "/" + process.platform + "/" + device,
'X-Firebase-GMPID': this.applicationId || ''
}
};
// If using Node with admin creds, AppCheck-related checks are unnecessary.
// Note that we send the credentials here even if they aren't admin credentials, which is
// not a problem.
// Note that this header is just used to bypass appcheck, and the token should still be sent
// through the websocket connection once it is established.
if (this.authToken) {
options.headers['Authorization'] = "Bearer " + this.authToken;
}
if (this.appCheckToken) {
options.headers['X-Firebase-AppCheck'] = this.appCheckToken;
}
// Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
var env = process['env'];
var proxy = this.connURL.indexOf('wss://') === 0
? env['HTTPS_PROXY'] || env['https_proxy']
: env['HTTP_PROXY'] || env['http_proxy'];
if (proxy) {
options['proxy'] = { origin: proxy };
}
this.mySock = new WebSocketImpl(this.connURL, [], options);
}
else {
var options = {
headers: {
'X-Firebase-GMPID': this.applicationId || '',
'X-Firebase-AppCheck': this.appCheckToken || ''
}
};
this.mySock = new WebSocketImpl(this.connURL, [], options);
}
}
catch (e) {
this.log_('Error instantiating WebSocket.');
var error = e.message || e.data;
if (error) {
this.log_(error);
}
this.onClosed_();
return;
}
this.mySock.onopen = function () {
_this.log_('Websocket connected.');
_this.everConnected_ = true;
};
this.mySock.onclose = function () {
_this.log_('Websocket connection was disconnected.');
_this.mySock = null;
_this.onClosed_();
};
this.mySock.onmessage = function (m) {
_this.handleIncomingFrame(m);
};
this.mySock.onerror = function (e) {
_this.log_('WebSocket error. Closing connection.');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var error = e.message || e.data;
if (error) {
_this.log_(error);
}
_this.onClosed_();
};
};
/**
* No-op for websockets, we don't need to do anything once the connection is confirmed as open
*/
WebSocketConnection.prototype.start = function () { };
WebSocketConnection.forceDisallow = function () {
WebSocketConnection.forceDisallow_ = true;
};
WebSocketConnection.isAvailable = function () {
var isOldAndroid = false;
if (typeof navigator !== 'undefined' && navigator.userAgent) {
var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
if (oldAndroidMatch && oldAndroidMatch.length > 1) {
if (parseFloat(oldAndroidMatch[1]) < 4.4) {
isOldAndroid = true;
}
}
}
return (!isOldAndroid &&
WebSocketImpl !== null &&
!WebSocketConnection.forceDisallow_);
};
/**
* Returns true if we previously failed to connect with this transport.
*/
WebSocketConnection.previouslyFailed = function () {
// If our persistent storage is actually only in-memory storage,
// we default to assuming that it previously failed to be safe.
return (PersistentStorage.isInMemoryStorage ||
PersistentStorage.get('previous_websocket_failure') === true);
};
WebSocketConnection.prototype.markConnectionHealthy = function () {
PersistentStorage.remove('previous_websocket_failure');
};
WebSocketConnection.prototype.appendFrame_ = function (data) {
this.frames.push(data);
if (this.frames.length === this.totalFrames) {
var fullMess = this.frames.join('');
this.frames = null;
var jsonMess = util.jsonEval(fullMess);
//handle the message
this.onMessage(jsonMess);
}
};
/**
* @param frameCount - The number of frames we are expecting from the server
*/
WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
this.totalFrames = frameCount;
this.frames = [];
};
/**
* Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
* @returns Any remaining data to be process, or null if there is none
*/
WebSocketConnection.prototype.extractFrameCount_ = function (data) {
util.assert(this.frames === null, 'We already have a frame buffer');
// TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
// currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
if (data.length <= 6) {
var frameCount = Number(data);
if (!isNaN(frameCount)) {
this.handleNewFrameCount_(frameCount);
return null;
}
}
this.handleNewFrameCount_(1);
return data;
};
/**
* Process a websocket frame that has arrived from the server.
* @param mess - The frame data
*/
WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
if (this.mySock === null) {
return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
}
var data = mess['data'];
this.bytesReceived += data.length;
this.stats_.incrementCounter('bytes_received', data.length);
this.resetKeepAlive();
if (this.frames !== null) {
// we're buffering
this.appendFrame_(data);
}
else {
// try to parse out a frame count, otherwise, assume 1 and process it
var remainingData = this.extractFrameCount_(data);
if (remainingData !== null) {
this.appendFrame_(remainingData);
}
}
};
/**
* Send a message to the server
* @param data - The JSON object to transmit
*/
WebSocketConnection.prototype.send = function (data) {
this.resetKeepAlive();
var dataStr = util.stringify(data);
this.bytesSent += dataStr.length;
this.stats_.incrementCounter('bytes_sent', dataStr.length);
//We can only fit a certain amount in each websocket frame, so we need to split this request
//up into multiple pieces if it doesn't fit in one request.
var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
//Send the length header
if (dataSegs.length > 1) {
this.sendString_(String(dataSegs.length));
}
//Send the actual data in segments.
for (var i = 0; i < dataSegs.length; i++) {
this.sendString_(dataSegs[i]);
}
};
WebSocketConnection.prototype.shutdown_ = function () {
this.isClosed_ = true;
if (this.keepaliveTimer) {
clearInterval(this.keepaliveTimer);
this.keepaliveTimer = null;
}
if (this.mySock) {
this.mySock.close();
this.mySock = null;
}
};
WebSocketConnection.prototype.onClosed_ = function () {
if (!this.isClosed_) {
this.log_('WebSocket is closing itself');
this.shutdown_();
// since this is an internal close, trigger the close listener
if (this.onDisconnect) {
this.onDisconnect(this.everConnected_);
this.onDisconnect = null;
}
}
};
/**
* External-facing close handler.
* Close the websocket and kill the connection.
*/
WebSocketConnection.prototype.close = function () {
if (!this.isClosed_) {
this.log_('WebSocket is being closed');
this.shutdown_();
}
};
/**
* Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
* the last activity.
*/
WebSocketConnection.prototype.resetKeepAlive = function () {
var _this = this;
clearInterval(this.keepaliveTimer);
this.keepaliveTimer = setInterval(function () {
//If there has been no websocket activity for a while, send a no-op
if (_this.mySock) {
_this.sendString_('0');
}
_this.resetKeepAlive();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
};
/**
* Send a string over the websocket.
*
* @param str - String to send.
*/
WebSocketConnection.prototype.sendString_ = function (str) {
// Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
// calls for some unknown reason. We treat these as an error and disconnect.
// See https://app.asana.com/0/58926111402292/68021340250410
try {
this.mySock.send(str);
}
catch (e) {
this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
setTimeout(this.onClosed_.bind(this), 0);
}
};
/**
* Number of response before we consider the connection "healthy."
*/
WebSocketConnection.responsesRequiredToBeHealthy = 2;
/**
* Time to wait for the connection te become healthy before giving up.
*/
WebSocketConnection.healthyTimeout = 30000;
return WebSocketConnection;
}());
var name = "@firebase/database";
var version = "0.12.4";
/**
* @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 \"" + 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.
*/
/**
* 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 = tslib.__read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2]; _a[3]; _a[4];
_this.incrementIncomingBytes_(args);
if (!_this.scriptTagHolder) {
return; // we closed the connection.
}
if (_this.connectTimeoutTimer_) {
clearTimeout(_this.connectTimeoutTimer_);
_this.connectTimeoutTimer_ = null;
}
_this.everConnected_ = true;
if (command === FIREBASE_LONGPOLL_START_PARAM) {
_this.id = arg1;
_this.password = arg2;
}
else if (command === FIREBASE_LONGPOLL_CLOSE_COMMAND) {
// Don't clear the host cache. We got a response from the server, so we know it's reachable
if (arg1) {
// We aren't expecting any more data (other than what the server's already in the process of sending us
// through our already open polls), so don't send any more.
_this.scriptTagHolder.sendNewPolls = false;
// arg1 in this case is the last response number sent by the server. We should try to receive
// all of the responses up to this one before closing
_this.myPacketOrderer.closeAfter(arg1, function () {
_this.onClosed_();
});
}
else {
_this.onClosed_();
}
}
else {
throw new Error('Unrecognized command received: ' + command);
}
}, function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var _a = tslib.__read(args, 2), pN = _a[0], data = _a[1];
_this.incrementIncomingBytes_(args);
_this.myPacketOrderer.handleResponse(pN, data);
}, function () {
_this.onClosed_();
}, _this.urlFn);
//Send the initial request to connect. The serial number is simply to keep the browser from pulling previous results
//from cache.
var urlParams = {};
urlParams[FIREBASE_LONGPOLL_START_PARAM] = 't';
urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = Math.floor(Math.random() * 100000000);
if (_this.scriptTagHolder.uniqueCallbackIdentifier) {
urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] =
_this.scriptTagHolder.uniqueCallbackIdentifier;
}
urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
if (_this.transportSessionId) {
urlParams[TRANSPORT_SESSION_PARAM] = _this.transportSessionId;
}
if (_this.lastSessionId) {
urlParams[LAST_SESSION_PARAM] = _this.lastSessionId;
}
if (_this.applicationId) {
urlParams[APPLICATION_ID_PARAM] = _this.applicationId;
}
if (_this.appCheckToken) {
urlParams[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
}
if (typeof location !== 'undefined' &&
location.hostname &&
FORGE_DOMAIN_RE.test(location.hostname)) {
urlParams[REFERER_PARAM] = FORGE_REF;
}
var connectURL = _this.urlFn(urlParams);
_this.log_('Connecting via long-poll to ' + connectURL);
_this.scriptTagHolder.addTag(connectURL, function () {
/* do nothing */
});
});
};
/**
* Call this when a handshake has completed successfully and we want to consider the connection established
*/
BrowserPollConnection.prototype.start = function () {
this.scriptTagHolder.startLongPoll(this.id, this.password);
this.addDisconnectPingFrame(this.id, this.password);
};
/**
* Forces long polling to be considered as a potential transport
*/
BrowserPollConnection.forceAllow = function () {
BrowserPollConnection.forceAllow_ = true;
};
/**
* Forces longpolling to not be considered as a potential transport
*/
BrowserPollConnection.forceDisallow = function () {
BrowserPollConnection.forceDisallow_ = true;
};
// Static method, use string literal so it can be accessed in a generic way
BrowserPollConnection.isAvailable = function () {
if (util.isNodeSdk()) {
return false;
}
else if (BrowserPollConnection.forceAllow_) {
return true;
}
else {
// NOTE: In React-Native there's normally no 'document', but if you debug a React-Native app in
// the Chrome debugger, 'document' is defined, but document.createElement is null (2015/06/08).
return (!BrowserPollConnection.forceDisallow_ &&
typeof document !== 'undefined' &&
document.createElement != null &&
!isChromeExtensionContentScript() &&
!isWindowsStoreApp());
}
};
/**
* No-op for polling
*/
BrowserPollConnection.prototype.markConnectionHealthy = function () { };
/**
* Stops polling and cleans up the iframe
*/
BrowserPollConnection.prototype.shutdown_ = function () {
this.isClosed_ = true;
if (this.scriptTagHolder) {
this.scriptTagHolder.close();
this.scriptTagHolder = null;
}
//remove the disconnect frame, which will trigger an XHR call to the server to tell it we're leaving.
if (this.myDisconnFrame) {
document.body.removeChild(this.myDisconnFrame);
this.myDisconnFrame = null;
}
if (this.connectTimeoutTimer_) {
clearTimeout(this.connectTimeoutTimer_);
this.connectTimeoutTimer_ = null;
}
};
/**
* Triggered when this transport is closed
*/
BrowserPollConnection.prototype.onClosed_ = function () {
if (!this.isClosed_) {
this.log_('Longpoll is closing itself');
this.shutdown_();
if (this.onDisconnect_) {
this.onDisconnect_(this.everConnected_);
this.onDisconnect_ = null;
}
}
};
/**
* External-facing close handler. RealTime has requested we shut down. Kill our connection and tell the server
* that we've left.
*/
BrowserPollConnection.prototype.close = function () {
if (!this.isClosed_) {
this.log_('Longpoll is being closed.');
this.shutdown_();
}
};
/**
* Send the JSON object down to the server. It will need to be stringified, base64 encoded, and then
* broken into chunks (since URLs have a small maximum length).
* @param data - The JSON data to transmit.
*/
BrowserPollConnection.prototype.send = function (data) {
var dataStr = util.stringify(data);
this.bytesSent += dataStr.length;
this.stats_.incrementCounter('bytes_sent', dataStr.length);
//first, lets get the base64-encoded data
var base64data = util.base64Encode(dataStr);
//We can only fit a certain amount in each URL, so we need to split this request
//up into multiple pieces if it doesn't fit in one request.
var dataSegs = splitStringBySize(base64data, MAX_PAYLOAD_SIZE);
//Enqueue each segment for transmission. We assign each chunk a sequential ID and a total number
//of segments so that we can reassemble the packet on the server.
for (var i = 0; i < dataSegs.length; i++) {
this.scriptTagHolder.enqueueSegment(this.curSegmentNum, dataSegs.length, dataSegs[i]);
this.curSegmentNum++;
}
};
/**
* This is how we notify the server that we're leaving.
* We aren't able to send requests with DHTML on a window close event, but we can
* trigger XHR requests in some browsers (everything but Opera basically).
*/
BrowserPollConnection.prototype.addDisconnectPingFrame = function (id, pw) {
if (util.isNodeSdk()) {
return;
}
this.myDisconnFrame = document.createElement('iframe');
var urlParams = {};
urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
urlParams[FIREBASE_LONGPOLL_ID_PARAM] = id;
urlParams[FIREBASE_LONGPOLL_PW_PARAM] = pw;
this.myDisconnFrame.src = this.urlFn(urlParams);
this.myDisconnFrame.style.display = 'none';
document.body.appendChild(this.myDisconnFrame);
};
/**
* Used to track the bytes received by this client
*/
BrowserPollConnection.prototype.incrementIncomingBytes_ = function (args) {
// TODO: This is an annoying perf hit just to track the number of incoming bytes. Maybe it should be opt-in.
var bytesReceived = util.stringify(args).length;
this.bytesReceived += bytesReceived;
this.stats_.incrementCounter('bytes_received', bytesReceived);
};
return BrowserPollConnection;
}());
/*********************************************************************************************
* A wrapper around an iframe that is used as a long-polling script holder.
*********************************************************************************************/
var FirebaseIFrameScriptHolder = /** @class */ (function () {
/**
* @param commandCB - The callback to be called when control commands are recevied from the server.
* @param onMessageCB - The callback to be triggered when responses arrive from the server.
* @param onDisconnect - The callback to be triggered when this tag holder is closed
* @param urlFn - A function that provides the URL of the endpoint to send data to.
*/
function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnect, urlFn) {
this.onDisconnect = onDisconnect;
this.urlFn = urlFn;
//We maintain a count of all of the outstanding requests, because if we have too many active at once it can cause
//problems in some browsers.
this.outstandingRequests = new Set();
//A queue of the pending segments waiting for transmission to the server.
this.pendingSegs = [];
//A serial number. We use this for two things:
// 1) A way to ensure the browser doesn't cache responses to polls
// 2) A way to make the server aware when long-polls arrive in a different order than we started them. The
// server needs to release both polls in this case or it will cause problems in Opera since Opera can only execute
// JSONP code in the order it was added to the iframe.
this.currentSerial = Math.floor(Math.random() * 100000000);
// This gets set to false when we're "closing down" the connection (e.g. we're switching transports but there's still
// incoming data from the server that we're waiting for).
this.sendNewPolls = true;
if (!util.isNodeSdk()) {
//Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
//iframes where we put the long-polling script tags. We have two callbacks:
// 1) Command Callback - Triggered for control issues, like starting a connection.
// 2) Message Callback - Triggered when new data arrives.
this.uniqueCallbackIdentifier = LUIDGenerator();
window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] =
onMessageCB;
//Create an iframe for us to add script tags to.
this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_();
// Set the iframe's contents.
var script = '';
// if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
// for ie9, but ie8 needs to do it again in the document itself.
if (this.myIFrame.src &&
this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
var currentDomain = document.domain;
script = '<script>document.domain="' + currentDomain + '";</script>';
}
var iframeContents = '<html><body>' + script + '</body></html>';
try {
this.myIFrame.doc.open();
this.myIFrame.doc.write(iframeContents);
this.myIFrame.doc.close();
}
catch (e) {
log('frame writing exception');
if (e.stack) {
log(e.stack);
}
log(e);
}
}
else {
this.commandCB = commandCB;
this.onMessageCB = onMessageCB;
}
}
/**
* Each browser has its own funny way to handle iframes. Here we mush them all together into one object that I can
* actually use.
*/
FirebaseIFrameScriptHolder.createIFrame_ = function () {
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
// This is necessary in order to initialize the document inside the iframe
if (document.body) {
document.body.appendChild(iframe);
try {
// If document.domain has been modified in IE, this will throw an error, and we need to set the
// domain of the iframe's document manually. We can do this via a javascript: url as the src attribute
// Also note that we must do this *after* the iframe has been appended to the page. Otherwise it doesn't work.
var a = iframe.contentWindow.document;
if (!a) {
// Apologies for the log-spam, I need to do something to keep closure from optimizing out the assignment above.
log('No IE domain setting required');
}
}
catch (e) {
var domain = document.domain;
iframe.src =
"javascript:void((function(){document.open();document.domain='" +
domain +
"';document.close();})())";
}
}
else {
// LongPollConnection attempts to delay initialization until the document is ready, so hopefully this
// never gets hit.
throw 'Document body has not initialized. Wait to initialize Firebase until after the document is ready.';
}
// Get the document of the iframe in a browser-specific way.
if (iframe.contentDocument) {
iframe.doc = iframe.contentDocument; // Firefox, Opera, Safari
}
else if (iframe.contentWindow) {
iframe.doc = iframe.contentWindow.document; // Internet Explorer
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
else if (iframe.document) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
iframe.doc = iframe.document; //others?
}
return iframe;
};
/**
* Cancel all outstanding queries and remove the frame.
*/
FirebaseIFrameScriptHolder.prototype.close = function () {
var _this = this;
//Mark this iframe as dead, so no new requests are sent.
this.alive = false;
if (this.myIFrame) {
//We have to actually remove all of the html inside this iframe before removing it from the
//window, or IE will continue loading and executing the script tags we've already added, which
//can lead to some errors being thrown. Setting innerHTML seems to be the easiest way to do this.
this.myIFrame.doc.body.innerHTML = '';
setTimeout(function () {
if (_this.myIFrame !== null) {
document.body.removeChild(_this.myIFrame);
_this.myIFrame = null;
}
}, Math.floor(0));
}
// Protect from being called recursively.
var onDisconnect = this.onDisconnect;
if (onDisconnect) {
this.onDisconnect = null;
onDisconnect();
}
};
/**
* Actually start the long-polling session by adding the first script tag(s) to the iframe.
* @param id - The ID of this connection
* @param pw - The password for this connection
*/
FirebaseIFrameScriptHolder.prototype.startLongPoll = function (id, pw) {
this.myID = id;
this.myPW = pw;
this.alive = true;
//send the initial request. If there are requests queued, make sure that we transmit as many as we are currently able to.
while (this.newRequest_()) { }
};
/**
* This is called any time someone might want a script tag to be added. It adds a script tag when there aren't
* too many outstanding requests and we are still alive.
*
* If there are outstanding packet segments to send, it sends one. If there aren't, it sends a long-poll anyways if
* needed.
*/
FirebaseIFrameScriptHolder.prototype.newRequest_ = function () {
// We keep one outstanding request open all the time to receive data, but if we need to send data
// (pendingSegs.length > 0) then we create a new request to send the data. The server will automatically
// close the old request.
if (this.alive &&
this.sendNewPolls &&
this.outstandingRequests.size < (this.pendingSegs.length > 0 ? 2 : 1)) {
//construct our url
this.currentSerial++;
var urlParams = {};
urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = this.currentSerial;
var theURL = this.urlFn(urlParams);
//Now add as much data as we can.
var curDataString = '';
var i = 0;
while (this.pendingSegs.length > 0) {
//first, lets see if the next segment will fit.
var nextSeg = this.pendingSegs[0];
if (nextSeg.d.length +
SEG_HEADER_SIZE +
curDataString.length <=
MAX_URL_DATA_SIZE) {
//great, the segment will fit. Lets append it.
var theSeg = this.pendingSegs.shift();
curDataString =
curDataString +
'&' +
FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM +
i +
'=' +
theSeg.seg +
'&' +
FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET +
i +
'=' +
theSeg.ts +
'&' +
FIREBASE_LONGPOLL_DATA_PARAM +
i +
'=' +
theSeg.d;
i++;
}
else {
break;
}
}
theURL = theURL + curDataString;
this.addLongPollTag_(theURL, this.currentSerial);
return true;
}
else {
return false;
}
};
/**
* Queue a packet for transmission to the server.
* @param segnum - A sequential id for this packet segment used for reassembly
* @param totalsegs - The total number of segments in this packet
* @param data - The data for this segment.
*/
FirebaseIFrameScriptHolder.prototype.enqueueSegment = function (segnum, totalsegs, data) {
//add this to the queue of segments to send.
this.pendingSegs.push({ seg: segnum, ts: totalsegs, d: data });
//send the data immediately if there isn't already data being transmitted, unless
//startLongPoll hasn't been called yet.
if (this.alive) {
this.newRequest_();
}
};
/**
* Add a script tag for a regular long-poll request.
* @param url - The URL of the script tag.
* @param serial - The serial number of the request.
*/
FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function (url, serial) {
var _this = this;
//remember that we sent this request.
this.outstandingRequests.add(serial);
var doNewRequest = function () {
_this.outstandingRequests.delete(serial);
_this.newRequest_();
};
// If this request doesn't return on its own accord (by the server sending us some data), we'll
// create a new one after the KEEPALIVE interval to make sure we always keep a fresh request open.
var keepaliveTimeout = setTimeout(doNewRequest, Math.floor(KEEPALIVE_REQUEST_INTERVAL));
var readyStateCB = function () {
// Request completed. Cancel the keepalive.
clearTimeout(keepaliveTimeout);
// Trigger a new request so we can continue receiving data.
doNewRequest();
};
this.addTag(url, readyStateCB);
};
/**
* Add an arbitrary script tag to the iframe.
* @param url - The URL for the script tag source.
* @param loadCB - A callback to be triggered once the script has loaded.
*/
FirebaseIFrameScriptHolder.prototype.addTag = function (url, loadCB) {
var _this = this;
if (util.isNodeSdk()) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.doNodeLongPoll(url, loadCB);
}
else {
setTimeout(function () {
try {
// if we're already closed, don't add this poll
if (!_this.sendNewPolls) {
return;
}
var newScript_1 = _this.myIFrame.doc.createElement('script');
newScript_1.type = 'text/javascript';
newScript_1.async = true;
newScript_1.src = url;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
newScript_1.onload = newScript_1.onreadystatechange =
function () {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var rstate = newScript_1.readyState;
if (!rstate || rstate === 'loaded' || rstate === 'complete') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
newScript_1.onload = newScript_1.onreadystatechange = null;
if (newScript_1.parentNode) {
newScript_1.parentNode.removeChild(newScript_1);
}
loadCB();
}
};
newScript_1.onerror = function () {
log('Long-poll script failed to load: ' + url);
_this.sendNewPolls = false;
_this.close();
};
_this.myIFrame.doc.body.appendChild(newScript_1);
}
catch (e) {
// TODO: we should make this error visible somehow
}
}, Math.floor(1));
}
};
return FirebaseIFrameScriptHolder;
}());
/**
* @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.
*/
/**
* Currently simplistic, this class manages what transport a Connection should use at various stages of its
* lifecycle.
*
* It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
* they are available.
*/
var TransportManager = /** @class */ (function () {
/**
* @param repoInfo - Metadata around the namespace we're connecting to
*/
function TransportManager(repoInfo) {
this.initTransports_(repoInfo);
}
Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
get: function () {
return [BrowserPollConnection, WebSocketConnection];
},
enumerable: false,
configurable: true
});
TransportManager.prototype.initTransports_ = function (repoInfo) {
var e_1, _a;
var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
if (repoInfo.webSocketOnly) {
if (!isWebSocketsAvailable) {
warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
}
isSkipPollConnection = true;
}
if (isSkipPollConnection) {
this.transports_ = [WebSocketConnection];
}
else {
var transports = (this.transports_ = []);
try {
for (var _b = tslib.__values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
var transport = _c.value;
if (transport && transport['isAvailable']()) {
transports.push(transport);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
}
};
/**
* @returns The constructor for the initial transport to use
*/
TransportManager.prototype.initialTransport = function () {
if (this.transports_.length > 0) {
return this.transports_[0];
}
else {
throw new Error('No transports available');
}
};
/**
* @returns The constructor for the next transport, or null
*/
TransportManager.prototype.upgradeTransport = function () {
if (this.transports_.length > 1) {
return this.transports_[1];
}
else {
return null;
}
};
return TransportManager;
}());
/**
* @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.
*/
// Abort upgrade attempt if it takes longer than 60s.
var UPGRADE_TIMEOUT = 60000;
// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
// If the initial data sent triggers a lot of bandwidth (i.e. it's a large put or a listen for a large amount of data)
// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
// but we've sent/received enough bytes, we don't cancel the connection.
var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
var MESSAGE_TYPE = 't';
var MESSAGE_DATA = 'd';
var CONTROL_SHUTDOWN = 's';
var CONTROL_RESET = 'r';
var CONTROL_ERROR = 'e';
var CONTROL_PONG = 'o';
var SWITCH_ACK = 'a';
var END_TRANSMISSION = 'n';
var PING = 'p';
var SERVER_HELLO = 'h';
/**
* Creates a new real-time connection to the server using whichever method works
* best in the current browser.
*/
var Connection = /** @class */ (function () {
/**
* @param id - an id for this connection
* @param repoInfo_ - the info for the endpoint to connect to
* @param applicationId_ - the Firebase App ID for this project
* @param appCheckToken_ - The App Check Token for this device.
* @param authToken_ - The auth token for this session.
* @param onMessage_ - the callback to be triggered when a server-push message arrives
* @param onReady_ - the callback to be triggered when this connection is ready to send messages.
* @param onDisconnect_ - the callback to be triggered when a connection was lost
* @param onKill_ - the callback to be triggered when this connection has permanently shut down.
* @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
*/
function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
this.id = id;
this.repoInfo_ = repoInfo_;
this.applicationId_ = applicationId_;
this.appCheckToken_ = appCheckToken_;
this.authToken_ = authToken_;
this.onMessage_ = onMessage_;
this.onReady_ = onReady_;
this.onDisconnect_ = onDisconnect_;
this.onKill_ = onKill_;
this.lastSessionId = lastSessionId;
this.connectionCount = 0;
this.pendingDataMessages = [];
this.state_ = 0 /* CONNECTING */;
this.log_ = logWrapper('c:' + this.id + ':');
this.transportManager_ = new TransportManager(repoInfo_);
this.log_('Connection created');
this.start_();
}
/**
* Starts a connection attempt
*/
Connection.prototype.start_ = function () {
var _this = this;
var conn = this.transportManager_.initialTransport();
this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, null, this.lastSessionId);
// For certain transports (WebSockets), we need to send and receive several messages back and forth before we
// can consider the transport healthy.
this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
var onMessageReceived = this.connReceiver_(this.conn_);
var onConnectionLost = this.disconnReceiver_(this.conn_);
this.tx_ = this.conn_;
this.rx_ = this.conn_;
this.secondaryConn_ = null;
this.isHealthy_ = false;
/*
* Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
* This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
* Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
* still have the context of your originating frame.
*/
setTimeout(function () {
// this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
_this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
}, Math.floor(0));
var healthyTimeoutMS = conn['healthyTimeout'] || 0;
if (healthyTimeoutMS > 0) {
this.healthyTimeout_ = setTimeoutNonBlocking(function () {
_this.healthyTimeout_ = null;
if (!_this.isHealthy_) {
if (_this.conn_ &&
_this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
_this.log_('Connection exceeded healthy timeout but has received ' +
_this.conn_.bytesReceived +
' bytes. Marking connection healthy.');
_this.isHealthy_ = true;
_this.conn_.markConnectionHealthy();
}
else if (_this.conn_ &&
_this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
_this.log_('Connection exceeded healthy timeout but has sent ' +
_this.conn_.bytesSent +
' bytes. Leaving connection alive.');
// NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
// the server.
}
else {
_this.log_('Closing unhealthy connection after timeout.');
_this.close();
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}, Math.floor(healthyTimeoutMS));
}
};
Connection.prototype.nextTransportId_ = function () {
return 'c:' + this.id + ':' + this.connectionCount++;
};
Connection.prototype.disconnReceiver_ = function (conn) {
var _this = this;
return function (everConnected) {
if (conn === _this.conn_) {
_this.onConnectionLost_(everConnected);
}
else if (conn === _this.secondaryConn_) {
_this.log_('Secondary connection lost.');
_this.onSecondaryConnectionLost_();
}