UNPKG

600 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var fayeWebsocket = require('faye-websocket');
6var util = require('@firebase/util');
7var tslib = require('tslib');
8var logger$1 = require('@firebase/logger');
9var app = require('@firebase/app');
10var component = require('@firebase/component');
11
12/**
13 * @license
14 * Copyright 2017 Google LLC
15 *
16 * Licensed under the Apache License, Version 2.0 (the "License");
17 * you may not use this file except in compliance with the License.
18 * You may obtain a copy of the License at
19 *
20 * http://www.apache.org/licenses/LICENSE-2.0
21 *
22 * Unless required by applicable law or agreed to in writing, software
23 * distributed under the License is distributed on an "AS IS" BASIS,
24 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 * See the License for the specific language governing permissions and
26 * limitations under the License.
27 */
28var PROTOCOL_VERSION = '5';
29var VERSION_PARAM = 'v';
30var TRANSPORT_SESSION_PARAM = 's';
31var REFERER_PARAM = 'r';
32var FORGE_REF = 'f';
33// Matches console.firebase.google.com, firebase-console-*.corp.google.com and
34// firebase.corp.google.com
35var FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/;
36var LAST_SESSION_PARAM = 'ls';
37var APPLICATION_ID_PARAM = 'p';
38var APP_CHECK_TOKEN_PARAM = 'ac';
39var WEBSOCKET = 'websocket';
40var LONG_POLLING = 'long_polling';
41
42/**
43 * @license
44 * Copyright 2017 Google LLC
45 *
46 * Licensed under the Apache License, Version 2.0 (the "License");
47 * you may not use this file except in compliance with the License.
48 * You may obtain a copy of the License at
49 *
50 * http://www.apache.org/licenses/LICENSE-2.0
51 *
52 * Unless required by applicable law or agreed to in writing, software
53 * distributed under the License is distributed on an "AS IS" BASIS,
54 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55 * See the License for the specific language governing permissions and
56 * limitations under the License.
57 */
58/**
59 * Wraps a DOM Storage object and:
60 * - automatically encode objects as JSON strings before storing them to allow us to store arbitrary types.
61 * - prefixes names with "firebase:" to avoid collisions with app data.
62 *
63 * We automatically (see storage.js) create two such wrappers, one for sessionStorage,
64 * and one for localStorage.
65 *
66 */
67var DOMStorageWrapper = /** @class */ (function () {
68 /**
69 * @param domStorage_ - The underlying storage object (e.g. localStorage or sessionStorage)
70 */
71 function DOMStorageWrapper(domStorage_) {
72 this.domStorage_ = domStorage_;
73 // Use a prefix to avoid collisions with other stuff saved by the app.
74 this.prefix_ = 'firebase:';
75 }
76 /**
77 * @param key - The key to save the value under
78 * @param value - The value being stored, or null to remove the key.
79 */
80 DOMStorageWrapper.prototype.set = function (key, value) {
81 if (value == null) {
82 this.domStorage_.removeItem(this.prefixedName_(key));
83 }
84 else {
85 this.domStorage_.setItem(this.prefixedName_(key), util.stringify(value));
86 }
87 };
88 /**
89 * @returns The value that was stored under this key, or null
90 */
91 DOMStorageWrapper.prototype.get = function (key) {
92 var storedVal = this.domStorage_.getItem(this.prefixedName_(key));
93 if (storedVal == null) {
94 return null;
95 }
96 else {
97 return util.jsonEval(storedVal);
98 }
99 };
100 DOMStorageWrapper.prototype.remove = function (key) {
101 this.domStorage_.removeItem(this.prefixedName_(key));
102 };
103 DOMStorageWrapper.prototype.prefixedName_ = function (name) {
104 return this.prefix_ + name;
105 };
106 DOMStorageWrapper.prototype.toString = function () {
107 return this.domStorage_.toString();
108 };
109 return DOMStorageWrapper;
110}());
111
112/**
113 * @license
114 * Copyright 2017 Google LLC
115 *
116 * Licensed under the Apache License, Version 2.0 (the "License");
117 * you may not use this file except in compliance with the License.
118 * You may obtain a copy of the License at
119 *
120 * http://www.apache.org/licenses/LICENSE-2.0
121 *
122 * Unless required by applicable law or agreed to in writing, software
123 * distributed under the License is distributed on an "AS IS" BASIS,
124 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
125 * See the License for the specific language governing permissions and
126 * limitations under the License.
127 */
128/**
129 * An in-memory storage implementation that matches the API of DOMStorageWrapper
130 * (TODO: create interface for both to implement).
131 */
132var MemoryStorage = /** @class */ (function () {
133 function MemoryStorage() {
134 this.cache_ = {};
135 this.isInMemoryStorage = true;
136 }
137 MemoryStorage.prototype.set = function (key, value) {
138 if (value == null) {
139 delete this.cache_[key];
140 }
141 else {
142 this.cache_[key] = value;
143 }
144 };
145 MemoryStorage.prototype.get = function (key) {
146 if (util.contains(this.cache_, key)) {
147 return this.cache_[key];
148 }
149 return null;
150 };
151 MemoryStorage.prototype.remove = function (key) {
152 delete this.cache_[key];
153 };
154 return MemoryStorage;
155}());
156
157/**
158 * @license
159 * Copyright 2017 Google LLC
160 *
161 * Licensed under the Apache License, Version 2.0 (the "License");
162 * you may not use this file except in compliance with the License.
163 * You may obtain a copy of the License at
164 *
165 * http://www.apache.org/licenses/LICENSE-2.0
166 *
167 * Unless required by applicable law or agreed to in writing, software
168 * distributed under the License is distributed on an "AS IS" BASIS,
169 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
170 * See the License for the specific language governing permissions and
171 * limitations under the License.
172 */
173/**
174 * Helper to create a DOMStorageWrapper or else fall back to MemoryStorage.
175 * TODO: Once MemoryStorage and DOMStorageWrapper have a shared interface this method annotation should change
176 * to reflect this type
177 *
178 * @param domStorageName - Name of the underlying storage object
179 * (e.g. 'localStorage' or 'sessionStorage').
180 * @returns Turning off type information until a common interface is defined.
181 */
182var createStoragefor = function (domStorageName) {
183 try {
184 // NOTE: just accessing "localStorage" or "window['localStorage']" may throw a security exception,
185 // so it must be inside the try/catch.
186 if (typeof window !== 'undefined' &&
187 typeof window[domStorageName] !== 'undefined') {
188 // Need to test cache. Just because it's here doesn't mean it works
189 var domStorage = window[domStorageName];
190 domStorage.setItem('firebase:sentinel', 'cache');
191 domStorage.removeItem('firebase:sentinel');
192 return new DOMStorageWrapper(domStorage);
193 }
194 }
195 catch (e) { }
196 // Failed to create wrapper. Just return in-memory storage.
197 // TODO: log?
198 return new MemoryStorage();
199};
200/** A storage object that lasts across sessions */
201var PersistentStorage = createStoragefor('localStorage');
202/** A storage object that only lasts one session */
203var SessionStorage = createStoragefor('sessionStorage');
204
205/**
206 * @license
207 * Copyright 2017 Google LLC
208 *
209 * Licensed under the Apache License, Version 2.0 (the "License");
210 * you may not use this file except in compliance with the License.
211 * You may obtain a copy of the License at
212 *
213 * http://www.apache.org/licenses/LICENSE-2.0
214 *
215 * Unless required by applicable law or agreed to in writing, software
216 * distributed under the License is distributed on an "AS IS" BASIS,
217 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
218 * See the License for the specific language governing permissions and
219 * limitations under the License.
220 */
221var logClient = new logger$1.Logger('@firebase/database');
222/**
223 * Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
224 */
225var LUIDGenerator = (function () {
226 var id = 1;
227 return function () {
228 return id++;
229 };
230})();
231/**
232 * Sha1 hash of the input string
233 * @param str - The string to hash
234 * @returns {!string} The resulting hash
235 */
236var sha1 = function (str) {
237 var utf8Bytes = util.stringToByteArray(str);
238 var sha1 = new util.Sha1();
239 sha1.update(utf8Bytes);
240 var sha1Bytes = sha1.digest();
241 return util.base64.encodeByteArray(sha1Bytes);
242};
243var buildLogMessage_ = function () {
244 var varArgs = [];
245 for (var _i = 0; _i < arguments.length; _i++) {
246 varArgs[_i] = arguments[_i];
247 }
248 var message = '';
249 for (var i = 0; i < varArgs.length; i++) {
250 var arg = varArgs[i];
251 if (Array.isArray(arg) ||
252 (arg &&
253 typeof arg === 'object' &&
254 // eslint-disable-next-line @typescript-eslint/no-explicit-any
255 typeof arg.length === 'number')) {
256 message += buildLogMessage_.apply(null, arg);
257 }
258 else if (typeof arg === 'object') {
259 message += util.stringify(arg);
260 }
261 else {
262 message += arg;
263 }
264 message += ' ';
265 }
266 return message;
267};
268/**
269 * Use this for all debug messages in Firebase.
270 */
271var logger = null;
272/**
273 * Flag to check for log availability on first log message
274 */
275var firstLog_ = true;
276/**
277 * The implementation of Firebase.enableLogging (defined here to break dependencies)
278 * @param logger_ - A flag to turn on logging, or a custom logger
279 * @param persistent - Whether or not to persist logging settings across refreshes
280 */
281var enableLogging$1 = function (logger_, persistent) {
282 util.assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
283 if (logger_ === true) {
284 logClient.logLevel = logger$1.LogLevel.VERBOSE;
285 logger = logClient.log.bind(logClient);
286 if (persistent) {
287 SessionStorage.set('logging_enabled', true);
288 }
289 }
290 else if (typeof logger_ === 'function') {
291 logger = logger_;
292 }
293 else {
294 logger = null;
295 SessionStorage.remove('logging_enabled');
296 }
297};
298var log = function () {
299 var varArgs = [];
300 for (var _i = 0; _i < arguments.length; _i++) {
301 varArgs[_i] = arguments[_i];
302 }
303 if (firstLog_ === true) {
304 firstLog_ = false;
305 if (logger === null && SessionStorage.get('logging_enabled') === true) {
306 enableLogging$1(true);
307 }
308 }
309 if (logger) {
310 var message = buildLogMessage_.apply(null, varArgs);
311 logger(message);
312 }
313};
314var logWrapper = function (prefix) {
315 return function () {
316 var varArgs = [];
317 for (var _i = 0; _i < arguments.length; _i++) {
318 varArgs[_i] = arguments[_i];
319 }
320 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
321 };
322};
323var error = function () {
324 var varArgs = [];
325 for (var _i = 0; _i < arguments.length; _i++) {
326 varArgs[_i] = arguments[_i];
327 }
328 var message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
329 logClient.error(message);
330};
331var fatal = function () {
332 var varArgs = [];
333 for (var _i = 0; _i < arguments.length; _i++) {
334 varArgs[_i] = arguments[_i];
335 }
336 var message = "FIREBASE FATAL ERROR: " + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
337 logClient.error(message);
338 throw new Error(message);
339};
340var warn = function () {
341 var varArgs = [];
342 for (var _i = 0; _i < arguments.length; _i++) {
343 varArgs[_i] = arguments[_i];
344 }
345 var message = 'FIREBASE WARNING: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
346 logClient.warn(message);
347};
348/**
349 * Logs a warning if the containing page uses https. Called when a call to new Firebase
350 * does not use https.
351 */
352var warnIfPageIsSecure = function () {
353 // Be very careful accessing browser globals. Who knows what may or may not exist.
354 if (typeof window !== 'undefined' &&
355 window.location &&
356 window.location.protocol &&
357 window.location.protocol.indexOf('https:') !== -1) {
358 warn('Insecure Firebase access from a secure page. ' +
359 'Please use https in calls to new Firebase().');
360 }
361};
362/**
363 * Returns true if data is NaN, or +/- Infinity.
364 */
365var isInvalidJSONNumber = function (data) {
366 return (typeof data === 'number' &&
367 (data !== data || // NaN
368 data === Number.POSITIVE_INFINITY ||
369 data === Number.NEGATIVE_INFINITY));
370};
371var executeWhenDOMReady = function (fn) {
372 if (util.isNodeSdk() || document.readyState === 'complete') {
373 fn();
374 }
375 else {
376 // Modeled after jQuery. Try DOMContentLoaded and onreadystatechange (which
377 // fire before onload), but fall back to onload.
378 var called_1 = false;
379 var wrappedFn_1 = function () {
380 if (!document.body) {
381 setTimeout(wrappedFn_1, Math.floor(10));
382 return;
383 }
384 if (!called_1) {
385 called_1 = true;
386 fn();
387 }
388 };
389 if (document.addEventListener) {
390 document.addEventListener('DOMContentLoaded', wrappedFn_1, false);
391 // fallback to onload.
392 window.addEventListener('load', wrappedFn_1, false);
393 // eslint-disable-next-line @typescript-eslint/no-explicit-any
394 }
395 else if (document.attachEvent) {
396 // IE.
397 // eslint-disable-next-line @typescript-eslint/no-explicit-any
398 document.attachEvent('onreadystatechange', function () {
399 if (document.readyState === 'complete') {
400 wrappedFn_1();
401 }
402 });
403 // fallback to onload.
404 // eslint-disable-next-line @typescript-eslint/no-explicit-any
405 window.attachEvent('onload', wrappedFn_1);
406 // jQuery has an extra hack for IE that we could employ (based on
407 // http://javascript.nwbox.com/IEContentLoaded/) But it looks really old.
408 // I'm hoping we don't need it.
409 }
410 }
411};
412/**
413 * Minimum key name. Invalid for actual data, used as a marker to sort before any valid names
414 */
415var MIN_NAME = '[MIN_NAME]';
416/**
417 * Maximum key name. Invalid for actual data, used as a marker to sort above any valid names
418 */
419var MAX_NAME = '[MAX_NAME]';
420/**
421 * Compares valid Firebase key names, plus min and max name
422 */
423var nameCompare = function (a, b) {
424 if (a === b) {
425 return 0;
426 }
427 else if (a === MIN_NAME || b === MAX_NAME) {
428 return -1;
429 }
430 else if (b === MIN_NAME || a === MAX_NAME) {
431 return 1;
432 }
433 else {
434 var aAsInt = tryParseInt(a), bAsInt = tryParseInt(b);
435 if (aAsInt !== null) {
436 if (bAsInt !== null) {
437 return aAsInt - bAsInt === 0 ? a.length - b.length : aAsInt - bAsInt;
438 }
439 else {
440 return -1;
441 }
442 }
443 else if (bAsInt !== null) {
444 return 1;
445 }
446 else {
447 return a < b ? -1 : 1;
448 }
449 }
450};
451/**
452 * @returns {!number} comparison result.
453 */
454var stringCompare = function (a, b) {
455 if (a === b) {
456 return 0;
457 }
458 else if (a < b) {
459 return -1;
460 }
461 else {
462 return 1;
463 }
464};
465var requireKey = function (key, obj) {
466 if (obj && key in obj) {
467 return obj[key];
468 }
469 else {
470 throw new Error('Missing required key (' + key + ') in object: ' + util.stringify(obj));
471 }
472};
473var ObjectToUniqueKey = function (obj) {
474 if (typeof obj !== 'object' || obj === null) {
475 return util.stringify(obj);
476 }
477 var keys = [];
478 // eslint-disable-next-line guard-for-in
479 for (var k in obj) {
480 keys.push(k);
481 }
482 // Export as json, but with the keys sorted.
483 keys.sort();
484 var key = '{';
485 for (var i = 0; i < keys.length; i++) {
486 if (i !== 0) {
487 key += ',';
488 }
489 key += util.stringify(keys[i]);
490 key += ':';
491 key += ObjectToUniqueKey(obj[keys[i]]);
492 }
493 key += '}';
494 return key;
495};
496/**
497 * Splits a string into a number of smaller segments of maximum size
498 * @param str - The string
499 * @param segsize - The maximum number of chars in the string.
500 * @returns The string, split into appropriately-sized chunks
501 */
502var splitStringBySize = function (str, segsize) {
503 var len = str.length;
504 if (len <= segsize) {
505 return [str];
506 }
507 var dataSegs = [];
508 for (var c = 0; c < len; c += segsize) {
509 if (c + segsize > len) {
510 dataSegs.push(str.substring(c, len));
511 }
512 else {
513 dataSegs.push(str.substring(c, c + segsize));
514 }
515 }
516 return dataSegs;
517};
518/**
519 * Apply a function to each (key, value) pair in an object or
520 * apply a function to each (index, value) pair in an array
521 * @param obj - The object or array to iterate over
522 * @param fn - The function to apply
523 */
524function each(obj, fn) {
525 for (var key in obj) {
526 if (obj.hasOwnProperty(key)) {
527 fn(key, obj[key]);
528 }
529 }
530}
531/**
532 * Borrowed from http://hg.secondlife.com/llsd/src/tip/js/typedarray.js (MIT License)
533 * I made one modification at the end and removed the NaN / Infinity
534 * handling (since it seemed broken [caused an overflow] and we don't need it). See MJL comments.
535 * @param v - A double
536 *
537 */
538var doubleToIEEE754String = function (v) {
539 util.assert(!isInvalidJSONNumber(v), 'Invalid JSON number'); // MJL
540 var ebits = 11, fbits = 52;
541 var bias = (1 << (ebits - 1)) - 1;
542 var s, e, f, ln, i;
543 // Compute sign, exponent, fraction
544 // Skip NaN / Infinity handling --MJL.
545 if (v === 0) {
546 e = 0;
547 f = 0;
548 s = 1 / v === -Infinity ? 1 : 0;
549 }
550 else {
551 s = v < 0;
552 v = Math.abs(v);
553 if (v >= Math.pow(2, 1 - bias)) {
554 // Normalized
555 ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
556 e = ln + bias;
557 f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
558 }
559 else {
560 // Denormalized
561 e = 0;
562 f = Math.round(v / Math.pow(2, 1 - bias - fbits));
563 }
564 }
565 // Pack sign, exponent, fraction
566 var bits = [];
567 for (i = fbits; i; i -= 1) {
568 bits.push(f % 2 ? 1 : 0);
569 f = Math.floor(f / 2);
570 }
571 for (i = ebits; i; i -= 1) {
572 bits.push(e % 2 ? 1 : 0);
573 e = Math.floor(e / 2);
574 }
575 bits.push(s ? 1 : 0);
576 bits.reverse();
577 var str = bits.join('');
578 // Return the data as a hex string. --MJL
579 var hexByteString = '';
580 for (i = 0; i < 64; i += 8) {
581 var hexByte = parseInt(str.substr(i, 8), 2).toString(16);
582 if (hexByte.length === 1) {
583 hexByte = '0' + hexByte;
584 }
585 hexByteString = hexByteString + hexByte;
586 }
587 return hexByteString.toLowerCase();
588};
589/**
590 * Used to detect if we're in a Chrome content script (which executes in an
591 * isolated environment where long-polling doesn't work).
592 */
593var isChromeExtensionContentScript = function () {
594 return !!(typeof window === 'object' &&
595 window['chrome'] &&
596 window['chrome']['extension'] &&
597 !/^chrome/.test(window.location.href));
598};
599/**
600 * Used to detect if we're in a Windows 8 Store app.
601 */
602var isWindowsStoreApp = function () {
603 // Check for the presence of a couple WinRT globals
604 return typeof Windows === 'object' && typeof Windows.UI === 'object';
605};
606/**
607 * Converts a server error code to a Javascript Error
608 */
609function errorForServerCode(code, query) {
610 var reason = 'Unknown Error';
611 if (code === 'too_big') {
612 reason =
613 'The data requested exceeds the maximum size ' +
614 'that can be accessed with a single request.';
615 }
616 else if (code === 'permission_denied') {
617 reason = "Client doesn't have permission to access the desired data.";
618 }
619 else if (code === 'unavailable') {
620 reason = 'The service is unavailable';
621 }
622 var error = new Error(code + ' at ' + query._path.toString() + ': ' + reason);
623 // eslint-disable-next-line @typescript-eslint/no-explicit-any
624 error.code = code.toUpperCase();
625 return error;
626}
627/**
628 * Used to test for integer-looking strings
629 */
630var INTEGER_REGEXP_ = new RegExp('^-?(0*)\\d{1,10}$');
631/**
632 * For use in keys, the minimum possible 32-bit integer.
633 */
634var INTEGER_32_MIN = -2147483648;
635/**
636 * For use in kyes, the maximum possible 32-bit integer.
637 */
638var INTEGER_32_MAX = 2147483647;
639/**
640 * If the string contains a 32-bit integer, return it. Else return null.
641 */
642var tryParseInt = function (str) {
643 if (INTEGER_REGEXP_.test(str)) {
644 var intVal = Number(str);
645 if (intVal >= INTEGER_32_MIN && intVal <= INTEGER_32_MAX) {
646 return intVal;
647 }
648 }
649 return null;
650};
651/**
652 * Helper to run some code but catch any exceptions and re-throw them later.
653 * Useful for preventing user callbacks from breaking internal code.
654 *
655 * Re-throwing the exception from a setTimeout is a little evil, but it's very
656 * convenient (we don't have to try to figure out when is a safe point to
657 * re-throw it), and the behavior seems reasonable:
658 *
659 * * If you aren't pausing on exceptions, you get an error in the console with
660 * the correct stack trace.
661 * * If you're pausing on all exceptions, the debugger will pause on your
662 * exception and then again when we rethrow it.
663 * * If you're only pausing on uncaught exceptions, the debugger will only pause
664 * on us re-throwing it.
665 *
666 * @param fn - The code to guard.
667 */
668var exceptionGuard = function (fn) {
669 try {
670 fn();
671 }
672 catch (e) {
673 // Re-throw exception when it's safe.
674 setTimeout(function () {
675 // It used to be that "throw e" would result in a good console error with
676 // relevant context, but as of Chrome 39, you just get the firebase.js
677 // file/line number where we re-throw it, which is useless. So we log
678 // e.stack explicitly.
679 var stack = e.stack || '';
680 warn('Exception was thrown by user callback.', stack);
681 throw e;
682 }, Math.floor(0));
683 }
684};
685/**
686 * @returns {boolean} true if we think we're currently being crawled.
687 */
688var beingCrawled = function () {
689 var userAgent = (typeof window === 'object' &&
690 window['navigator'] &&
691 window['navigator']['userAgent']) ||
692 '';
693 // For now we whitelist the most popular crawlers. We should refine this to be the set of crawlers we
694 // believe to support JavaScript/AJAX rendering.
695 // NOTE: Google Webmaster Tools doesn't really belong, but their "This is how a visitor to your website
696 // would have seen the page" is flaky if we don't treat it as a crawler.
697 return (userAgent.search(/googlebot|google webmaster tools|bingbot|yahoo! slurp|baiduspider|yandexbot|duckduckbot/i) >= 0);
698};
699/**
700 * Same as setTimeout() except on Node.JS it will /not/ prevent the process from exiting.
701 *
702 * It is removed with clearTimeout() as normal.
703 *
704 * @param fn - Function to run.
705 * @param time - Milliseconds to wait before running.
706 * @returns The setTimeout() return value.
707 */
708var setTimeoutNonBlocking = function (fn, time) {
709 var timeout = setTimeout(fn, time);
710 // eslint-disable-next-line @typescript-eslint/no-explicit-any
711 if (typeof timeout === 'object' && timeout['unref']) {
712 // eslint-disable-next-line @typescript-eslint/no-explicit-any
713 timeout['unref']();
714 }
715 return timeout;
716};
717
718/**
719 * @license
720 * Copyright 2017 Google LLC
721 *
722 * Licensed under the Apache License, Version 2.0 (the "License");
723 * you may not use this file except in compliance with the License.
724 * You may obtain a copy of the License at
725 *
726 * http://www.apache.org/licenses/LICENSE-2.0
727 *
728 * Unless required by applicable law or agreed to in writing, software
729 * distributed under the License is distributed on an "AS IS" BASIS,
730 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
731 * See the License for the specific language governing permissions and
732 * limitations under the License.
733 */
734/**
735 * A class that holds metadata about a Repo object
736 */
737var RepoInfo = /** @class */ (function () {
738 /**
739 * @param host - Hostname portion of the url for the repo
740 * @param secure - Whether or not this repo is accessed over ssl
741 * @param namespace - The namespace represented by the repo
742 * @param webSocketOnly - Whether to prefer websockets over all other transports (used by Nest).
743 * @param nodeAdmin - Whether this instance uses Admin SDK credentials
744 * @param persistenceKey - Override the default session persistence storage key
745 */
746 function RepoInfo(host, secure, namespace, webSocketOnly, nodeAdmin, persistenceKey, includeNamespaceInQueryParams) {
747 if (nodeAdmin === void 0) { nodeAdmin = false; }
748 if (persistenceKey === void 0) { persistenceKey = ''; }
749 if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
750 this.secure = secure;
751 this.namespace = namespace;
752 this.webSocketOnly = webSocketOnly;
753 this.nodeAdmin = nodeAdmin;
754 this.persistenceKey = persistenceKey;
755 this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
756 this._host = host.toLowerCase();
757 this._domain = this._host.substr(this._host.indexOf('.') + 1);
758 this.internalHost =
759 PersistentStorage.get('host:' + host) || this._host;
760 }
761 RepoInfo.prototype.isCacheableHost = function () {
762 return this.internalHost.substr(0, 2) === 's-';
763 };
764 RepoInfo.prototype.isCustomHost = function () {
765 return (this._domain !== 'firebaseio.com' &&
766 this._domain !== 'firebaseio-demo.com');
767 };
768 Object.defineProperty(RepoInfo.prototype, "host", {
769 get: function () {
770 return this._host;
771 },
772 set: function (newHost) {
773 if (newHost !== this.internalHost) {
774 this.internalHost = newHost;
775 if (this.isCacheableHost()) {
776 PersistentStorage.set('host:' + this._host, this.internalHost);
777 }
778 }
779 },
780 enumerable: false,
781 configurable: true
782 });
783 RepoInfo.prototype.toString = function () {
784 var str = this.toURLString();
785 if (this.persistenceKey) {
786 str += '<' + this.persistenceKey + '>';
787 }
788 return str;
789 };
790 RepoInfo.prototype.toURLString = function () {
791 var protocol = this.secure ? 'https://' : 'http://';
792 var query = this.includeNamespaceInQueryParams
793 ? "?ns=" + this.namespace
794 : '';
795 return "" + protocol + this.host + "/" + query;
796 };
797 return RepoInfo;
798}());
799function repoInfoNeedsQueryParam(repoInfo) {
800 return (repoInfo.host !== repoInfo.internalHost ||
801 repoInfo.isCustomHost() ||
802 repoInfo.includeNamespaceInQueryParams);
803}
804/**
805 * Returns the websocket URL for this repo
806 * @param repoInfo - RepoInfo object
807 * @param type - of connection
808 * @param params - list
809 * @returns The URL for this repo
810 */
811function repoInfoConnectionURL(repoInfo, type, params) {
812 util.assert(typeof type === 'string', 'typeof type must == string');
813 util.assert(typeof params === 'object', 'typeof params must == object');
814 var connURL;
815 if (type === WEBSOCKET) {
816 connURL =
817 (repoInfo.secure ? 'wss://' : 'ws://') + repoInfo.internalHost + '/.ws?';
818 }
819 else if (type === LONG_POLLING) {
820 connURL =
821 (repoInfo.secure ? 'https://' : 'http://') +
822 repoInfo.internalHost +
823 '/.lp?';
824 }
825 else {
826 throw new Error('Unknown connection type: ' + type);
827 }
828 if (repoInfoNeedsQueryParam(repoInfo)) {
829 params['ns'] = repoInfo.namespace;
830 }
831 var pairs = [];
832 each(params, function (key, value) {
833 pairs.push(key + '=' + value);
834 });
835 return connURL + pairs.join('&');
836}
837
838/**
839 * @license
840 * Copyright 2017 Google LLC
841 *
842 * Licensed under the Apache License, Version 2.0 (the "License");
843 * you may not use this file except in compliance with the License.
844 * You may obtain a copy of the License at
845 *
846 * http://www.apache.org/licenses/LICENSE-2.0
847 *
848 * Unless required by applicable law or agreed to in writing, software
849 * distributed under the License is distributed on an "AS IS" BASIS,
850 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
851 * See the License for the specific language governing permissions and
852 * limitations under the License.
853 */
854/**
855 * Tracks a collection of stats.
856 */
857var StatsCollection = /** @class */ (function () {
858 function StatsCollection() {
859 this.counters_ = {};
860 }
861 StatsCollection.prototype.incrementCounter = function (name, amount) {
862 if (amount === void 0) { amount = 1; }
863 if (!util.contains(this.counters_, name)) {
864 this.counters_[name] = 0;
865 }
866 this.counters_[name] += amount;
867 };
868 StatsCollection.prototype.get = function () {
869 return util.deepCopy(this.counters_);
870 };
871 return StatsCollection;
872}());
873
874/**
875 * @license
876 * Copyright 2017 Google LLC
877 *
878 * Licensed under the Apache License, Version 2.0 (the "License");
879 * you may not use this file except in compliance with the License.
880 * You may obtain a copy of the License at
881 *
882 * http://www.apache.org/licenses/LICENSE-2.0
883 *
884 * Unless required by applicable law or agreed to in writing, software
885 * distributed under the License is distributed on an "AS IS" BASIS,
886 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
887 * See the License for the specific language governing permissions and
888 * limitations under the License.
889 */
890var collections = {};
891var reporters = {};
892function statsManagerGetCollection(repoInfo) {
893 var hashString = repoInfo.toString();
894 if (!collections[hashString]) {
895 collections[hashString] = new StatsCollection();
896 }
897 return collections[hashString];
898}
899function statsManagerGetOrCreateReporter(repoInfo, creatorFunction) {
900 var hashString = repoInfo.toString();
901 if (!reporters[hashString]) {
902 reporters[hashString] = creatorFunction();
903 }
904 return reporters[hashString];
905}
906
907/**
908 * @license
909 * Copyright 2019 Google LLC
910 *
911 * Licensed under the Apache License, Version 2.0 (the "License");
912 * you may not use this file except in compliance with the License.
913 * You may obtain a copy of the License at
914 *
915 * http://www.apache.org/licenses/LICENSE-2.0
916 *
917 * Unless required by applicable law or agreed to in writing, software
918 * distributed under the License is distributed on an "AS IS" BASIS,
919 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
920 * See the License for the specific language governing permissions and
921 * limitations under the License.
922 */
923/** The semver (www.semver.org) version of the SDK. */
924var SDK_VERSION = '';
925/**
926 * SDK_VERSION should be set before any database instance is created
927 * @internal
928 */
929function setSDKVersion(version) {
930 SDK_VERSION = version;
931}
932
933/**
934 * @license
935 * Copyright 2017 Google LLC
936 *
937 * Licensed under the Apache License, Version 2.0 (the "License");
938 * you may not use this file except in compliance with the License.
939 * You may obtain a copy of the License at
940 *
941 * http://www.apache.org/licenses/LICENSE-2.0
942 *
943 * Unless required by applicable law or agreed to in writing, software
944 * distributed under the License is distributed on an "AS IS" BASIS,
945 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
946 * See the License for the specific language governing permissions and
947 * limitations under the License.
948 */
949var WEBSOCKET_MAX_FRAME_SIZE = 16384;
950var WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
951var WebSocketImpl = null;
952if (typeof MozWebSocket !== 'undefined') {
953 WebSocketImpl = MozWebSocket;
954}
955else if (typeof WebSocket !== 'undefined') {
956 WebSocketImpl = WebSocket;
957}
958function setWebSocketImpl(impl) {
959 WebSocketImpl = impl;
960}
961/**
962 * Create a new websocket connection with the given callbacks.
963 */
964var WebSocketConnection = /** @class */ (function () {
965 /**
966 * @param connId identifier for this transport
967 * @param repoInfo The info for the websocket endpoint.
968 * @param applicationId The Firebase App ID for this project.
969 * @param appCheckToken The App Check Token for this client.
970 * @param authToken The Auth Token for this client.
971 * @param transportSessionId Optional transportSessionId if this is connecting
972 * to an existing transport session
973 * @param lastSessionId Optional lastSessionId if there was a previous
974 * connection
975 */
976 function WebSocketConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
977 this.connId = connId;
978 this.applicationId = applicationId;
979 this.appCheckToken = appCheckToken;
980 this.authToken = authToken;
981 this.keepaliveTimer = null;
982 this.frames = null;
983 this.totalFrames = 0;
984 this.bytesSent = 0;
985 this.bytesReceived = 0;
986 this.log_ = logWrapper(this.connId);
987 this.stats_ = statsManagerGetCollection(repoInfo);
988 this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId, appCheckToken);
989 this.nodeAdmin = repoInfo.nodeAdmin;
990 }
991 /**
992 * @param repoInfo - The info for the websocket endpoint.
993 * @param transportSessionId - Optional transportSessionId if this is connecting to an existing transport
994 * session
995 * @param lastSessionId - Optional lastSessionId if there was a previous connection
996 * @returns connection url
997 */
998 WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId, appCheckToken) {
999 var urlParams = {};
1000 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1001 if (!util.isNodeSdk() &&
1002 typeof location !== 'undefined' &&
1003 location.hostname &&
1004 FORGE_DOMAIN_RE.test(location.hostname)) {
1005 urlParams[REFERER_PARAM] = FORGE_REF;
1006 }
1007 if (transportSessionId) {
1008 urlParams[TRANSPORT_SESSION_PARAM] = transportSessionId;
1009 }
1010 if (lastSessionId) {
1011 urlParams[LAST_SESSION_PARAM] = lastSessionId;
1012 }
1013 if (appCheckToken) {
1014 urlParams[APP_CHECK_TOKEN_PARAM] = appCheckToken;
1015 }
1016 return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
1017 };
1018 /**
1019 * @param onMessage - Callback when messages arrive
1020 * @param onDisconnect - Callback with connection lost.
1021 */
1022 WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
1023 var _this = this;
1024 this.onDisconnect = onDisconnect;
1025 this.onMessage = onMessage;
1026 this.log_('Websocket connecting to ' + this.connURL);
1027 this.everConnected_ = false;
1028 // Assume failure until proven otherwise.
1029 PersistentStorage.set('previous_websocket_failure', true);
1030 try {
1031 if (util.isNodeSdk()) {
1032 var device = this.nodeAdmin ? 'AdminNode' : 'Node';
1033 // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
1034 var options = {
1035 headers: {
1036 'User-Agent': "Firebase/" + PROTOCOL_VERSION + "/" + SDK_VERSION + "/" + process.platform + "/" + device,
1037 'X-Firebase-GMPID': this.applicationId || ''
1038 }
1039 };
1040 // If using Node with admin creds, AppCheck-related checks are unnecessary.
1041 // Note that we send the credentials here even if they aren't admin credentials, which is
1042 // not a problem.
1043 // Note that this header is just used to bypass appcheck, and the token should still be sent
1044 // through the websocket connection once it is established.
1045 if (this.authToken) {
1046 options.headers['Authorization'] = "Bearer " + this.authToken;
1047 }
1048 if (this.appCheckToken) {
1049 options.headers['X-Firebase-AppCheck'] = this.appCheckToken;
1050 }
1051 // Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
1052 var env = process['env'];
1053 var proxy = this.connURL.indexOf('wss://') === 0
1054 ? env['HTTPS_PROXY'] || env['https_proxy']
1055 : env['HTTP_PROXY'] || env['http_proxy'];
1056 if (proxy) {
1057 options['proxy'] = { origin: proxy };
1058 }
1059 this.mySock = new WebSocketImpl(this.connURL, [], options);
1060 }
1061 else {
1062 var options = {
1063 headers: {
1064 'X-Firebase-GMPID': this.applicationId || '',
1065 'X-Firebase-AppCheck': this.appCheckToken || ''
1066 }
1067 };
1068 this.mySock = new WebSocketImpl(this.connURL, [], options);
1069 }
1070 }
1071 catch (e) {
1072 this.log_('Error instantiating WebSocket.');
1073 var error = e.message || e.data;
1074 if (error) {
1075 this.log_(error);
1076 }
1077 this.onClosed_();
1078 return;
1079 }
1080 this.mySock.onopen = function () {
1081 _this.log_('Websocket connected.');
1082 _this.everConnected_ = true;
1083 };
1084 this.mySock.onclose = function () {
1085 _this.log_('Websocket connection was disconnected.');
1086 _this.mySock = null;
1087 _this.onClosed_();
1088 };
1089 this.mySock.onmessage = function (m) {
1090 _this.handleIncomingFrame(m);
1091 };
1092 this.mySock.onerror = function (e) {
1093 _this.log_('WebSocket error. Closing connection.');
1094 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1095 var error = e.message || e.data;
1096 if (error) {
1097 _this.log_(error);
1098 }
1099 _this.onClosed_();
1100 };
1101 };
1102 /**
1103 * No-op for websockets, we don't need to do anything once the connection is confirmed as open
1104 */
1105 WebSocketConnection.prototype.start = function () { };
1106 WebSocketConnection.forceDisallow = function () {
1107 WebSocketConnection.forceDisallow_ = true;
1108 };
1109 WebSocketConnection.isAvailable = function () {
1110 var isOldAndroid = false;
1111 if (typeof navigator !== 'undefined' && navigator.userAgent) {
1112 var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
1113 var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
1114 if (oldAndroidMatch && oldAndroidMatch.length > 1) {
1115 if (parseFloat(oldAndroidMatch[1]) < 4.4) {
1116 isOldAndroid = true;
1117 }
1118 }
1119 }
1120 return (!isOldAndroid &&
1121 WebSocketImpl !== null &&
1122 !WebSocketConnection.forceDisallow_);
1123 };
1124 /**
1125 * Returns true if we previously failed to connect with this transport.
1126 */
1127 WebSocketConnection.previouslyFailed = function () {
1128 // If our persistent storage is actually only in-memory storage,
1129 // we default to assuming that it previously failed to be safe.
1130 return (PersistentStorage.isInMemoryStorage ||
1131 PersistentStorage.get('previous_websocket_failure') === true);
1132 };
1133 WebSocketConnection.prototype.markConnectionHealthy = function () {
1134 PersistentStorage.remove('previous_websocket_failure');
1135 };
1136 WebSocketConnection.prototype.appendFrame_ = function (data) {
1137 this.frames.push(data);
1138 if (this.frames.length === this.totalFrames) {
1139 var fullMess = this.frames.join('');
1140 this.frames = null;
1141 var jsonMess = util.jsonEval(fullMess);
1142 //handle the message
1143 this.onMessage(jsonMess);
1144 }
1145 };
1146 /**
1147 * @param frameCount - The number of frames we are expecting from the server
1148 */
1149 WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
1150 this.totalFrames = frameCount;
1151 this.frames = [];
1152 };
1153 /**
1154 * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
1155 * @returns Any remaining data to be process, or null if there is none
1156 */
1157 WebSocketConnection.prototype.extractFrameCount_ = function (data) {
1158 util.assert(this.frames === null, 'We already have a frame buffer');
1159 // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
1160 // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
1161 if (data.length <= 6) {
1162 var frameCount = Number(data);
1163 if (!isNaN(frameCount)) {
1164 this.handleNewFrameCount_(frameCount);
1165 return null;
1166 }
1167 }
1168 this.handleNewFrameCount_(1);
1169 return data;
1170 };
1171 /**
1172 * Process a websocket frame that has arrived from the server.
1173 * @param mess - The frame data
1174 */
1175 WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
1176 if (this.mySock === null) {
1177 return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
1178 }
1179 var data = mess['data'];
1180 this.bytesReceived += data.length;
1181 this.stats_.incrementCounter('bytes_received', data.length);
1182 this.resetKeepAlive();
1183 if (this.frames !== null) {
1184 // we're buffering
1185 this.appendFrame_(data);
1186 }
1187 else {
1188 // try to parse out a frame count, otherwise, assume 1 and process it
1189 var remainingData = this.extractFrameCount_(data);
1190 if (remainingData !== null) {
1191 this.appendFrame_(remainingData);
1192 }
1193 }
1194 };
1195 /**
1196 * Send a message to the server
1197 * @param data - The JSON object to transmit
1198 */
1199 WebSocketConnection.prototype.send = function (data) {
1200 this.resetKeepAlive();
1201 var dataStr = util.stringify(data);
1202 this.bytesSent += dataStr.length;
1203 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1204 //We can only fit a certain amount in each websocket frame, so we need to split this request
1205 //up into multiple pieces if it doesn't fit in one request.
1206 var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
1207 //Send the length header
1208 if (dataSegs.length > 1) {
1209 this.sendString_(String(dataSegs.length));
1210 }
1211 //Send the actual data in segments.
1212 for (var i = 0; i < dataSegs.length; i++) {
1213 this.sendString_(dataSegs[i]);
1214 }
1215 };
1216 WebSocketConnection.prototype.shutdown_ = function () {
1217 this.isClosed_ = true;
1218 if (this.keepaliveTimer) {
1219 clearInterval(this.keepaliveTimer);
1220 this.keepaliveTimer = null;
1221 }
1222 if (this.mySock) {
1223 this.mySock.close();
1224 this.mySock = null;
1225 }
1226 };
1227 WebSocketConnection.prototype.onClosed_ = function () {
1228 if (!this.isClosed_) {
1229 this.log_('WebSocket is closing itself');
1230 this.shutdown_();
1231 // since this is an internal close, trigger the close listener
1232 if (this.onDisconnect) {
1233 this.onDisconnect(this.everConnected_);
1234 this.onDisconnect = null;
1235 }
1236 }
1237 };
1238 /**
1239 * External-facing close handler.
1240 * Close the websocket and kill the connection.
1241 */
1242 WebSocketConnection.prototype.close = function () {
1243 if (!this.isClosed_) {
1244 this.log_('WebSocket is being closed');
1245 this.shutdown_();
1246 }
1247 };
1248 /**
1249 * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
1250 * the last activity.
1251 */
1252 WebSocketConnection.prototype.resetKeepAlive = function () {
1253 var _this = this;
1254 clearInterval(this.keepaliveTimer);
1255 this.keepaliveTimer = setInterval(function () {
1256 //If there has been no websocket activity for a while, send a no-op
1257 if (_this.mySock) {
1258 _this.sendString_('0');
1259 }
1260 _this.resetKeepAlive();
1261 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1262 }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
1263 };
1264 /**
1265 * Send a string over the websocket.
1266 *
1267 * @param str - String to send.
1268 */
1269 WebSocketConnection.prototype.sendString_ = function (str) {
1270 // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
1271 // calls for some unknown reason. We treat these as an error and disconnect.
1272 // See https://app.asana.com/0/58926111402292/68021340250410
1273 try {
1274 this.mySock.send(str);
1275 }
1276 catch (e) {
1277 this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
1278 setTimeout(this.onClosed_.bind(this), 0);
1279 }
1280 };
1281 /**
1282 * Number of response before we consider the connection "healthy."
1283 */
1284 WebSocketConnection.responsesRequiredToBeHealthy = 2;
1285 /**
1286 * Time to wait for the connection te become healthy before giving up.
1287 */
1288 WebSocketConnection.healthyTimeout = 30000;
1289 return WebSocketConnection;
1290}());
1291
1292var name = "@firebase/database";
1293var version = "0.12.0";
1294
1295/**
1296 * @license
1297 * Copyright 2021 Google LLC
1298 *
1299 * Licensed under the Apache License, Version 2.0 (the "License");
1300 * you may not use this file except in compliance with the License.
1301 * You may obtain a copy of the License at
1302 *
1303 * http://www.apache.org/licenses/LICENSE-2.0
1304 *
1305 * Unless required by applicable law or agreed to in writing, software
1306 * distributed under the License is distributed on an "AS IS" BASIS,
1307 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1308 * See the License for the specific language governing permissions and
1309 * limitations under the License.
1310 */
1311/**
1312 * Abstraction around AppCheck's token fetching capabilities.
1313 */
1314var AppCheckTokenProvider = /** @class */ (function () {
1315 function AppCheckTokenProvider(appName_, appCheckProvider) {
1316 var _this = this;
1317 this.appName_ = appName_;
1318 this.appCheckProvider = appCheckProvider;
1319 this.appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
1320 if (!this.appCheck) {
1321 appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.get().then(function (appCheck) { return (_this.appCheck = appCheck); });
1322 }
1323 }
1324 AppCheckTokenProvider.prototype.getToken = function (forceRefresh) {
1325 var _this = this;
1326 if (!this.appCheck) {
1327 return new Promise(function (resolve, reject) {
1328 // Support delayed initialization of FirebaseAppCheck. This allows our
1329 // customers to initialize the RTDB SDK before initializing Firebase
1330 // AppCheck and ensures that all requests are authenticated if a token
1331 // becomes available before the timoeout below expires.
1332 setTimeout(function () {
1333 if (_this.appCheck) {
1334 _this.getToken(forceRefresh).then(resolve, reject);
1335 }
1336 else {
1337 resolve(null);
1338 }
1339 }, 0);
1340 });
1341 }
1342 return this.appCheck.getToken(forceRefresh);
1343 };
1344 AppCheckTokenProvider.prototype.addTokenChangeListener = function (listener) {
1345 var _a;
1346 (_a = this.appCheckProvider) === null || _a === void 0 ? void 0 : _a.get().then(function (appCheck) { return appCheck.addTokenListener(listener); });
1347 };
1348 AppCheckTokenProvider.prototype.notifyForInvalidToken = function () {
1349 warn("Provided AppCheck credentials for the app named \"" + this.appName_ + "\" " +
1350 'are invalid. This usually indicates your app was not initialized correctly.');
1351 };
1352 return AppCheckTokenProvider;
1353}());
1354
1355/**
1356 * @license
1357 * Copyright 2017 Google LLC
1358 *
1359 * Licensed under the Apache License, Version 2.0 (the "License");
1360 * you may not use this file except in compliance with the License.
1361 * You may obtain a copy of the License at
1362 *
1363 * http://www.apache.org/licenses/LICENSE-2.0
1364 *
1365 * Unless required by applicable law or agreed to in writing, software
1366 * distributed under the License is distributed on an "AS IS" BASIS,
1367 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1368 * See the License for the specific language governing permissions and
1369 * limitations under the License.
1370 */
1371/**
1372 * Abstraction around FirebaseApp's token fetching capabilities.
1373 */
1374var FirebaseAuthTokenProvider = /** @class */ (function () {
1375 function FirebaseAuthTokenProvider(appName_, firebaseOptions_, authProvider_) {
1376 var _this = this;
1377 this.appName_ = appName_;
1378 this.firebaseOptions_ = firebaseOptions_;
1379 this.authProvider_ = authProvider_;
1380 this.auth_ = null;
1381 this.auth_ = authProvider_.getImmediate({ optional: true });
1382 if (!this.auth_) {
1383 authProvider_.onInit(function (auth) { return (_this.auth_ = auth); });
1384 }
1385 }
1386 FirebaseAuthTokenProvider.prototype.getToken = function (forceRefresh) {
1387 var _this = this;
1388 if (!this.auth_) {
1389 return new Promise(function (resolve, reject) {
1390 // Support delayed initialization of FirebaseAuth. This allows our
1391 // customers to initialize the RTDB SDK before initializing Firebase
1392 // Auth and ensures that all requests are authenticated if a token
1393 // becomes available before the timoeout below expires.
1394 setTimeout(function () {
1395 if (_this.auth_) {
1396 _this.getToken(forceRefresh).then(resolve, reject);
1397 }
1398 else {
1399 resolve(null);
1400 }
1401 }, 0);
1402 });
1403 }
1404 return this.auth_.getToken(forceRefresh).catch(function (error) {
1405 // TODO: Need to figure out all the cases this is raised and whether
1406 // this makes sense.
1407 if (error && error.code === 'auth/token-not-initialized') {
1408 log('Got auth/token-not-initialized error. Treating as null token.');
1409 return null;
1410 }
1411 else {
1412 return Promise.reject(error);
1413 }
1414 });
1415 };
1416 FirebaseAuthTokenProvider.prototype.addTokenChangeListener = function (listener) {
1417 // TODO: We might want to wrap the listener and call it with no args to
1418 // avoid a leaky abstraction, but that makes removing the listener harder.
1419 if (this.auth_) {
1420 this.auth_.addAuthTokenListener(listener);
1421 }
1422 else {
1423 this.authProvider_
1424 .get()
1425 .then(function (auth) { return auth.addAuthTokenListener(listener); });
1426 }
1427 };
1428 FirebaseAuthTokenProvider.prototype.removeTokenChangeListener = function (listener) {
1429 this.authProvider_
1430 .get()
1431 .then(function (auth) { return auth.removeAuthTokenListener(listener); });
1432 };
1433 FirebaseAuthTokenProvider.prototype.notifyForInvalidToken = function () {
1434 var errorMessage = 'Provided authentication credentials for the app named "' +
1435 this.appName_ +
1436 '" are invalid. This usually indicates your app was not ' +
1437 'initialized correctly. ';
1438 if ('credential' in this.firebaseOptions_) {
1439 errorMessage +=
1440 'Make sure the "credential" property provided to initializeApp() ' +
1441 'is authorized to access the specified "databaseURL" and is from the correct ' +
1442 'project.';
1443 }
1444 else if ('serviceAccount' in this.firebaseOptions_) {
1445 errorMessage +=
1446 'Make sure the "serviceAccount" property provided to initializeApp() ' +
1447 'is authorized to access the specified "databaseURL" and is from the correct ' +
1448 'project.';
1449 }
1450 else {
1451 errorMessage +=
1452 'Make sure the "apiKey" and "databaseURL" properties provided to ' +
1453 'initializeApp() match the values provided for your app at ' +
1454 'https://console.firebase.google.com/.';
1455 }
1456 warn(errorMessage);
1457 };
1458 return FirebaseAuthTokenProvider;
1459}());
1460/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */
1461var EmulatorTokenProvider = /** @class */ (function () {
1462 function EmulatorTokenProvider(accessToken) {
1463 this.accessToken = accessToken;
1464 }
1465 EmulatorTokenProvider.prototype.getToken = function (forceRefresh) {
1466 return Promise.resolve({
1467 accessToken: this.accessToken
1468 });
1469 };
1470 EmulatorTokenProvider.prototype.addTokenChangeListener = function (listener) {
1471 // Invoke the listener immediately to match the behavior in Firebase Auth
1472 // (see packages/auth/src/auth.js#L1807)
1473 listener(this.accessToken);
1474 };
1475 EmulatorTokenProvider.prototype.removeTokenChangeListener = function (listener) { };
1476 EmulatorTokenProvider.prototype.notifyForInvalidToken = function () { };
1477 /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */
1478 EmulatorTokenProvider.OWNER = 'owner';
1479 return EmulatorTokenProvider;
1480}());
1481
1482/**
1483 * @license
1484 * Copyright 2017 Google LLC
1485 *
1486 * Licensed under the Apache License, Version 2.0 (the "License");
1487 * you may not use this file except in compliance with the License.
1488 * You may obtain a copy of the License at
1489 *
1490 * http://www.apache.org/licenses/LICENSE-2.0
1491 *
1492 * Unless required by applicable law or agreed to in writing, software
1493 * distributed under the License is distributed on an "AS IS" BASIS,
1494 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1495 * See the License for the specific language governing permissions and
1496 * limitations under the License.
1497 */
1498/**
1499 * This class ensures the packets from the server arrive in order
1500 * This class takes data from the server and ensures it gets passed into the callbacks in order.
1501 */
1502var PacketReceiver = /** @class */ (function () {
1503 /**
1504 * @param onMessage_
1505 */
1506 function PacketReceiver(onMessage_) {
1507 this.onMessage_ = onMessage_;
1508 this.pendingResponses = [];
1509 this.currentResponseNum = 0;
1510 this.closeAfterResponse = -1;
1511 this.onClose = null;
1512 }
1513 PacketReceiver.prototype.closeAfter = function (responseNum, callback) {
1514 this.closeAfterResponse = responseNum;
1515 this.onClose = callback;
1516 if (this.closeAfterResponse < this.currentResponseNum) {
1517 this.onClose();
1518 this.onClose = null;
1519 }
1520 };
1521 /**
1522 * Each message from the server comes with a response number, and an array of data. The responseNumber
1523 * allows us to ensure that we process them in the right order, since we can't be guaranteed that all
1524 * browsers will respond in the same order as the requests we sent
1525 */
1526 PacketReceiver.prototype.handleResponse = function (requestNum, data) {
1527 var _this = this;
1528 this.pendingResponses[requestNum] = data;
1529 var _loop_1 = function () {
1530 var toProcess = this_1.pendingResponses[this_1.currentResponseNum];
1531 delete this_1.pendingResponses[this_1.currentResponseNum];
1532 var _loop_2 = function (i) {
1533 if (toProcess[i]) {
1534 exceptionGuard(function () {
1535 _this.onMessage_(toProcess[i]);
1536 });
1537 }
1538 };
1539 for (var i = 0; i < toProcess.length; ++i) {
1540 _loop_2(i);
1541 }
1542 if (this_1.currentResponseNum === this_1.closeAfterResponse) {
1543 if (this_1.onClose) {
1544 this_1.onClose();
1545 this_1.onClose = null;
1546 }
1547 return "break";
1548 }
1549 this_1.currentResponseNum++;
1550 };
1551 var this_1 = this;
1552 while (this.pendingResponses[this.currentResponseNum]) {
1553 var state_1 = _loop_1();
1554 if (state_1 === "break")
1555 break;
1556 }
1557 };
1558 return PacketReceiver;
1559}());
1560
1561/**
1562 * @license
1563 * Copyright 2017 Google LLC
1564 *
1565 * Licensed under the Apache License, Version 2.0 (the "License");
1566 * you may not use this file except in compliance with the License.
1567 * You may obtain a copy of the License at
1568 *
1569 * http://www.apache.org/licenses/LICENSE-2.0
1570 *
1571 * Unless required by applicable law or agreed to in writing, software
1572 * distributed under the License is distributed on an "AS IS" BASIS,
1573 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1574 * See the License for the specific language governing permissions and
1575 * limitations under the License.
1576 */
1577// URL query parameters associated with longpolling
1578var FIREBASE_LONGPOLL_START_PARAM = 'start';
1579var FIREBASE_LONGPOLL_CLOSE_COMMAND = 'close';
1580var FIREBASE_LONGPOLL_COMMAND_CB_NAME = 'pLPCommand';
1581var FIREBASE_LONGPOLL_DATA_CB_NAME = 'pRTLPCB';
1582var FIREBASE_LONGPOLL_ID_PARAM = 'id';
1583var FIREBASE_LONGPOLL_PW_PARAM = 'pw';
1584var FIREBASE_LONGPOLL_SERIAL_PARAM = 'ser';
1585var FIREBASE_LONGPOLL_CALLBACK_ID_PARAM = 'cb';
1586var FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM = 'seg';
1587var FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET = 'ts';
1588var FIREBASE_LONGPOLL_DATA_PARAM = 'd';
1589var FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM = 'dframe';
1590//Data size constants.
1591//TODO: Perf: the maximum length actually differs from browser to browser.
1592// We should check what browser we're on and set accordingly.
1593var MAX_URL_DATA_SIZE = 1870;
1594var SEG_HEADER_SIZE = 30; //ie: &seg=8299234&ts=982389123&d=
1595var MAX_PAYLOAD_SIZE = MAX_URL_DATA_SIZE - SEG_HEADER_SIZE;
1596/**
1597 * Keepalive period
1598 * send a fresh request at minimum every 25 seconds. Opera has a maximum request
1599 * length of 30 seconds that we can't exceed.
1600 */
1601var KEEPALIVE_REQUEST_INTERVAL = 25000;
1602/**
1603 * How long to wait before aborting a long-polling connection attempt.
1604 */
1605var LP_CONNECT_TIMEOUT = 30000;
1606/**
1607 * This class manages a single long-polling connection.
1608 */
1609var BrowserPollConnection = /** @class */ (function () {
1610 /**
1611 * @param connId An identifier for this connection, used for logging
1612 * @param repoInfo The info for the endpoint to send data to.
1613 * @param applicationId The Firebase App ID for this project.
1614 * @param appCheckToken The AppCheck token for this client.
1615 * @param authToken The AuthToken to use for this connection.
1616 * @param transportSessionId Optional transportSessionid if we are
1617 * reconnecting for an existing transport session
1618 * @param lastSessionId Optional lastSessionId if the PersistentConnection has
1619 * already created a connection previously
1620 */
1621 function BrowserPollConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1622 var _this = this;
1623 this.connId = connId;
1624 this.repoInfo = repoInfo;
1625 this.applicationId = applicationId;
1626 this.appCheckToken = appCheckToken;
1627 this.authToken = authToken;
1628 this.transportSessionId = transportSessionId;
1629 this.lastSessionId = lastSessionId;
1630 this.bytesSent = 0;
1631 this.bytesReceived = 0;
1632 this.everConnected_ = false;
1633 this.log_ = logWrapper(connId);
1634 this.stats_ = statsManagerGetCollection(repoInfo);
1635 this.urlFn = function (params) {
1636 // Always add the token if we have one.
1637 if (_this.appCheckToken) {
1638 params[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1639 }
1640 return repoInfoConnectionURL(repoInfo, LONG_POLLING, params);
1641 };
1642 }
1643 /**
1644 * @param onMessage - Callback when messages arrive
1645 * @param onDisconnect - Callback with connection lost.
1646 */
1647 BrowserPollConnection.prototype.open = function (onMessage, onDisconnect) {
1648 var _this = this;
1649 this.curSegmentNum = 0;
1650 this.onDisconnect_ = onDisconnect;
1651 this.myPacketOrderer = new PacketReceiver(onMessage);
1652 this.isClosed_ = false;
1653 this.connectTimeoutTimer_ = setTimeout(function () {
1654 _this.log_('Timed out trying to connect.');
1655 // Make sure we clear the host cache
1656 _this.onClosed_();
1657 _this.connectTimeoutTimer_ = null;
1658 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1659 }, Math.floor(LP_CONNECT_TIMEOUT));
1660 // Ensure we delay the creation of the iframe until the DOM is loaded.
1661 executeWhenDOMReady(function () {
1662 if (_this.isClosed_) {
1663 return;
1664 }
1665 //Set up a callback that gets triggered once a connection is set up.
1666 _this.scriptTagHolder = new FirebaseIFrameScriptHolder(function () {
1667 var args = [];
1668 for (var _i = 0; _i < arguments.length; _i++) {
1669 args[_i] = arguments[_i];
1670 }
1671 var _a = tslib.__read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2]; _a[3]; _a[4];
1672 _this.incrementIncomingBytes_(args);
1673 if (!_this.scriptTagHolder) {
1674 return; // we closed the connection.
1675 }
1676 if (_this.connectTimeoutTimer_) {
1677 clearTimeout(_this.connectTimeoutTimer_);
1678 _this.connectTimeoutTimer_ = null;
1679 }
1680 _this.everConnected_ = true;
1681 if (command === FIREBASE_LONGPOLL_START_PARAM) {
1682 _this.id = arg1;
1683 _this.password = arg2;
1684 }
1685 else if (command === FIREBASE_LONGPOLL_CLOSE_COMMAND) {
1686 // Don't clear the host cache. We got a response from the server, so we know it's reachable
1687 if (arg1) {
1688 // We aren't expecting any more data (other than what the server's already in the process of sending us
1689 // through our already open polls), so don't send any more.
1690 _this.scriptTagHolder.sendNewPolls = false;
1691 // arg1 in this case is the last response number sent by the server. We should try to receive
1692 // all of the responses up to this one before closing
1693 _this.myPacketOrderer.closeAfter(arg1, function () {
1694 _this.onClosed_();
1695 });
1696 }
1697 else {
1698 _this.onClosed_();
1699 }
1700 }
1701 else {
1702 throw new Error('Unrecognized command received: ' + command);
1703 }
1704 }, function () {
1705 var args = [];
1706 for (var _i = 0; _i < arguments.length; _i++) {
1707 args[_i] = arguments[_i];
1708 }
1709 var _a = tslib.__read(args, 2), pN = _a[0], data = _a[1];
1710 _this.incrementIncomingBytes_(args);
1711 _this.myPacketOrderer.handleResponse(pN, data);
1712 }, function () {
1713 _this.onClosed_();
1714 }, _this.urlFn);
1715 //Send the initial request to connect. The serial number is simply to keep the browser from pulling previous results
1716 //from cache.
1717 var urlParams = {};
1718 urlParams[FIREBASE_LONGPOLL_START_PARAM] = 't';
1719 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = Math.floor(Math.random() * 100000000);
1720 if (_this.scriptTagHolder.uniqueCallbackIdentifier) {
1721 urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] =
1722 _this.scriptTagHolder.uniqueCallbackIdentifier;
1723 }
1724 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1725 if (_this.transportSessionId) {
1726 urlParams[TRANSPORT_SESSION_PARAM] = _this.transportSessionId;
1727 }
1728 if (_this.lastSessionId) {
1729 urlParams[LAST_SESSION_PARAM] = _this.lastSessionId;
1730 }
1731 if (_this.applicationId) {
1732 urlParams[APPLICATION_ID_PARAM] = _this.applicationId;
1733 }
1734 if (_this.appCheckToken) {
1735 urlParams[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1736 }
1737 if (typeof location !== 'undefined' &&
1738 location.hostname &&
1739 FORGE_DOMAIN_RE.test(location.hostname)) {
1740 urlParams[REFERER_PARAM] = FORGE_REF;
1741 }
1742 var connectURL = _this.urlFn(urlParams);
1743 _this.log_('Connecting via long-poll to ' + connectURL);
1744 _this.scriptTagHolder.addTag(connectURL, function () {
1745 /* do nothing */
1746 });
1747 });
1748 };
1749 /**
1750 * Call this when a handshake has completed successfully and we want to consider the connection established
1751 */
1752 BrowserPollConnection.prototype.start = function () {
1753 this.scriptTagHolder.startLongPoll(this.id, this.password);
1754 this.addDisconnectPingFrame(this.id, this.password);
1755 };
1756 /**
1757 * Forces long polling to be considered as a potential transport
1758 */
1759 BrowserPollConnection.forceAllow = function () {
1760 BrowserPollConnection.forceAllow_ = true;
1761 };
1762 /**
1763 * Forces longpolling to not be considered as a potential transport
1764 */
1765 BrowserPollConnection.forceDisallow = function () {
1766 BrowserPollConnection.forceDisallow_ = true;
1767 };
1768 // Static method, use string literal so it can be accessed in a generic way
1769 BrowserPollConnection.isAvailable = function () {
1770 if (util.isNodeSdk()) {
1771 return false;
1772 }
1773 else if (BrowserPollConnection.forceAllow_) {
1774 return true;
1775 }
1776 else {
1777 // NOTE: In React-Native there's normally no 'document', but if you debug a React-Native app in
1778 // the Chrome debugger, 'document' is defined, but document.createElement is null (2015/06/08).
1779 return (!BrowserPollConnection.forceDisallow_ &&
1780 typeof document !== 'undefined' &&
1781 document.createElement != null &&
1782 !isChromeExtensionContentScript() &&
1783 !isWindowsStoreApp());
1784 }
1785 };
1786 /**
1787 * No-op for polling
1788 */
1789 BrowserPollConnection.prototype.markConnectionHealthy = function () { };
1790 /**
1791 * Stops polling and cleans up the iframe
1792 */
1793 BrowserPollConnection.prototype.shutdown_ = function () {
1794 this.isClosed_ = true;
1795 if (this.scriptTagHolder) {
1796 this.scriptTagHolder.close();
1797 this.scriptTagHolder = null;
1798 }
1799 //remove the disconnect frame, which will trigger an XHR call to the server to tell it we're leaving.
1800 if (this.myDisconnFrame) {
1801 document.body.removeChild(this.myDisconnFrame);
1802 this.myDisconnFrame = null;
1803 }
1804 if (this.connectTimeoutTimer_) {
1805 clearTimeout(this.connectTimeoutTimer_);
1806 this.connectTimeoutTimer_ = null;
1807 }
1808 };
1809 /**
1810 * Triggered when this transport is closed
1811 */
1812 BrowserPollConnection.prototype.onClosed_ = function () {
1813 if (!this.isClosed_) {
1814 this.log_('Longpoll is closing itself');
1815 this.shutdown_();
1816 if (this.onDisconnect_) {
1817 this.onDisconnect_(this.everConnected_);
1818 this.onDisconnect_ = null;
1819 }
1820 }
1821 };
1822 /**
1823 * External-facing close handler. RealTime has requested we shut down. Kill our connection and tell the server
1824 * that we've left.
1825 */
1826 BrowserPollConnection.prototype.close = function () {
1827 if (!this.isClosed_) {
1828 this.log_('Longpoll is being closed.');
1829 this.shutdown_();
1830 }
1831 };
1832 /**
1833 * Send the JSON object down to the server. It will need to be stringified, base64 encoded, and then
1834 * broken into chunks (since URLs have a small maximum length).
1835 * @param data - The JSON data to transmit.
1836 */
1837 BrowserPollConnection.prototype.send = function (data) {
1838 var dataStr = util.stringify(data);
1839 this.bytesSent += dataStr.length;
1840 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1841 //first, lets get the base64-encoded data
1842 var base64data = util.base64Encode(dataStr);
1843 //We can only fit a certain amount in each URL, so we need to split this request
1844 //up into multiple pieces if it doesn't fit in one request.
1845 var dataSegs = splitStringBySize(base64data, MAX_PAYLOAD_SIZE);
1846 //Enqueue each segment for transmission. We assign each chunk a sequential ID and a total number
1847 //of segments so that we can reassemble the packet on the server.
1848 for (var i = 0; i < dataSegs.length; i++) {
1849 this.scriptTagHolder.enqueueSegment(this.curSegmentNum, dataSegs.length, dataSegs[i]);
1850 this.curSegmentNum++;
1851 }
1852 };
1853 /**
1854 * This is how we notify the server that we're leaving.
1855 * We aren't able to send requests with DHTML on a window close event, but we can
1856 * trigger XHR requests in some browsers (everything but Opera basically).
1857 */
1858 BrowserPollConnection.prototype.addDisconnectPingFrame = function (id, pw) {
1859 if (util.isNodeSdk()) {
1860 return;
1861 }
1862 this.myDisconnFrame = document.createElement('iframe');
1863 var urlParams = {};
1864 urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
1865 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = id;
1866 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = pw;
1867 this.myDisconnFrame.src = this.urlFn(urlParams);
1868 this.myDisconnFrame.style.display = 'none';
1869 document.body.appendChild(this.myDisconnFrame);
1870 };
1871 /**
1872 * Used to track the bytes received by this client
1873 */
1874 BrowserPollConnection.prototype.incrementIncomingBytes_ = function (args) {
1875 // TODO: This is an annoying perf hit just to track the number of incoming bytes. Maybe it should be opt-in.
1876 var bytesReceived = util.stringify(args).length;
1877 this.bytesReceived += bytesReceived;
1878 this.stats_.incrementCounter('bytes_received', bytesReceived);
1879 };
1880 return BrowserPollConnection;
1881}());
1882/*********************************************************************************************
1883 * A wrapper around an iframe that is used as a long-polling script holder.
1884 *********************************************************************************************/
1885var FirebaseIFrameScriptHolder = /** @class */ (function () {
1886 /**
1887 * @param commandCB - The callback to be called when control commands are recevied from the server.
1888 * @param onMessageCB - The callback to be triggered when responses arrive from the server.
1889 * @param onDisconnect - The callback to be triggered when this tag holder is closed
1890 * @param urlFn - A function that provides the URL of the endpoint to send data to.
1891 */
1892 function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnect, urlFn) {
1893 this.onDisconnect = onDisconnect;
1894 this.urlFn = urlFn;
1895 //We maintain a count of all of the outstanding requests, because if we have too many active at once it can cause
1896 //problems in some browsers.
1897 this.outstandingRequests = new Set();
1898 //A queue of the pending segments waiting for transmission to the server.
1899 this.pendingSegs = [];
1900 //A serial number. We use this for two things:
1901 // 1) A way to ensure the browser doesn't cache responses to polls
1902 // 2) A way to make the server aware when long-polls arrive in a different order than we started them. The
1903 // server needs to release both polls in this case or it will cause problems in Opera since Opera can only execute
1904 // JSONP code in the order it was added to the iframe.
1905 this.currentSerial = Math.floor(Math.random() * 100000000);
1906 // This gets set to false when we're "closing down" the connection (e.g. we're switching transports but there's still
1907 // incoming data from the server that we're waiting for).
1908 this.sendNewPolls = true;
1909 if (!util.isNodeSdk()) {
1910 //Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
1911 //iframes where we put the long-polling script tags. We have two callbacks:
1912 // 1) Command Callback - Triggered for control issues, like starting a connection.
1913 // 2) Message Callback - Triggered when new data arrives.
1914 this.uniqueCallbackIdentifier = LUIDGenerator();
1915 window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
1916 window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] =
1917 onMessageCB;
1918 //Create an iframe for us to add script tags to.
1919 this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_();
1920 // Set the iframe's contents.
1921 var script = '';
1922 // if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
1923 // for ie9, but ie8 needs to do it again in the document itself.
1924 if (this.myIFrame.src &&
1925 this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
1926 var currentDomain = document.domain;
1927 script = '<script>document.domain="' + currentDomain + '";</script>';
1928 }
1929 var iframeContents = '<html><body>' + script + '</body></html>';
1930 try {
1931 this.myIFrame.doc.open();
1932 this.myIFrame.doc.write(iframeContents);
1933 this.myIFrame.doc.close();
1934 }
1935 catch (e) {
1936 log('frame writing exception');
1937 if (e.stack) {
1938 log(e.stack);
1939 }
1940 log(e);
1941 }
1942 }
1943 else {
1944 this.commandCB = commandCB;
1945 this.onMessageCB = onMessageCB;
1946 }
1947 }
1948 /**
1949 * Each browser has its own funny way to handle iframes. Here we mush them all together into one object that I can
1950 * actually use.
1951 */
1952 FirebaseIFrameScriptHolder.createIFrame_ = function () {
1953 var iframe = document.createElement('iframe');
1954 iframe.style.display = 'none';
1955 // This is necessary in order to initialize the document inside the iframe
1956 if (document.body) {
1957 document.body.appendChild(iframe);
1958 try {
1959 // If document.domain has been modified in IE, this will throw an error, and we need to set the
1960 // domain of the iframe's document manually. We can do this via a javascript: url as the src attribute
1961 // Also note that we must do this *after* the iframe has been appended to the page. Otherwise it doesn't work.
1962 var a = iframe.contentWindow.document;
1963 if (!a) {
1964 // Apologies for the log-spam, I need to do something to keep closure from optimizing out the assignment above.
1965 log('No IE domain setting required');
1966 }
1967 }
1968 catch (e) {
1969 var domain = document.domain;
1970 iframe.src =
1971 "javascript:void((function(){document.open();document.domain='" +
1972 domain +
1973 "';document.close();})())";
1974 }
1975 }
1976 else {
1977 // LongPollConnection attempts to delay initialization until the document is ready, so hopefully this
1978 // never gets hit.
1979 throw 'Document body has not initialized. Wait to initialize Firebase until after the document is ready.';
1980 }
1981 // Get the document of the iframe in a browser-specific way.
1982 if (iframe.contentDocument) {
1983 iframe.doc = iframe.contentDocument; // Firefox, Opera, Safari
1984 }
1985 else if (iframe.contentWindow) {
1986 iframe.doc = iframe.contentWindow.document; // Internet Explorer
1987 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1988 }
1989 else if (iframe.document) {
1990 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1991 iframe.doc = iframe.document; //others?
1992 }
1993 return iframe;
1994 };
1995 /**
1996 * Cancel all outstanding queries and remove the frame.
1997 */
1998 FirebaseIFrameScriptHolder.prototype.close = function () {
1999 var _this = this;
2000 //Mark this iframe as dead, so no new requests are sent.
2001 this.alive = false;
2002 if (this.myIFrame) {
2003 //We have to actually remove all of the html inside this iframe before removing it from the
2004 //window, or IE will continue loading and executing the script tags we've already added, which
2005 //can lead to some errors being thrown. Setting innerHTML seems to be the easiest way to do this.
2006 this.myIFrame.doc.body.innerHTML = '';
2007 setTimeout(function () {
2008 if (_this.myIFrame !== null) {
2009 document.body.removeChild(_this.myIFrame);
2010 _this.myIFrame = null;
2011 }
2012 }, Math.floor(0));
2013 }
2014 // Protect from being called recursively.
2015 var onDisconnect = this.onDisconnect;
2016 if (onDisconnect) {
2017 this.onDisconnect = null;
2018 onDisconnect();
2019 }
2020 };
2021 /**
2022 * Actually start the long-polling session by adding the first script tag(s) to the iframe.
2023 * @param id - The ID of this connection
2024 * @param pw - The password for this connection
2025 */
2026 FirebaseIFrameScriptHolder.prototype.startLongPoll = function (id, pw) {
2027 this.myID = id;
2028 this.myPW = pw;
2029 this.alive = true;
2030 //send the initial request. If there are requests queued, make sure that we transmit as many as we are currently able to.
2031 while (this.newRequest_()) { }
2032 };
2033 /**
2034 * This is called any time someone might want a script tag to be added. It adds a script tag when there aren't
2035 * too many outstanding requests and we are still alive.
2036 *
2037 * If there are outstanding packet segments to send, it sends one. If there aren't, it sends a long-poll anyways if
2038 * needed.
2039 */
2040 FirebaseIFrameScriptHolder.prototype.newRequest_ = function () {
2041 // We keep one outstanding request open all the time to receive data, but if we need to send data
2042 // (pendingSegs.length > 0) then we create a new request to send the data. The server will automatically
2043 // close the old request.
2044 if (this.alive &&
2045 this.sendNewPolls &&
2046 this.outstandingRequests.size < (this.pendingSegs.length > 0 ? 2 : 1)) {
2047 //construct our url
2048 this.currentSerial++;
2049 var urlParams = {};
2050 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
2051 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
2052 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = this.currentSerial;
2053 var theURL = this.urlFn(urlParams);
2054 //Now add as much data as we can.
2055 var curDataString = '';
2056 var i = 0;
2057 while (this.pendingSegs.length > 0) {
2058 //first, lets see if the next segment will fit.
2059 var nextSeg = this.pendingSegs[0];
2060 if (nextSeg.d.length +
2061 SEG_HEADER_SIZE +
2062 curDataString.length <=
2063 MAX_URL_DATA_SIZE) {
2064 //great, the segment will fit. Lets append it.
2065 var theSeg = this.pendingSegs.shift();
2066 curDataString =
2067 curDataString +
2068 '&' +
2069 FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM +
2070 i +
2071 '=' +
2072 theSeg.seg +
2073 '&' +
2074 FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET +
2075 i +
2076 '=' +
2077 theSeg.ts +
2078 '&' +
2079 FIREBASE_LONGPOLL_DATA_PARAM +
2080 i +
2081 '=' +
2082 theSeg.d;
2083 i++;
2084 }
2085 else {
2086 break;
2087 }
2088 }
2089 theURL = theURL + curDataString;
2090 this.addLongPollTag_(theURL, this.currentSerial);
2091 return true;
2092 }
2093 else {
2094 return false;
2095 }
2096 };
2097 /**
2098 * Queue a packet for transmission to the server.
2099 * @param segnum - A sequential id for this packet segment used for reassembly
2100 * @param totalsegs - The total number of segments in this packet
2101 * @param data - The data for this segment.
2102 */
2103 FirebaseIFrameScriptHolder.prototype.enqueueSegment = function (segnum, totalsegs, data) {
2104 //add this to the queue of segments to send.
2105 this.pendingSegs.push({ seg: segnum, ts: totalsegs, d: data });
2106 //send the data immediately if there isn't already data being transmitted, unless
2107 //startLongPoll hasn't been called yet.
2108 if (this.alive) {
2109 this.newRequest_();
2110 }
2111 };
2112 /**
2113 * Add a script tag for a regular long-poll request.
2114 * @param url - The URL of the script tag.
2115 * @param serial - The serial number of the request.
2116 */
2117 FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function (url, serial) {
2118 var _this = this;
2119 //remember that we sent this request.
2120 this.outstandingRequests.add(serial);
2121 var doNewRequest = function () {
2122 _this.outstandingRequests.delete(serial);
2123 _this.newRequest_();
2124 };
2125 // If this request doesn't return on its own accord (by the server sending us some data), we'll
2126 // create a new one after the KEEPALIVE interval to make sure we always keep a fresh request open.
2127 var keepaliveTimeout = setTimeout(doNewRequest, Math.floor(KEEPALIVE_REQUEST_INTERVAL));
2128 var readyStateCB = function () {
2129 // Request completed. Cancel the keepalive.
2130 clearTimeout(keepaliveTimeout);
2131 // Trigger a new request so we can continue receiving data.
2132 doNewRequest();
2133 };
2134 this.addTag(url, readyStateCB);
2135 };
2136 /**
2137 * Add an arbitrary script tag to the iframe.
2138 * @param url - The URL for the script tag source.
2139 * @param loadCB - A callback to be triggered once the script has loaded.
2140 */
2141 FirebaseIFrameScriptHolder.prototype.addTag = function (url, loadCB) {
2142 var _this = this;
2143 if (util.isNodeSdk()) {
2144 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2145 this.doNodeLongPoll(url, loadCB);
2146 }
2147 else {
2148 setTimeout(function () {
2149 try {
2150 // if we're already closed, don't add this poll
2151 if (!_this.sendNewPolls) {
2152 return;
2153 }
2154 var newScript_1 = _this.myIFrame.doc.createElement('script');
2155 newScript_1.type = 'text/javascript';
2156 newScript_1.async = true;
2157 newScript_1.src = url;
2158 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2159 newScript_1.onload = newScript_1.onreadystatechange =
2160 function () {
2161 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2162 var rstate = newScript_1.readyState;
2163 if (!rstate || rstate === 'loaded' || rstate === 'complete') {
2164 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2165 newScript_1.onload = newScript_1.onreadystatechange = null;
2166 if (newScript_1.parentNode) {
2167 newScript_1.parentNode.removeChild(newScript_1);
2168 }
2169 loadCB();
2170 }
2171 };
2172 newScript_1.onerror = function () {
2173 log('Long-poll script failed to load: ' + url);
2174 _this.sendNewPolls = false;
2175 _this.close();
2176 };
2177 _this.myIFrame.doc.body.appendChild(newScript_1);
2178 }
2179 catch (e) {
2180 // TODO: we should make this error visible somehow
2181 }
2182 }, Math.floor(1));
2183 }
2184 };
2185 return FirebaseIFrameScriptHolder;
2186}());
2187
2188/**
2189 * @license
2190 * Copyright 2017 Google LLC
2191 *
2192 * Licensed under the Apache License, Version 2.0 (the "License");
2193 * you may not use this file except in compliance with the License.
2194 * You may obtain a copy of the License at
2195 *
2196 * http://www.apache.org/licenses/LICENSE-2.0
2197 *
2198 * Unless required by applicable law or agreed to in writing, software
2199 * distributed under the License is distributed on an "AS IS" BASIS,
2200 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2201 * See the License for the specific language governing permissions and
2202 * limitations under the License.
2203 */
2204/**
2205 * Currently simplistic, this class manages what transport a Connection should use at various stages of its
2206 * lifecycle.
2207 *
2208 * It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
2209 * they are available.
2210 */
2211var TransportManager = /** @class */ (function () {
2212 /**
2213 * @param repoInfo - Metadata around the namespace we're connecting to
2214 */
2215 function TransportManager(repoInfo) {
2216 this.initTransports_(repoInfo);
2217 }
2218 Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
2219 get: function () {
2220 return [BrowserPollConnection, WebSocketConnection];
2221 },
2222 enumerable: false,
2223 configurable: true
2224 });
2225 TransportManager.prototype.initTransports_ = function (repoInfo) {
2226 var e_1, _a;
2227 var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
2228 var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
2229 if (repoInfo.webSocketOnly) {
2230 if (!isWebSocketsAvailable) {
2231 warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
2232 }
2233 isSkipPollConnection = true;
2234 }
2235 if (isSkipPollConnection) {
2236 this.transports_ = [WebSocketConnection];
2237 }
2238 else {
2239 var transports = (this.transports_ = []);
2240 try {
2241 for (var _b = tslib.__values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
2242 var transport = _c.value;
2243 if (transport && transport['isAvailable']()) {
2244 transports.push(transport);
2245 }
2246 }
2247 }
2248 catch (e_1_1) { e_1 = { error: e_1_1 }; }
2249 finally {
2250 try {
2251 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
2252 }
2253 finally { if (e_1) throw e_1.error; }
2254 }
2255 }
2256 };
2257 /**
2258 * @returns The constructor for the initial transport to use
2259 */
2260 TransportManager.prototype.initialTransport = function () {
2261 if (this.transports_.length > 0) {
2262 return this.transports_[0];
2263 }
2264 else {
2265 throw new Error('No transports available');
2266 }
2267 };
2268 /**
2269 * @returns The constructor for the next transport, or null
2270 */
2271 TransportManager.prototype.upgradeTransport = function () {
2272 if (this.transports_.length > 1) {
2273 return this.transports_[1];
2274 }
2275 else {
2276 return null;
2277 }
2278 };
2279 return TransportManager;
2280}());
2281
2282/**
2283 * @license
2284 * Copyright 2017 Google LLC
2285 *
2286 * Licensed under the Apache License, Version 2.0 (the "License");
2287 * you may not use this file except in compliance with the License.
2288 * You may obtain a copy of the License at
2289 *
2290 * http://www.apache.org/licenses/LICENSE-2.0
2291 *
2292 * Unless required by applicable law or agreed to in writing, software
2293 * distributed under the License is distributed on an "AS IS" BASIS,
2294 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2295 * See the License for the specific language governing permissions and
2296 * limitations under the License.
2297 */
2298// Abort upgrade attempt if it takes longer than 60s.
2299var UPGRADE_TIMEOUT = 60000;
2300// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
2301// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
2302var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
2303// 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)
2304// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
2305// but we've sent/received enough bytes, we don't cancel the connection.
2306var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
2307var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
2308var MESSAGE_TYPE = 't';
2309var MESSAGE_DATA = 'd';
2310var CONTROL_SHUTDOWN = 's';
2311var CONTROL_RESET = 'r';
2312var CONTROL_ERROR = 'e';
2313var CONTROL_PONG = 'o';
2314var SWITCH_ACK = 'a';
2315var END_TRANSMISSION = 'n';
2316var PING = 'p';
2317var SERVER_HELLO = 'h';
2318/**
2319 * Creates a new real-time connection to the server using whichever method works
2320 * best in the current browser.
2321 */
2322var Connection = /** @class */ (function () {
2323 /**
2324 * @param id - an id for this connection
2325 * @param repoInfo_ - the info for the endpoint to connect to
2326 * @param applicationId_ - the Firebase App ID for this project
2327 * @param appCheckToken_ - The App Check Token for this device.
2328 * @param authToken_ - The auth token for this session.
2329 * @param onMessage_ - the callback to be triggered when a server-push message arrives
2330 * @param onReady_ - the callback to be triggered when this connection is ready to send messages.
2331 * @param onDisconnect_ - the callback to be triggered when a connection was lost
2332 * @param onKill_ - the callback to be triggered when this connection has permanently shut down.
2333 * @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
2334 */
2335 function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
2336 this.id = id;
2337 this.repoInfo_ = repoInfo_;
2338 this.applicationId_ = applicationId_;
2339 this.appCheckToken_ = appCheckToken_;
2340 this.authToken_ = authToken_;
2341 this.onMessage_ = onMessage_;
2342 this.onReady_ = onReady_;
2343 this.onDisconnect_ = onDisconnect_;
2344 this.onKill_ = onKill_;
2345 this.lastSessionId = lastSessionId;
2346 this.connectionCount = 0;
2347 this.pendingDataMessages = [];
2348 this.state_ = 0 /* CONNECTING */;
2349 this.log_ = logWrapper('c:' + this.id + ':');
2350 this.transportManager_ = new TransportManager(repoInfo_);
2351 this.log_('Connection created');
2352 this.start_();
2353 }
2354 /**
2355 * Starts a connection attempt
2356 */
2357 Connection.prototype.start_ = function () {
2358 var _this = this;
2359 var conn = this.transportManager_.initialTransport();
2360 this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, null, this.lastSessionId);
2361 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2362 // can consider the transport healthy.
2363 this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
2364 var onMessageReceived = this.connReceiver_(this.conn_);
2365 var onConnectionLost = this.disconnReceiver_(this.conn_);
2366 this.tx_ = this.conn_;
2367 this.rx_ = this.conn_;
2368 this.secondaryConn_ = null;
2369 this.isHealthy_ = false;
2370 /*
2371 * Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
2372 * This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
2373 * Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
2374 * still have the context of your originating frame.
2375 */
2376 setTimeout(function () {
2377 // this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
2378 _this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
2379 }, Math.floor(0));
2380 var healthyTimeoutMS = conn['healthyTimeout'] || 0;
2381 if (healthyTimeoutMS > 0) {
2382 this.healthyTimeout_ = setTimeoutNonBlocking(function () {
2383 _this.healthyTimeout_ = null;
2384 if (!_this.isHealthy_) {
2385 if (_this.conn_ &&
2386 _this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
2387 _this.log_('Connection exceeded healthy timeout but has received ' +
2388 _this.conn_.bytesReceived +
2389 ' bytes. Marking connection healthy.');
2390 _this.isHealthy_ = true;
2391 _this.conn_.markConnectionHealthy();
2392 }
2393 else if (_this.conn_ &&
2394 _this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
2395 _this.log_('Connection exceeded healthy timeout but has sent ' +
2396 _this.conn_.bytesSent +
2397 ' bytes. Leaving connection alive.');
2398 // NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
2399 // the server.
2400 }
2401 else {
2402 _this.log_('Closing unhealthy connection after timeout.');
2403 _this.close();
2404 }
2405 }
2406 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2407 }, Math.floor(healthyTimeoutMS));
2408 }
2409 };
2410 Connection.prototype.nextTransportId_ = function () {
2411 return 'c:' + this.id + ':' + this.connectionCount++;
2412 };
2413 Connection.prototype.disconnReceiver_ = function (conn) {
2414 var _this = this;
2415 return function (everConnected) {
2416 if (conn === _this.conn_) {
2417 _this.onConnectionLost_(everConnected);
2418 }
2419 else if (conn === _this.secondaryConn_) {
2420 _this.log_('Secondary connection lost.');
2421 _this.onSecondaryConnectionLost_();
2422 }
2423 else {
2424 _this.log_('closing an old connection');
2425 }
2426 };
2427 };
2428 Connection.prototype.connReceiver_ = function (conn) {
2429 var _this = this;
2430 return function (message) {
2431 if (_this.state_ !== 2 /* DISCONNECTED */) {
2432 if (conn === _this.rx_) {
2433 _this.onPrimaryMessageReceived_(message);
2434 }
2435 else if (conn === _this.secondaryConn_) {
2436 _this.onSecondaryMessageReceived_(message);
2437 }
2438 else {
2439 _this.log_('message on old connection');
2440 }
2441 }
2442 };
2443 };
2444 /**
2445 * @param dataMsg - An arbitrary data message to be sent to the server
2446 */
2447 Connection.prototype.sendRequest = function (dataMsg) {
2448 // wrap in a data message envelope and send it on
2449 var msg = { t: 'd', d: dataMsg };
2450 this.sendData_(msg);
2451 };
2452 Connection.prototype.tryCleanupConnection = function () {
2453 if (this.tx_ === this.secondaryConn_ && this.rx_ === this.secondaryConn_) {
2454 this.log_('cleaning up and promoting a connection: ' + this.secondaryConn_.connId);
2455 this.conn_ = this.secondaryConn_;
2456 this.secondaryConn_ = null;
2457 // the server will shutdown the old connection
2458 }
2459 };
2460 Connection.prototype.onSecondaryControl_ = function (controlData) {
2461 if (MESSAGE_TYPE in controlData) {
2462 var cmd = controlData[MESSAGE_TYPE];
2463 if (cmd === SWITCH_ACK) {
2464 this.upgradeIfSecondaryHealthy_();
2465 }
2466 else if (cmd === CONTROL_RESET) {
2467 // Most likely the session wasn't valid. Abandon the switch attempt
2468 this.log_('Got a reset on secondary, closing it');
2469 this.secondaryConn_.close();
2470 // If we were already using this connection for something, than we need to fully close
2471 if (this.tx_ === this.secondaryConn_ ||
2472 this.rx_ === this.secondaryConn_) {
2473 this.close();
2474 }
2475 }
2476 else if (cmd === CONTROL_PONG) {
2477 this.log_('got pong on secondary.');
2478 this.secondaryResponsesRequired_--;
2479 this.upgradeIfSecondaryHealthy_();
2480 }
2481 }
2482 };
2483 Connection.prototype.onSecondaryMessageReceived_ = function (parsedData) {
2484 var layer = requireKey('t', parsedData);
2485 var data = requireKey('d', parsedData);
2486 if (layer === 'c') {
2487 this.onSecondaryControl_(data);
2488 }
2489 else if (layer === 'd') {
2490 // got a data message, but we're still second connection. Need to buffer it up
2491 this.pendingDataMessages.push(data);
2492 }
2493 else {
2494 throw new Error('Unknown protocol layer: ' + layer);
2495 }
2496 };
2497 Connection.prototype.upgradeIfSecondaryHealthy_ = function () {
2498 if (this.secondaryResponsesRequired_ <= 0) {
2499 this.log_('Secondary connection is healthy.');
2500 this.isHealthy_ = true;
2501 this.secondaryConn_.markConnectionHealthy();
2502 this.proceedWithUpgrade_();
2503 }
2504 else {
2505 // Send a ping to make sure the connection is healthy.
2506 this.log_('sending ping on secondary.');
2507 this.secondaryConn_.send({ t: 'c', d: { t: PING, d: {} } });
2508 }
2509 };
2510 Connection.prototype.proceedWithUpgrade_ = function () {
2511 // tell this connection to consider itself open
2512 this.secondaryConn_.start();
2513 // send ack
2514 this.log_('sending client ack on secondary');
2515 this.secondaryConn_.send({ t: 'c', d: { t: SWITCH_ACK, d: {} } });
2516 // send end packet on primary transport, switch to sending on this one
2517 // can receive on this one, buffer responses until end received on primary transport
2518 this.log_('Ending transmission on primary');
2519 this.conn_.send({ t: 'c', d: { t: END_TRANSMISSION, d: {} } });
2520 this.tx_ = this.secondaryConn_;
2521 this.tryCleanupConnection();
2522 };
2523 Connection.prototype.onPrimaryMessageReceived_ = function (parsedData) {
2524 // Must refer to parsedData properties in quotes, so closure doesn't touch them.
2525 var layer = requireKey('t', parsedData);
2526 var data = requireKey('d', parsedData);
2527 if (layer === 'c') {
2528 this.onControl_(data);
2529 }
2530 else if (layer === 'd') {
2531 this.onDataMessage_(data);
2532 }
2533 };
2534 Connection.prototype.onDataMessage_ = function (message) {
2535 this.onPrimaryResponse_();
2536 // We don't do anything with data messages, just kick them up a level
2537 this.onMessage_(message);
2538 };
2539 Connection.prototype.onPrimaryResponse_ = function () {
2540 if (!this.isHealthy_) {
2541 this.primaryResponsesRequired_--;
2542 if (this.primaryResponsesRequired_ <= 0) {
2543 this.log_('Primary connection is healthy.');
2544 this.isHealthy_ = true;
2545 this.conn_.markConnectionHealthy();
2546 }
2547 }
2548 };
2549 Connection.prototype.onControl_ = function (controlData) {
2550 var cmd = requireKey(MESSAGE_TYPE, controlData);
2551 if (MESSAGE_DATA in controlData) {
2552 var payload = controlData[MESSAGE_DATA];
2553 if (cmd === SERVER_HELLO) {
2554 this.onHandshake_(payload);
2555 }
2556 else if (cmd === END_TRANSMISSION) {
2557 this.log_('recvd end transmission on primary');
2558 this.rx_ = this.secondaryConn_;
2559 for (var i = 0; i < this.pendingDataMessages.length; ++i) {
2560 this.onDataMessage_(this.pendingDataMessages[i]);
2561 }
2562 this.pendingDataMessages = [];
2563 this.tryCleanupConnection();
2564 }
2565 else if (cmd === CONTROL_SHUTDOWN) {
2566 // This was previously the 'onKill' callback passed to the lower-level connection
2567 // payload in this case is the reason for the shutdown. Generally a human-readable error
2568 this.onConnectionShutdown_(payload);
2569 }
2570 else if (cmd === CONTROL_RESET) {
2571 // payload in this case is the host we should contact
2572 this.onReset_(payload);
2573 }
2574 else if (cmd === CONTROL_ERROR) {
2575 error('Server Error: ' + payload);
2576 }
2577 else if (cmd === CONTROL_PONG) {
2578 this.log_('got pong on primary.');
2579 this.onPrimaryResponse_();
2580 this.sendPingOnPrimaryIfNecessary_();
2581 }
2582 else {
2583 error('Unknown control packet command: ' + cmd);
2584 }
2585 }
2586 };
2587 /**
2588 * @param handshake - The handshake data returned from the server
2589 */
2590 Connection.prototype.onHandshake_ = function (handshake) {
2591 var timestamp = handshake.ts;
2592 var version = handshake.v;
2593 var host = handshake.h;
2594 this.sessionId = handshake.s;
2595 this.repoInfo_.host = host;
2596 // if we've already closed the connection, then don't bother trying to progress further
2597 if (this.state_ === 0 /* CONNECTING */) {
2598 this.conn_.start();
2599 this.onConnectionEstablished_(this.conn_, timestamp);
2600 if (PROTOCOL_VERSION !== version) {
2601 warn('Protocol version mismatch detected');
2602 }
2603 // TODO: do we want to upgrade? when? maybe a delay?
2604 this.tryStartUpgrade_();
2605 }
2606 };
2607 Connection.prototype.tryStartUpgrade_ = function () {
2608 var conn = this.transportManager_.upgradeTransport();
2609 if (conn) {
2610 this.startUpgrade_(conn);
2611 }
2612 };
2613 Connection.prototype.startUpgrade_ = function (conn) {
2614 var _this = this;
2615 this.secondaryConn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, this.sessionId);
2616 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2617 // can consider the transport healthy.
2618 this.secondaryResponsesRequired_ =
2619 conn['responsesRequiredToBeHealthy'] || 0;
2620 var onMessage = this.connReceiver_(this.secondaryConn_);
2621 var onDisconnect = this.disconnReceiver_(this.secondaryConn_);
2622 this.secondaryConn_.open(onMessage, onDisconnect);
2623 // If we haven't successfully upgraded after UPGRADE_TIMEOUT, give up and kill the secondary.
2624 setTimeoutNonBlocking(function () {
2625 if (_this.secondaryConn_) {
2626 _this.log_('Timed out trying to upgrade.');
2627 _this.secondaryConn_.close();
2628 }
2629 }, Math.floor(UPGRADE_TIMEOUT));
2630 };
2631 Connection.prototype.onReset_ = function (host) {
2632 this.log_('Reset packet received. New host: ' + host);
2633 this.repoInfo_.host = host;
2634 // TODO: if we're already "connected", we need to trigger a disconnect at the next layer up.
2635 // We don't currently support resets after the connection has already been established
2636 if (this.state_ === 1 /* CONNECTED */) {
2637 this.close();
2638 }
2639 else {
2640 // Close whatever connections we have open and start again.
2641 this.closeConnections_();
2642 this.start_();
2643 }
2644 };
2645 Connection.prototype.onConnectionEstablished_ = function (conn, timestamp) {
2646 var _this = this;
2647 this.log_('Realtime connection established.');
2648 this.conn_ = conn;
2649 this.state_ = 1 /* CONNECTED */;
2650 if (this.onReady_) {
2651 this.onReady_(timestamp, this.sessionId);
2652 this.onReady_ = null;
2653 }
2654 // If after 5 seconds we haven't sent enough requests to the server to get the connection healthy,
2655 // send some pings.
2656 if (this.primaryResponsesRequired_ === 0) {
2657 this.log_('Primary connection is healthy.');
2658 this.isHealthy_ = true;
2659 }
2660 else {
2661 setTimeoutNonBlocking(function () {
2662 _this.sendPingOnPrimaryIfNecessary_();
2663 }, Math.floor(DELAY_BEFORE_SENDING_EXTRA_REQUESTS));
2664 }
2665 };
2666 Connection.prototype.sendPingOnPrimaryIfNecessary_ = function () {
2667 // If the connection isn't considered healthy yet, we'll send a noop ping packet request.
2668 if (!this.isHealthy_ && this.state_ === 1 /* CONNECTED */) {
2669 this.log_('sending ping on primary.');
2670 this.sendData_({ t: 'c', d: { t: PING, d: {} } });
2671 }
2672 };
2673 Connection.prototype.onSecondaryConnectionLost_ = function () {
2674 var conn = this.secondaryConn_;
2675 this.secondaryConn_ = null;
2676 if (this.tx_ === conn || this.rx_ === conn) {
2677 // we are relying on this connection already in some capacity. Therefore, a failure is real
2678 this.close();
2679 }
2680 };
2681 /**
2682 * @param everConnected - Whether or not the connection ever reached a server. Used to determine if
2683 * we should flush the host cache
2684 */
2685 Connection.prototype.onConnectionLost_ = function (everConnected) {
2686 this.conn_ = null;
2687 // NOTE: IF you're seeing a Firefox error for this line, I think it might be because it's getting
2688 // called on window close and RealtimeState.CONNECTING is no longer defined. Just a guess.
2689 if (!everConnected && this.state_ === 0 /* CONNECTING */) {
2690 this.log_('Realtime connection failed.');
2691 // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
2692 if (this.repoInfo_.isCacheableHost()) {
2693 PersistentStorage.remove('host:' + this.repoInfo_.host);
2694 // reset the internal host to what we would show the user, i.e. <ns>.firebaseio.com
2695 this.repoInfo_.internalHost = this.repoInfo_.host;
2696 }
2697 }
2698 else if (this.state_ === 1 /* CONNECTED */) {
2699 this.log_('Realtime connection lost.');
2700 }
2701 this.close();
2702 };
2703 Connection.prototype.onConnectionShutdown_ = function (reason) {
2704 this.log_('Connection shutdown command received. Shutting down...');
2705 if (this.onKill_) {
2706 this.onKill_(reason);
2707 this.onKill_ = null;
2708 }
2709 // We intentionally don't want to fire onDisconnect (kill is a different case),
2710 // so clear the callback.
2711 this.onDisconnect_ = null;
2712 this.close();
2713 };
2714 Connection.prototype.sendData_ = function (data) {
2715 if (this.state_ !== 1 /* CONNECTED */) {
2716 throw 'Connection is not connected';
2717 }
2718 else {
2719 this.tx_.send(data);
2720 }
2721 };
2722 /**
2723 * Cleans up this connection, calling the appropriate callbacks
2724 */
2725 Connection.prototype.close = function () {
2726 if (this.state_ !== 2 /* DISCONNECTED */) {
2727 this.log_('Closing realtime connection.');
2728 this.state_ = 2 /* DISCONNECTED */;
2729 this.closeConnections_();
2730 if (this.onDisconnect_) {
2731 this.onDisconnect_();
2732 this.onDisconnect_ = null;
2733 }
2734 }
2735 };
2736 Connection.prototype.closeConnections_ = function () {
2737 this.log_('Shutting down all connections');
2738 if (this.conn_) {
2739 this.conn_.close();
2740 this.conn_ = null;
2741 }
2742 if (this.secondaryConn_) {
2743 this.secondaryConn_.close();
2744 this.secondaryConn_ = null;
2745 }
2746 if (this.healthyTimeout_) {
2747 clearTimeout(this.healthyTimeout_);
2748 this.healthyTimeout_ = null;
2749 }
2750 };
2751 return Connection;
2752}());
2753
2754/**
2755 * @license
2756 * Copyright 2017 Google LLC
2757 *
2758 * Licensed under the Apache License, Version 2.0 (the "License");
2759 * you may not use this file except in compliance with the License.
2760 * You may obtain a copy of the License at
2761 *
2762 * http://www.apache.org/licenses/LICENSE-2.0
2763 *
2764 * Unless required by applicable law or agreed to in writing, software
2765 * distributed under the License is distributed on an "AS IS" BASIS,
2766 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2767 * See the License for the specific language governing permissions and
2768 * limitations under the License.
2769 */
2770/**
2771 * Interface defining the set of actions that can be performed against the Firebase server
2772 * (basically corresponds to our wire protocol).
2773 *
2774 * @interface
2775 */
2776var ServerActions = /** @class */ (function () {
2777 function ServerActions() {
2778 }
2779 ServerActions.prototype.put = function (pathString, data, onComplete, hash) { };
2780 ServerActions.prototype.merge = function (pathString, data, onComplete, hash) { };
2781 /**
2782 * Refreshes the auth token for the current connection.
2783 * @param token - The authentication token
2784 */
2785 ServerActions.prototype.refreshAuthToken = function (token) { };
2786 /**
2787 * Refreshes the app check token for the current connection.
2788 * @param token The app check token
2789 */
2790 ServerActions.prototype.refreshAppCheckToken = function (token) { };
2791 ServerActions.prototype.onDisconnectPut = function (pathString, data, onComplete) { };
2792 ServerActions.prototype.onDisconnectMerge = function (pathString, data, onComplete) { };
2793 ServerActions.prototype.onDisconnectCancel = function (pathString, onComplete) { };
2794 ServerActions.prototype.reportStats = function (stats) { };
2795 return ServerActions;
2796}());
2797
2798/**
2799 * @license
2800 * Copyright 2017 Google LLC
2801 *
2802 * Licensed under the Apache License, Version 2.0 (the "License");
2803 * you may not use this file except in compliance with the License.
2804 * You may obtain a copy of the License at
2805 *
2806 * http://www.apache.org/licenses/LICENSE-2.0
2807 *
2808 * Unless required by applicable law or agreed to in writing, software
2809 * distributed under the License is distributed on an "AS IS" BASIS,
2810 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2811 * See the License for the specific language governing permissions and
2812 * limitations under the License.
2813 */
2814/**
2815 * Base class to be used if you want to emit events. Call the constructor with
2816 * the set of allowed event names.
2817 */
2818var EventEmitter = /** @class */ (function () {
2819 function EventEmitter(allowedEvents_) {
2820 this.allowedEvents_ = allowedEvents_;
2821 this.listeners_ = {};
2822 util.assert(Array.isArray(allowedEvents_) && allowedEvents_.length > 0, 'Requires a non-empty array');
2823 }
2824 /**
2825 * To be called by derived classes to trigger events.
2826 */
2827 EventEmitter.prototype.trigger = function (eventType) {
2828 var varArgs = [];
2829 for (var _i = 1; _i < arguments.length; _i++) {
2830 varArgs[_i - 1] = arguments[_i];
2831 }
2832 if (Array.isArray(this.listeners_[eventType])) {
2833 // Clone the list, since callbacks could add/remove listeners.
2834 var listeners = tslib.__spreadArray([], tslib.__read(this.listeners_[eventType]));
2835 for (var i = 0; i < listeners.length; i++) {
2836 listeners[i].callback.apply(listeners[i].context, varArgs);
2837 }
2838 }
2839 };
2840 EventEmitter.prototype.on = function (eventType, callback, context) {
2841 this.validateEventType_(eventType);
2842 this.listeners_[eventType] = this.listeners_[eventType] || [];
2843 this.listeners_[eventType].push({ callback: callback, context: context });
2844 var eventData = this.getInitialEvent(eventType);
2845 if (eventData) {
2846 callback.apply(context, eventData);
2847 }
2848 };
2849 EventEmitter.prototype.off = function (eventType, callback, context) {
2850 this.validateEventType_(eventType);
2851 var listeners = this.listeners_[eventType] || [];
2852 for (var i = 0; i < listeners.length; i++) {
2853 if (listeners[i].callback === callback &&
2854 (!context || context === listeners[i].context)) {
2855 listeners.splice(i, 1);
2856 return;
2857 }
2858 }
2859 };
2860 EventEmitter.prototype.validateEventType_ = function (eventType) {
2861 util.assert(this.allowedEvents_.find(function (et) {
2862 return et === eventType;
2863 }), 'Unknown event: ' + eventType);
2864 };
2865 return EventEmitter;
2866}());
2867
2868/**
2869 * @license
2870 * Copyright 2017 Google LLC
2871 *
2872 * Licensed under the Apache License, Version 2.0 (the "License");
2873 * you may not use this file except in compliance with the License.
2874 * You may obtain a copy of the License at
2875 *
2876 * http://www.apache.org/licenses/LICENSE-2.0
2877 *
2878 * Unless required by applicable law or agreed to in writing, software
2879 * distributed under the License is distributed on an "AS IS" BASIS,
2880 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2881 * See the License for the specific language governing permissions and
2882 * limitations under the License.
2883 */
2884/**
2885 * Monitors online state (as reported by window.online/offline events).
2886 *
2887 * The expectation is that this could have many false positives (thinks we are online
2888 * when we're not), but no false negatives. So we can safely use it to determine when
2889 * we definitely cannot reach the internet.
2890 */
2891var OnlineMonitor = /** @class */ (function (_super) {
2892 tslib.__extends(OnlineMonitor, _super);
2893 function OnlineMonitor() {
2894 var _this = _super.call(this, ['online']) || this;
2895 _this.online_ = true;
2896 // We've had repeated complaints that Cordova apps can get stuck "offline", e.g.
2897 // https://forum.ionicframework.com/t/firebase-connection-is-lost-and-never-come-back/43810
2898 // It would seem that the 'online' event does not always fire consistently. So we disable it
2899 // for Cordova.
2900 if (typeof window !== 'undefined' &&
2901 typeof window.addEventListener !== 'undefined' &&
2902 !util.isMobileCordova()) {
2903 window.addEventListener('online', function () {
2904 if (!_this.online_) {
2905 _this.online_ = true;
2906 _this.trigger('online', true);
2907 }
2908 }, false);
2909 window.addEventListener('offline', function () {
2910 if (_this.online_) {
2911 _this.online_ = false;
2912 _this.trigger('online', false);
2913 }
2914 }, false);
2915 }
2916 return _this;
2917 }
2918 OnlineMonitor.getInstance = function () {
2919 return new OnlineMonitor();
2920 };
2921 OnlineMonitor.prototype.getInitialEvent = function (eventType) {
2922 util.assert(eventType === 'online', 'Unknown event type: ' + eventType);
2923 return [this.online_];
2924 };
2925 OnlineMonitor.prototype.currentlyOnline = function () {
2926 return this.online_;
2927 };
2928 return OnlineMonitor;
2929}(EventEmitter));
2930
2931/**
2932 * @license
2933 * Copyright 2017 Google LLC
2934 *
2935 * Licensed under the Apache License, Version 2.0 (the "License");
2936 * you may not use this file except in compliance with the License.
2937 * You may obtain a copy of the License at
2938 *
2939 * http://www.apache.org/licenses/LICENSE-2.0
2940 *
2941 * Unless required by applicable law or agreed to in writing, software
2942 * distributed under the License is distributed on an "AS IS" BASIS,
2943 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2944 * See the License for the specific language governing permissions and
2945 * limitations under the License.
2946 */
2947/** Maximum key depth. */
2948var MAX_PATH_DEPTH = 32;
2949/** Maximum number of (UTF8) bytes in a Firebase path. */
2950var MAX_PATH_LENGTH_BYTES = 768;
2951/**
2952 * An immutable object representing a parsed path. It's immutable so that you
2953 * can pass them around to other functions without worrying about them changing
2954 * it.
2955 */
2956var Path = /** @class */ (function () {
2957 /**
2958 * @param pathOrString - Path string to parse, or another path, or the raw
2959 * tokens array
2960 */
2961 function Path(pathOrString, pieceNum) {
2962 if (pieceNum === void 0) {
2963 this.pieces_ = pathOrString.split('/');
2964 // Remove empty pieces.
2965 var copyTo = 0;
2966 for (var i = 0; i < this.pieces_.length; i++) {
2967 if (this.pieces_[i].length > 0) {
2968 this.pieces_[copyTo] = this.pieces_[i];
2969 copyTo++;
2970 }
2971 }
2972 this.pieces_.length = copyTo;
2973 this.pieceNum_ = 0;
2974 }
2975 else {
2976 this.pieces_ = pathOrString;
2977 this.pieceNum_ = pieceNum;
2978 }
2979 }
2980 Path.prototype.toString = function () {
2981 var pathString = '';
2982 for (var i = this.pieceNum_; i < this.pieces_.length; i++) {
2983 if (this.pieces_[i] !== '') {
2984 pathString += '/' + this.pieces_[i];
2985 }
2986 }
2987 return pathString || '/';
2988 };
2989 return Path;
2990}());
2991function newEmptyPath() {
2992 return new Path('');
2993}
2994function pathGetFront(path) {
2995 if (path.pieceNum_ >= path.pieces_.length) {
2996 return null;
2997 }
2998 return path.pieces_[path.pieceNum_];
2999}
3000/**
3001 * @returns The number of segments in this path
3002 */
3003function pathGetLength(path) {
3004 return path.pieces_.length - path.pieceNum_;
3005}
3006function pathPopFront(path) {
3007 var pieceNum = path.pieceNum_;
3008 if (pieceNum < path.pieces_.length) {
3009 pieceNum++;
3010 }
3011 return new Path(path.pieces_, pieceNum);
3012}
3013function pathGetBack(path) {
3014 if (path.pieceNum_ < path.pieces_.length) {
3015 return path.pieces_[path.pieces_.length - 1];
3016 }
3017 return null;
3018}
3019function pathToUrlEncodedString(path) {
3020 var pathString = '';
3021 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3022 if (path.pieces_[i] !== '') {
3023 pathString += '/' + encodeURIComponent(String(path.pieces_[i]));
3024 }
3025 }
3026 return pathString || '/';
3027}
3028/**
3029 * Shallow copy of the parts of the path.
3030 *
3031 */
3032function pathSlice(path, begin) {
3033 if (begin === void 0) { begin = 0; }
3034 return path.pieces_.slice(path.pieceNum_ + begin);
3035}
3036function pathParent(path) {
3037 if (path.pieceNum_ >= path.pieces_.length) {
3038 return null;
3039 }
3040 var pieces = [];
3041 for (var i = path.pieceNum_; i < path.pieces_.length - 1; i++) {
3042 pieces.push(path.pieces_[i]);
3043 }
3044 return new Path(pieces, 0);
3045}
3046function pathChild(path, childPathObj) {
3047 var pieces = [];
3048 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3049 pieces.push(path.pieces_[i]);
3050 }
3051 if (childPathObj instanceof Path) {
3052 for (var i = childPathObj.pieceNum_; i < childPathObj.pieces_.length; i++) {
3053 pieces.push(childPathObj.pieces_[i]);
3054 }
3055 }
3056 else {
3057 var childPieces = childPathObj.split('/');
3058 for (var i = 0; i < childPieces.length; i++) {
3059 if (childPieces[i].length > 0) {
3060 pieces.push(childPieces[i]);
3061 }
3062 }
3063 }
3064 return new Path(pieces, 0);
3065}
3066/**
3067 * @returns True if there are no segments in this path
3068 */
3069function pathIsEmpty(path) {
3070 return path.pieceNum_ >= path.pieces_.length;
3071}
3072/**
3073 * @returns The path from outerPath to innerPath
3074 */
3075function newRelativePath(outerPath, innerPath) {
3076 var outer = pathGetFront(outerPath), inner = pathGetFront(innerPath);
3077 if (outer === null) {
3078 return innerPath;
3079 }
3080 else if (outer === inner) {
3081 return newRelativePath(pathPopFront(outerPath), pathPopFront(innerPath));
3082 }
3083 else {
3084 throw new Error('INTERNAL ERROR: innerPath (' +
3085 innerPath +
3086 ') is not within ' +
3087 'outerPath (' +
3088 outerPath +
3089 ')');
3090 }
3091}
3092/**
3093 * @returns -1, 0, 1 if left is less, equal, or greater than the right.
3094 */
3095function pathCompare(left, right) {
3096 var leftKeys = pathSlice(left, 0);
3097 var rightKeys = pathSlice(right, 0);
3098 for (var i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
3099 var cmp = nameCompare(leftKeys[i], rightKeys[i]);
3100 if (cmp !== 0) {
3101 return cmp;
3102 }
3103 }
3104 if (leftKeys.length === rightKeys.length) {
3105 return 0;
3106 }
3107 return leftKeys.length < rightKeys.length ? -1 : 1;
3108}
3109/**
3110 * @returns true if paths are the same.
3111 */
3112function pathEquals(path, other) {
3113 if (pathGetLength(path) !== pathGetLength(other)) {
3114 return false;
3115 }
3116 for (var i = path.pieceNum_, j = other.pieceNum_; i <= path.pieces_.length; i++, j++) {
3117 if (path.pieces_[i] !== other.pieces_[j]) {
3118 return false;
3119 }
3120 }
3121 return true;
3122}
3123/**
3124 * @returns True if this path is a parent (or the same as) other
3125 */
3126function pathContains(path, other) {
3127 var i = path.pieceNum_;
3128 var j = other.pieceNum_;
3129 if (pathGetLength(path) > pathGetLength(other)) {
3130 return false;
3131 }
3132 while (i < path.pieces_.length) {
3133 if (path.pieces_[i] !== other.pieces_[j]) {
3134 return false;
3135 }
3136 ++i;
3137 ++j;
3138 }
3139 return true;
3140}
3141/**
3142 * Dynamic (mutable) path used to count path lengths.
3143 *
3144 * This class is used to efficiently check paths for valid
3145 * length (in UTF8 bytes) and depth (used in path validation).
3146 *
3147 * Throws Error exception if path is ever invalid.
3148 *
3149 * The definition of a path always begins with '/'.
3150 */
3151var ValidationPath = /** @class */ (function () {
3152 /**
3153 * @param path - Initial Path.
3154 * @param errorPrefix_ - Prefix for any error messages.
3155 */
3156 function ValidationPath(path, errorPrefix_) {
3157 this.errorPrefix_ = errorPrefix_;
3158 this.parts_ = pathSlice(path, 0);
3159 /** Initialize to number of '/' chars needed in path. */
3160 this.byteLength_ = Math.max(1, this.parts_.length);
3161 for (var i = 0; i < this.parts_.length; i++) {
3162 this.byteLength_ += util.stringLength(this.parts_[i]);
3163 }
3164 validationPathCheckValid(this);
3165 }
3166 return ValidationPath;
3167}());
3168function validationPathPush(validationPath, child) {
3169 // Count the needed '/'
3170 if (validationPath.parts_.length > 0) {
3171 validationPath.byteLength_ += 1;
3172 }
3173 validationPath.parts_.push(child);
3174 validationPath.byteLength_ += util.stringLength(child);
3175 validationPathCheckValid(validationPath);
3176}
3177function validationPathPop(validationPath) {
3178 var last = validationPath.parts_.pop();
3179 validationPath.byteLength_ -= util.stringLength(last);
3180 // Un-count the previous '/'
3181 if (validationPath.parts_.length > 0) {
3182 validationPath.byteLength_ -= 1;
3183 }
3184}
3185function validationPathCheckValid(validationPath) {
3186 if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
3187 throw new Error(validationPath.errorPrefix_ +
3188 'has a key path longer than ' +
3189 MAX_PATH_LENGTH_BYTES +
3190 ' bytes (' +
3191 validationPath.byteLength_ +
3192 ').');
3193 }
3194 if (validationPath.parts_.length > MAX_PATH_DEPTH) {
3195 throw new Error(validationPath.errorPrefix_ +
3196 'path specified exceeds the maximum depth that can be written (' +
3197 MAX_PATH_DEPTH +
3198 ') or object contains a cycle ' +
3199 validationPathToErrorString(validationPath));
3200 }
3201}
3202/**
3203 * String for use in error messages - uses '.' notation for path.
3204 */
3205function validationPathToErrorString(validationPath) {
3206 if (validationPath.parts_.length === 0) {
3207 return '';
3208 }
3209 return "in property '" + validationPath.parts_.join('.') + "'";
3210}
3211
3212/**
3213 * @license
3214 * Copyright 2017 Google LLC
3215 *
3216 * Licensed under the Apache License, Version 2.0 (the "License");
3217 * you may not use this file except in compliance with the License.
3218 * You may obtain a copy of the License at
3219 *
3220 * http://www.apache.org/licenses/LICENSE-2.0
3221 *
3222 * Unless required by applicable law or agreed to in writing, software
3223 * distributed under the License is distributed on an "AS IS" BASIS,
3224 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3225 * See the License for the specific language governing permissions and
3226 * limitations under the License.
3227 */
3228var VisibilityMonitor = /** @class */ (function (_super) {
3229 tslib.__extends(VisibilityMonitor, _super);
3230 function VisibilityMonitor() {
3231 var _this = _super.call(this, ['visible']) || this;
3232 var hidden;
3233 var visibilityChange;
3234 if (typeof document !== 'undefined' &&
3235 typeof document.addEventListener !== 'undefined') {
3236 if (typeof document['hidden'] !== 'undefined') {
3237 // Opera 12.10 and Firefox 18 and later support
3238 visibilityChange = 'visibilitychange';
3239 hidden = 'hidden';
3240 }
3241 else if (typeof document['mozHidden'] !== 'undefined') {
3242 visibilityChange = 'mozvisibilitychange';
3243 hidden = 'mozHidden';
3244 }
3245 else if (typeof document['msHidden'] !== 'undefined') {
3246 visibilityChange = 'msvisibilitychange';
3247 hidden = 'msHidden';
3248 }
3249 else if (typeof document['webkitHidden'] !== 'undefined') {
3250 visibilityChange = 'webkitvisibilitychange';
3251 hidden = 'webkitHidden';
3252 }
3253 }
3254 // Initially, we always assume we are visible. This ensures that in browsers
3255 // without page visibility support or in cases where we are never visible
3256 // (e.g. chrome extension), we act as if we are visible, i.e. don't delay
3257 // reconnects
3258 _this.visible_ = true;
3259 if (visibilityChange) {
3260 document.addEventListener(visibilityChange, function () {
3261 var visible = !document[hidden];
3262 if (visible !== _this.visible_) {
3263 _this.visible_ = visible;
3264 _this.trigger('visible', visible);
3265 }
3266 }, false);
3267 }
3268 return _this;
3269 }
3270 VisibilityMonitor.getInstance = function () {
3271 return new VisibilityMonitor();
3272 };
3273 VisibilityMonitor.prototype.getInitialEvent = function (eventType) {
3274 util.assert(eventType === 'visible', 'Unknown event type: ' + eventType);
3275 return [this.visible_];
3276 };
3277 return VisibilityMonitor;
3278}(EventEmitter));
3279
3280/**
3281 * @license
3282 * Copyright 2017 Google LLC
3283 *
3284 * Licensed under the Apache License, Version 2.0 (the "License");
3285 * you may not use this file except in compliance with the License.
3286 * You may obtain a copy of the License at
3287 *
3288 * http://www.apache.org/licenses/LICENSE-2.0
3289 *
3290 * Unless required by applicable law or agreed to in writing, software
3291 * distributed under the License is distributed on an "AS IS" BASIS,
3292 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3293 * See the License for the specific language governing permissions and
3294 * limitations under the License.
3295 */
3296var RECONNECT_MIN_DELAY = 1000;
3297var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
3298var GET_CONNECT_TIMEOUT = 3 * 1000;
3299var RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
3300var RECONNECT_DELAY_MULTIPLIER = 1.3;
3301var RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
3302var SERVER_KILL_INTERRUPT_REASON = 'server_kill';
3303// If auth fails repeatedly, we'll assume something is wrong and log a warning / back off.
3304var INVALID_TOKEN_THRESHOLD = 3;
3305/**
3306 * Firebase connection. Abstracts wire protocol and handles reconnecting.
3307 *
3308 * NOTE: All JSON objects sent to the realtime connection must have property names enclosed
3309 * in quotes to make sure the closure compiler does not minify them.
3310 */
3311var PersistentConnection = /** @class */ (function (_super) {
3312 tslib.__extends(PersistentConnection, _super);
3313 /**
3314 * @param repoInfo_ - Data about the namespace we are connecting to
3315 * @param applicationId_ - The Firebase App ID for this project
3316 * @param onDataUpdate_ - A callback for new data from the server
3317 */
3318 function PersistentConnection(repoInfo_, applicationId_, onDataUpdate_, onConnectStatus_, onServerInfoUpdate_, authTokenProvider_, appCheckTokenProvider_, authOverride_) {
3319 var _this = _super.call(this) || this;
3320 _this.repoInfo_ = repoInfo_;
3321 _this.applicationId_ = applicationId_;
3322 _this.onDataUpdate_ = onDataUpdate_;
3323 _this.onConnectStatus_ = onConnectStatus_;
3324 _this.onServerInfoUpdate_ = onServerInfoUpdate_;
3325 _this.authTokenProvider_ = authTokenProvider_;
3326 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
3327 _this.authOverride_ = authOverride_;
3328 // Used for diagnostic logging.
3329 _this.id = PersistentConnection.nextPersistentConnectionId_++;
3330 _this.log_ = logWrapper('p:' + _this.id + ':');
3331 _this.interruptReasons_ = {};
3332 _this.listens = new Map();
3333 _this.outstandingPuts_ = [];
3334 _this.outstandingGets_ = [];
3335 _this.outstandingPutCount_ = 0;
3336 _this.outstandingGetCount_ = 0;
3337 _this.onDisconnectRequestQueue_ = [];
3338 _this.connected_ = false;
3339 _this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3340 _this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_DEFAULT;
3341 _this.securityDebugCallback_ = null;
3342 _this.lastSessionId = null;
3343 _this.establishConnectionTimer_ = null;
3344 _this.visible_ = false;
3345 // Before we get connected, we keep a queue of pending messages to send.
3346 _this.requestCBHash_ = {};
3347 _this.requestNumber_ = 0;
3348 _this.realtime_ = null;
3349 _this.authToken_ = null;
3350 _this.appCheckToken_ = null;
3351 _this.forceTokenRefresh_ = false;
3352 _this.invalidAuthTokenCount_ = 0;
3353 _this.invalidAppCheckTokenCount_ = 0;
3354 _this.firstConnection_ = true;
3355 _this.lastConnectionAttemptTime_ = null;
3356 _this.lastConnectionEstablishedTime_ = null;
3357 if (authOverride_ && !util.isNodeSdk()) {
3358 throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
3359 }
3360 VisibilityMonitor.getInstance().on('visible', _this.onVisible_, _this);
3361 if (repoInfo_.host.indexOf('fblocal') === -1) {
3362 OnlineMonitor.getInstance().on('online', _this.onOnline_, _this);
3363 }
3364 return _this;
3365 }
3366 PersistentConnection.prototype.sendRequest = function (action, body, onResponse) {
3367 var curReqNum = ++this.requestNumber_;
3368 var msg = { r: curReqNum, a: action, b: body };
3369 this.log_(util.stringify(msg));
3370 util.assert(this.connected_, "sendRequest call when we're not connected not allowed.");
3371 this.realtime_.sendRequest(msg);
3372 if (onResponse) {
3373 this.requestCBHash_[curReqNum] = onResponse;
3374 }
3375 };
3376 PersistentConnection.prototype.get = function (query) {
3377 var _this = this;
3378 this.initConnection_();
3379 var deferred = new util.Deferred();
3380 var request = {
3381 p: query._path.toString(),
3382 q: query._queryObject
3383 };
3384 var outstandingGet = {
3385 action: 'g',
3386 request: request,
3387 onComplete: function (message) {
3388 var payload = message['d'];
3389 if (message['s'] === 'ok') {
3390 _this.onDataUpdate_(request['p'], payload,
3391 /*isMerge*/ false,
3392 /*tag*/ null);
3393 deferred.resolve(payload);
3394 }
3395 else {
3396 deferred.reject(payload);
3397 }
3398 }
3399 };
3400 this.outstandingGets_.push(outstandingGet);
3401 this.outstandingGetCount_++;
3402 var index = this.outstandingGets_.length - 1;
3403 if (!this.connected_) {
3404 setTimeout(function () {
3405 var get = _this.outstandingGets_[index];
3406 if (get === undefined || outstandingGet !== get) {
3407 return;
3408 }
3409 delete _this.outstandingGets_[index];
3410 _this.outstandingGetCount_--;
3411 if (_this.outstandingGetCount_ === 0) {
3412 _this.outstandingGets_ = [];
3413 }
3414 _this.log_('get ' + index + ' timed out on connection');
3415 deferred.reject(new Error('Client is offline.'));
3416 }, GET_CONNECT_TIMEOUT);
3417 }
3418 if (this.connected_) {
3419 this.sendGet_(index);
3420 }
3421 return deferred.promise;
3422 };
3423 PersistentConnection.prototype.listen = function (query, currentHashFn, tag, onComplete) {
3424 this.initConnection_();
3425 var queryId = query._queryIdentifier;
3426 var pathString = query._path.toString();
3427 this.log_('Listen called for ' + pathString + ' ' + queryId);
3428 if (!this.listens.has(pathString)) {
3429 this.listens.set(pathString, new Map());
3430 }
3431 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'listen() called for non-default but complete query');
3432 util.assert(!this.listens.get(pathString).has(queryId), 'listen() called twice for same path/queryId.');
3433 var listenSpec = {
3434 onComplete: onComplete,
3435 hashFn: currentHashFn,
3436 query: query,
3437 tag: tag
3438 };
3439 this.listens.get(pathString).set(queryId, listenSpec);
3440 if (this.connected_) {
3441 this.sendListen_(listenSpec);
3442 }
3443 };
3444 PersistentConnection.prototype.sendGet_ = function (index) {
3445 var _this = this;
3446 var get = this.outstandingGets_[index];
3447 this.sendRequest('g', get.request, function (message) {
3448 delete _this.outstandingGets_[index];
3449 _this.outstandingGetCount_--;
3450 if (_this.outstandingGetCount_ === 0) {
3451 _this.outstandingGets_ = [];
3452 }
3453 if (get.onComplete) {
3454 get.onComplete(message);
3455 }
3456 });
3457 };
3458 PersistentConnection.prototype.sendListen_ = function (listenSpec) {
3459 var _this = this;
3460 var query = listenSpec.query;
3461 var pathString = query._path.toString();
3462 var queryId = query._queryIdentifier;
3463 this.log_('Listen on ' + pathString + ' for ' + queryId);
3464 var req = { /*path*/ p: pathString };
3465 var action = 'q';
3466 // Only bother to send query if it's non-default.
3467 if (listenSpec.tag) {
3468 req['q'] = query._queryObject;
3469 req['t'] = listenSpec.tag;
3470 }
3471 req[ /*hash*/'h'] = listenSpec.hashFn();
3472 this.sendRequest(action, req, function (message) {
3473 var payload = message[ /*data*/'d'];
3474 var status = message[ /*status*/'s'];
3475 // print warnings in any case...
3476 PersistentConnection.warnOnListenWarnings_(payload, query);
3477 var currentListenSpec = _this.listens.get(pathString) &&
3478 _this.listens.get(pathString).get(queryId);
3479 // only trigger actions if the listen hasn't been removed and readded
3480 if (currentListenSpec === listenSpec) {
3481 _this.log_('listen response', message);
3482 if (status !== 'ok') {
3483 _this.removeListen_(pathString, queryId);
3484 }
3485 if (listenSpec.onComplete) {
3486 listenSpec.onComplete(status, payload);
3487 }
3488 }
3489 });
3490 };
3491 PersistentConnection.warnOnListenWarnings_ = function (payload, query) {
3492 if (payload && typeof payload === 'object' && util.contains(payload, 'w')) {
3493 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3494 var warnings = util.safeGet(payload, 'w');
3495 if (Array.isArray(warnings) && ~warnings.indexOf('no_index')) {
3496 var indexSpec = '".indexOn": "' + query._queryParams.getIndex().toString() + '"';
3497 var indexPath = query._path.toString();
3498 warn("Using an unspecified index. Your data will be downloaded and " +
3499 ("filtered on the client. Consider adding " + indexSpec + " at ") +
3500 (indexPath + " to your security rules for better performance."));
3501 }
3502 }
3503 };
3504 PersistentConnection.prototype.refreshAuthToken = function (token) {
3505 this.authToken_ = token;
3506 this.log_('Auth token refreshed');
3507 if (this.authToken_) {
3508 this.tryAuth();
3509 }
3510 else {
3511 //If we're connected we want to let the server know to unauthenticate us. If we're not connected, simply delete
3512 //the credential so we dont become authenticated next time we connect.
3513 if (this.connected_) {
3514 this.sendRequest('unauth', {}, function () { });
3515 }
3516 }
3517 this.reduceReconnectDelayIfAdminCredential_(token);
3518 };
3519 PersistentConnection.prototype.reduceReconnectDelayIfAdminCredential_ = function (credential) {
3520 // NOTE: This isn't intended to be bulletproof (a malicious developer can always just modify the client).
3521 // Additionally, we don't bother resetting the max delay back to the default if auth fails / expires.
3522 var isFirebaseSecret = credential && credential.length === 40;
3523 if (isFirebaseSecret || util.isAdmin(credential)) {
3524 this.log_('Admin auth credential detected. Reducing max reconnect time.');
3525 this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
3526 }
3527 };
3528 PersistentConnection.prototype.refreshAppCheckToken = function (token) {
3529 this.appCheckToken_ = token;
3530 this.log_('App check token refreshed');
3531 if (this.appCheckToken_) {
3532 this.tryAppCheck();
3533 }
3534 else {
3535 //If we're connected we want to let the server know to unauthenticate us.
3536 //If we're not connected, simply delete the credential so we dont become
3537 // authenticated next time we connect.
3538 if (this.connected_) {
3539 this.sendRequest('unappeck', {}, function () { });
3540 }
3541 }
3542 };
3543 /**
3544 * Attempts to authenticate with the given credentials. If the authentication attempt fails, it's triggered like
3545 * a auth revoked (the connection is closed).
3546 */
3547 PersistentConnection.prototype.tryAuth = function () {
3548 var _this = this;
3549 if (this.connected_ && this.authToken_) {
3550 var token_1 = this.authToken_;
3551 var authMethod = util.isValidFormat(token_1) ? 'auth' : 'gauth';
3552 var requestData = { cred: token_1 };
3553 if (this.authOverride_ === null) {
3554 requestData['noauth'] = true;
3555 }
3556 else if (typeof this.authOverride_ === 'object') {
3557 requestData['authvar'] = this.authOverride_;
3558 }
3559 this.sendRequest(authMethod, requestData, function (res) {
3560 var status = res[ /*status*/'s'];
3561 var data = res[ /*data*/'d'] || 'error';
3562 if (_this.authToken_ === token_1) {
3563 if (status === 'ok') {
3564 _this.invalidAuthTokenCount_ = 0;
3565 }
3566 else {
3567 // Triggers reconnect and force refresh for auth token
3568 _this.onAuthRevoked_(status, data);
3569 }
3570 }
3571 });
3572 }
3573 };
3574 /**
3575 * Attempts to authenticate with the given token. If the authentication
3576 * attempt fails, it's triggered like the token was revoked (the connection is
3577 * closed).
3578 */
3579 PersistentConnection.prototype.tryAppCheck = function () {
3580 var _this = this;
3581 if (this.connected_ && this.appCheckToken_) {
3582 this.sendRequest('appcheck', { 'token': this.appCheckToken_ }, function (res) {
3583 var status = res[ /*status*/'s'];
3584 var data = res[ /*data*/'d'] || 'error';
3585 if (status === 'ok') {
3586 _this.invalidAppCheckTokenCount_ = 0;
3587 }
3588 else {
3589 _this.onAppCheckRevoked_(status, data);
3590 }
3591 });
3592 }
3593 };
3594 /**
3595 * @inheritDoc
3596 */
3597 PersistentConnection.prototype.unlisten = function (query, tag) {
3598 var pathString = query._path.toString();
3599 var queryId = query._queryIdentifier;
3600 this.log_('Unlisten called for ' + pathString + ' ' + queryId);
3601 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'unlisten() called for non-default but complete query');
3602 var listen = this.removeListen_(pathString, queryId);
3603 if (listen && this.connected_) {
3604 this.sendUnlisten_(pathString, queryId, query._queryObject, tag);
3605 }
3606 };
3607 PersistentConnection.prototype.sendUnlisten_ = function (pathString, queryId, queryObj, tag) {
3608 this.log_('Unlisten on ' + pathString + ' for ' + queryId);
3609 var req = { /*path*/ p: pathString };
3610 var action = 'n';
3611 // Only bother sending queryId if it's non-default.
3612 if (tag) {
3613 req['q'] = queryObj;
3614 req['t'] = tag;
3615 }
3616 this.sendRequest(action, req);
3617 };
3618 PersistentConnection.prototype.onDisconnectPut = function (pathString, data, onComplete) {
3619 this.initConnection_();
3620 if (this.connected_) {
3621 this.sendOnDisconnect_('o', pathString, data, onComplete);
3622 }
3623 else {
3624 this.onDisconnectRequestQueue_.push({
3625 pathString: pathString,
3626 action: 'o',
3627 data: data,
3628 onComplete: onComplete
3629 });
3630 }
3631 };
3632 PersistentConnection.prototype.onDisconnectMerge = function (pathString, data, onComplete) {
3633 this.initConnection_();
3634 if (this.connected_) {
3635 this.sendOnDisconnect_('om', pathString, data, onComplete);
3636 }
3637 else {
3638 this.onDisconnectRequestQueue_.push({
3639 pathString: pathString,
3640 action: 'om',
3641 data: data,
3642 onComplete: onComplete
3643 });
3644 }
3645 };
3646 PersistentConnection.prototype.onDisconnectCancel = function (pathString, onComplete) {
3647 this.initConnection_();
3648 if (this.connected_) {
3649 this.sendOnDisconnect_('oc', pathString, null, onComplete);
3650 }
3651 else {
3652 this.onDisconnectRequestQueue_.push({
3653 pathString: pathString,
3654 action: 'oc',
3655 data: null,
3656 onComplete: onComplete
3657 });
3658 }
3659 };
3660 PersistentConnection.prototype.sendOnDisconnect_ = function (action, pathString, data, onComplete) {
3661 var request = { /*path*/ p: pathString, /*data*/ d: data };
3662 this.log_('onDisconnect ' + action, request);
3663 this.sendRequest(action, request, function (response) {
3664 if (onComplete) {
3665 setTimeout(function () {
3666 onComplete(response[ /*status*/'s'], response[ /* data */'d']);
3667 }, Math.floor(0));
3668 }
3669 });
3670 };
3671 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
3672 this.putInternal('p', pathString, data, onComplete, hash);
3673 };
3674 PersistentConnection.prototype.merge = function (pathString, data, onComplete, hash) {
3675 this.putInternal('m', pathString, data, onComplete, hash);
3676 };
3677 PersistentConnection.prototype.putInternal = function (action, pathString, data, onComplete, hash) {
3678 this.initConnection_();
3679 var request = {
3680 /*path*/ p: pathString,
3681 /*data*/ d: data
3682 };
3683 if (hash !== undefined) {
3684 request[ /*hash*/'h'] = hash;
3685 }
3686 // TODO: Only keep track of the most recent put for a given path?
3687 this.outstandingPuts_.push({
3688 action: action,
3689 request: request,
3690 onComplete: onComplete
3691 });
3692 this.outstandingPutCount_++;
3693 var index = this.outstandingPuts_.length - 1;
3694 if (this.connected_) {
3695 this.sendPut_(index);
3696 }
3697 else {
3698 this.log_('Buffering put: ' + pathString);
3699 }
3700 };
3701 PersistentConnection.prototype.sendPut_ = function (index) {
3702 var _this = this;
3703 var action = this.outstandingPuts_[index].action;
3704 var request = this.outstandingPuts_[index].request;
3705 var onComplete = this.outstandingPuts_[index].onComplete;
3706 this.outstandingPuts_[index].queued = this.connected_;
3707 this.sendRequest(action, request, function (message) {
3708 _this.log_(action + ' response', message);
3709 delete _this.outstandingPuts_[index];
3710 _this.outstandingPutCount_--;
3711 // Clean up array occasionally.
3712 if (_this.outstandingPutCount_ === 0) {
3713 _this.outstandingPuts_ = [];
3714 }
3715 if (onComplete) {
3716 onComplete(message[ /*status*/'s'], message[ /* data */'d']);
3717 }
3718 });
3719 };
3720 PersistentConnection.prototype.reportStats = function (stats) {
3721 var _this = this;
3722 // If we're not connected, we just drop the stats.
3723 if (this.connected_) {
3724 var request = { /*counters*/ c: stats };
3725 this.log_('reportStats', request);
3726 this.sendRequest(/*stats*/ 's', request, function (result) {
3727 var status = result[ /*status*/'s'];
3728 if (status !== 'ok') {
3729 var errorReason = result[ /* data */'d'];
3730 _this.log_('reportStats', 'Error sending stats: ' + errorReason);
3731 }
3732 });
3733 }
3734 };
3735 PersistentConnection.prototype.onDataMessage_ = function (message) {
3736 if ('r' in message) {
3737 // this is a response
3738 this.log_('from server: ' + util.stringify(message));
3739 var reqNum = message['r'];
3740 var onResponse = this.requestCBHash_[reqNum];
3741 if (onResponse) {
3742 delete this.requestCBHash_[reqNum];
3743 onResponse(message[ /*body*/'b']);
3744 }
3745 }
3746 else if ('error' in message) {
3747 throw 'A server-side error has occurred: ' + message['error'];
3748 }
3749 else if ('a' in message) {
3750 // a and b are action and body, respectively
3751 this.onDataPush_(message['a'], message['b']);
3752 }
3753 };
3754 PersistentConnection.prototype.onDataPush_ = function (action, body) {
3755 this.log_('handleServerMessage', action, body);
3756 if (action === 'd') {
3757 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3758 /*isMerge*/ false, body['t']);
3759 }
3760 else if (action === 'm') {
3761 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3762 /*isMerge=*/ true, body['t']);
3763 }
3764 else if (action === 'c') {
3765 this.onListenRevoked_(body[ /*path*/'p'], body[ /*query*/'q']);
3766 }
3767 else if (action === 'ac') {
3768 this.onAuthRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3769 }
3770 else if (action === 'apc') {
3771 this.onAppCheckRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3772 }
3773 else if (action === 'sd') {
3774 this.onSecurityDebugPacket_(body);
3775 }
3776 else {
3777 error('Unrecognized action received from server: ' +
3778 util.stringify(action) +
3779 '\nAre you using the latest client?');
3780 }
3781 };
3782 PersistentConnection.prototype.onReady_ = function (timestamp, sessionId) {
3783 this.log_('connection ready');
3784 this.connected_ = true;
3785 this.lastConnectionEstablishedTime_ = new Date().getTime();
3786 this.handleTimestamp_(timestamp);
3787 this.lastSessionId = sessionId;
3788 if (this.firstConnection_) {
3789 this.sendConnectStats_();
3790 }
3791 this.restoreState_();
3792 this.firstConnection_ = false;
3793 this.onConnectStatus_(true);
3794 };
3795 PersistentConnection.prototype.scheduleConnect_ = function (timeout) {
3796 var _this = this;
3797 util.assert(!this.realtime_, "Scheduling a connect when we're already connected/ing?");
3798 if (this.establishConnectionTimer_) {
3799 clearTimeout(this.establishConnectionTimer_);
3800 }
3801 // NOTE: Even when timeout is 0, it's important to do a setTimeout to work around an infuriating "Security Error" in
3802 // Firefox when trying to write to our long-polling iframe in some scenarios (e.g. Forge or our unit tests).
3803 this.establishConnectionTimer_ = setTimeout(function () {
3804 _this.establishConnectionTimer_ = null;
3805 _this.establishConnection_();
3806 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3807 }, Math.floor(timeout));
3808 };
3809 PersistentConnection.prototype.initConnection_ = function () {
3810 if (!this.realtime_ && this.firstConnection_) {
3811 this.scheduleConnect_(0);
3812 }
3813 };
3814 PersistentConnection.prototype.onVisible_ = function (visible) {
3815 // NOTE: Tabbing away and back to a window will defeat our reconnect backoff, but I think that's fine.
3816 if (visible &&
3817 !this.visible_ &&
3818 this.reconnectDelay_ === this.maxReconnectDelay_) {
3819 this.log_('Window became visible. Reducing delay.');
3820 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3821 if (!this.realtime_) {
3822 this.scheduleConnect_(0);
3823 }
3824 }
3825 this.visible_ = visible;
3826 };
3827 PersistentConnection.prototype.onOnline_ = function (online) {
3828 if (online) {
3829 this.log_('Browser went online.');
3830 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3831 if (!this.realtime_) {
3832 this.scheduleConnect_(0);
3833 }
3834 }
3835 else {
3836 this.log_('Browser went offline. Killing connection.');
3837 if (this.realtime_) {
3838 this.realtime_.close();
3839 }
3840 }
3841 };
3842 PersistentConnection.prototype.onRealtimeDisconnect_ = function () {
3843 this.log_('data client disconnected');
3844 this.connected_ = false;
3845 this.realtime_ = null;
3846 // Since we don't know if our sent transactions succeeded or not, we need to cancel them.
3847 this.cancelSentTransactions_();
3848 // Clear out the pending requests.
3849 this.requestCBHash_ = {};
3850 if (this.shouldReconnect_()) {
3851 if (!this.visible_) {
3852 this.log_("Window isn't visible. Delaying reconnect.");
3853 this.reconnectDelay_ = this.maxReconnectDelay_;
3854 this.lastConnectionAttemptTime_ = new Date().getTime();
3855 }
3856 else if (this.lastConnectionEstablishedTime_) {
3857 // If we've been connected long enough, reset reconnect delay to minimum.
3858 var timeSinceLastConnectSucceeded = new Date().getTime() - this.lastConnectionEstablishedTime_;
3859 if (timeSinceLastConnectSucceeded > RECONNECT_DELAY_RESET_TIMEOUT) {
3860 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3861 }
3862 this.lastConnectionEstablishedTime_ = null;
3863 }
3864 var timeSinceLastConnectAttempt = new Date().getTime() - this.lastConnectionAttemptTime_;
3865 var reconnectDelay = Math.max(0, this.reconnectDelay_ - timeSinceLastConnectAttempt);
3866 reconnectDelay = Math.random() * reconnectDelay;
3867 this.log_('Trying to reconnect in ' + reconnectDelay + 'ms');
3868 this.scheduleConnect_(reconnectDelay);
3869 // Adjust reconnect delay for next time.
3870 this.reconnectDelay_ = Math.min(this.maxReconnectDelay_, this.reconnectDelay_ * RECONNECT_DELAY_MULTIPLIER);
3871 }
3872 this.onConnectStatus_(false);
3873 };
3874 PersistentConnection.prototype.establishConnection_ = function () {
3875 return tslib.__awaiter(this, void 0, void 0, function () {
3876 var onDataMessage, onReady, onDisconnect_1, connId, lastSessionId, canceled_1, connection_1, closeFn, sendRequestFn, forceRefresh, _a, authToken, appCheckToken, error_1;
3877 var _this = this;
3878 return tslib.__generator(this, function (_b) {
3879 switch (_b.label) {
3880 case 0:
3881 if (!this.shouldReconnect_()) return [3 /*break*/, 4];
3882 this.log_('Making a connection attempt');
3883 this.lastConnectionAttemptTime_ = new Date().getTime();
3884 this.lastConnectionEstablishedTime_ = null;
3885 onDataMessage = this.onDataMessage_.bind(this);
3886 onReady = this.onReady_.bind(this);
3887 onDisconnect_1 = this.onRealtimeDisconnect_.bind(this);
3888 connId = this.id + ':' + PersistentConnection.nextConnectionId_++;
3889 lastSessionId = this.lastSessionId;
3890 canceled_1 = false;
3891 connection_1 = null;
3892 closeFn = function () {
3893 if (connection_1) {
3894 connection_1.close();
3895 }
3896 else {
3897 canceled_1 = true;
3898 onDisconnect_1();
3899 }
3900 };
3901 sendRequestFn = function (msg) {
3902 util.assert(connection_1, "sendRequest call when we're not connected not allowed.");
3903 connection_1.sendRequest(msg);
3904 };
3905 this.realtime_ = {
3906 close: closeFn,
3907 sendRequest: sendRequestFn
3908 };
3909 forceRefresh = this.forceTokenRefresh_;
3910 this.forceTokenRefresh_ = false;
3911 _b.label = 1;
3912 case 1:
3913 _b.trys.push([1, 3, , 4]);
3914 return [4 /*yield*/, Promise.all([
3915 this.authTokenProvider_.getToken(forceRefresh),
3916 this.appCheckTokenProvider_.getToken(forceRefresh)
3917 ])];
3918 case 2:
3919 _a = tslib.__read.apply(void 0, [_b.sent(), 2]), authToken = _a[0], appCheckToken = _a[1];
3920 if (!canceled_1) {
3921 log('getToken() completed. Creating connection.');
3922 this.authToken_ = authToken && authToken.accessToken;
3923 this.appCheckToken_ = appCheckToken && appCheckToken.token;
3924 connection_1 = new Connection(connId, this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, onDataMessage, onReady, onDisconnect_1,
3925 /* onKill= */ function (reason) {
3926 warn(reason + ' (' + _this.repoInfo_.toString() + ')');
3927 _this.interrupt(SERVER_KILL_INTERRUPT_REASON);
3928 }, lastSessionId);
3929 }
3930 else {
3931 log('getToken() completed but was canceled');
3932 }
3933 return [3 /*break*/, 4];
3934 case 3:
3935 error_1 = _b.sent();
3936 this.log_('Failed to get token: ' + error_1);
3937 if (!canceled_1) {
3938 if (this.repoInfo_.nodeAdmin) {
3939 // This may be a critical error for the Admin Node.js SDK, so log a warning.
3940 // But getToken() may also just have temporarily failed, so we still want to
3941 // continue retrying.
3942 warn(error_1);
3943 }
3944 closeFn();
3945 }
3946 return [3 /*break*/, 4];
3947 case 4: return [2 /*return*/];
3948 }
3949 });
3950 });
3951 };
3952 PersistentConnection.prototype.interrupt = function (reason) {
3953 log('Interrupting connection for reason: ' + reason);
3954 this.interruptReasons_[reason] = true;
3955 if (this.realtime_) {
3956 this.realtime_.close();
3957 }
3958 else {
3959 if (this.establishConnectionTimer_) {
3960 clearTimeout(this.establishConnectionTimer_);
3961 this.establishConnectionTimer_ = null;
3962 }
3963 if (this.connected_) {
3964 this.onRealtimeDisconnect_();
3965 }
3966 }
3967 };
3968 PersistentConnection.prototype.resume = function (reason) {
3969 log('Resuming connection for reason: ' + reason);
3970 delete this.interruptReasons_[reason];
3971 if (util.isEmpty(this.interruptReasons_)) {
3972 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3973 if (!this.realtime_) {
3974 this.scheduleConnect_(0);
3975 }
3976 }
3977 };
3978 PersistentConnection.prototype.handleTimestamp_ = function (timestamp) {
3979 var delta = timestamp - new Date().getTime();
3980 this.onServerInfoUpdate_({ serverTimeOffset: delta });
3981 };
3982 PersistentConnection.prototype.cancelSentTransactions_ = function () {
3983 for (var i = 0; i < this.outstandingPuts_.length; i++) {
3984 var put = this.outstandingPuts_[i];
3985 if (put && /*hash*/ 'h' in put.request && put.queued) {
3986 if (put.onComplete) {
3987 put.onComplete('disconnect');
3988 }
3989 delete this.outstandingPuts_[i];
3990 this.outstandingPutCount_--;
3991 }
3992 }
3993 // Clean up array occasionally.
3994 if (this.outstandingPutCount_ === 0) {
3995 this.outstandingPuts_ = [];
3996 }
3997 };
3998 PersistentConnection.prototype.onListenRevoked_ = function (pathString, query) {
3999 // Remove the listen and manufacture a "permission_denied" error for the failed listen.
4000 var queryId;
4001 if (!query) {
4002 queryId = 'default';
4003 }
4004 else {
4005 queryId = query.map(function (q) { return ObjectToUniqueKey(q); }).join('$');
4006 }
4007 var listen = this.removeListen_(pathString, queryId);
4008 if (listen && listen.onComplete) {
4009 listen.onComplete('permission_denied');
4010 }
4011 };
4012 PersistentConnection.prototype.removeListen_ = function (pathString, queryId) {
4013 var normalizedPathString = new Path(pathString).toString(); // normalize path.
4014 var listen;
4015 if (this.listens.has(normalizedPathString)) {
4016 var map = this.listens.get(normalizedPathString);
4017 listen = map.get(queryId);
4018 map.delete(queryId);
4019 if (map.size === 0) {
4020 this.listens.delete(normalizedPathString);
4021 }
4022 }
4023 else {
4024 // all listens for this path has already been removed
4025 listen = undefined;
4026 }
4027 return listen;
4028 };
4029 PersistentConnection.prototype.onAuthRevoked_ = function (statusCode, explanation) {
4030 log('Auth token revoked: ' + statusCode + '/' + explanation);
4031 this.authToken_ = null;
4032 this.forceTokenRefresh_ = true;
4033 this.realtime_.close();
4034 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4035 // We'll wait a couple times before logging the warning / increasing the
4036 // retry period since oauth tokens will report as "invalid" if they're
4037 // just expired. Plus there may be transient issues that resolve themselves.
4038 this.invalidAuthTokenCount_++;
4039 if (this.invalidAuthTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4040 // Set a long reconnect delay because recovery is unlikely
4041 this.reconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
4042 // Notify the auth token provider that the token is invalid, which will log
4043 // a warning
4044 this.authTokenProvider_.notifyForInvalidToken();
4045 }
4046 }
4047 };
4048 PersistentConnection.prototype.onAppCheckRevoked_ = function (statusCode, explanation) {
4049 log('App check token revoked: ' + statusCode + '/' + explanation);
4050 this.appCheckToken_ = null;
4051 this.forceTokenRefresh_ = true;
4052 // Note: We don't close the connection as the developer may not have
4053 // enforcement enabled. The backend closes connections with enforcements.
4054 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4055 // We'll wait a couple times before logging the warning / increasing the
4056 // retry period since oauth tokens will report as "invalid" if they're
4057 // just expired. Plus there may be transient issues that resolve themselves.
4058 this.invalidAppCheckTokenCount_++;
4059 if (this.invalidAppCheckTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4060 this.appCheckTokenProvider_.notifyForInvalidToken();
4061 }
4062 }
4063 };
4064 PersistentConnection.prototype.onSecurityDebugPacket_ = function (body) {
4065 if (this.securityDebugCallback_) {
4066 this.securityDebugCallback_(body);
4067 }
4068 else {
4069 if ('msg' in body) {
4070 console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
4071 }
4072 }
4073 };
4074 PersistentConnection.prototype.restoreState_ = function () {
4075 var e_1, _a, e_2, _b;
4076 //Re-authenticate ourselves if we have a credential stored.
4077 this.tryAuth();
4078 this.tryAppCheck();
4079 try {
4080 // Puts depend on having received the corresponding data update from the server before they complete, so we must
4081 // make sure to send listens before puts.
4082 for (var _c = tslib.__values(this.listens.values()), _d = _c.next(); !_d.done; _d = _c.next()) {
4083 var queries = _d.value;
4084 try {
4085 for (var _e = (e_2 = void 0, tslib.__values(queries.values())), _f = _e.next(); !_f.done; _f = _e.next()) {
4086 var listenSpec = _f.value;
4087 this.sendListen_(listenSpec);
4088 }
4089 }
4090 catch (e_2_1) { e_2 = { error: e_2_1 }; }
4091 finally {
4092 try {
4093 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
4094 }
4095 finally { if (e_2) throw e_2.error; }
4096 }
4097 }
4098 }
4099 catch (e_1_1) { e_1 = { error: e_1_1 }; }
4100 finally {
4101 try {
4102 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
4103 }
4104 finally { if (e_1) throw e_1.error; }
4105 }
4106 for (var i = 0; i < this.outstandingPuts_.length; i++) {
4107 if (this.outstandingPuts_[i]) {
4108 this.sendPut_(i);
4109 }
4110 }
4111 while (this.onDisconnectRequestQueue_.length) {
4112 var request = this.onDisconnectRequestQueue_.shift();
4113 this.sendOnDisconnect_(request.action, request.pathString, request.data, request.onComplete);
4114 }
4115 for (var i = 0; i < this.outstandingGets_.length; i++) {
4116 if (this.outstandingGets_[i]) {
4117 this.sendGet_(i);
4118 }
4119 }
4120 };
4121 /**
4122 * Sends client stats for first connection
4123 */
4124 PersistentConnection.prototype.sendConnectStats_ = function () {
4125 var stats = {};
4126 var clientName = 'js';
4127 if (util.isNodeSdk()) {
4128 if (this.repoInfo_.nodeAdmin) {
4129 clientName = 'admin_node';
4130 }
4131 else {
4132 clientName = 'node';
4133 }
4134 }
4135 stats['sdk.' + clientName + '.' + SDK_VERSION.replace(/\./g, '-')] = 1;
4136 if (util.isMobileCordova()) {
4137 stats['framework.cordova'] = 1;
4138 }
4139 else if (util.isReactNative()) {
4140 stats['framework.reactnative'] = 1;
4141 }
4142 this.reportStats(stats);
4143 };
4144 PersistentConnection.prototype.shouldReconnect_ = function () {
4145 var online = OnlineMonitor.getInstance().currentlyOnline();
4146 return util.isEmpty(this.interruptReasons_) && online;
4147 };
4148 PersistentConnection.nextPersistentConnectionId_ = 0;
4149 /**
4150 * Counter for number of connections created. Mainly used for tagging in the logs
4151 */
4152 PersistentConnection.nextConnectionId_ = 0;
4153 return PersistentConnection;
4154}(ServerActions));
4155
4156/**
4157 * @license
4158 * Copyright 2017 Google LLC
4159 *
4160 * Licensed under the Apache License, Version 2.0 (the "License");
4161 * you may not use this file except in compliance with the License.
4162 * You may obtain a copy of the License at
4163 *
4164 * http://www.apache.org/licenses/LICENSE-2.0
4165 *
4166 * Unless required by applicable law or agreed to in writing, software
4167 * distributed under the License is distributed on an "AS IS" BASIS,
4168 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4169 * See the License for the specific language governing permissions and
4170 * limitations under the License.
4171 */
4172var NamedNode = /** @class */ (function () {
4173 function NamedNode(name, node) {
4174 this.name = name;
4175 this.node = node;
4176 }
4177 NamedNode.Wrap = function (name, node) {
4178 return new NamedNode(name, node);
4179 };
4180 return NamedNode;
4181}());
4182
4183/**
4184 * @license
4185 * Copyright 2017 Google LLC
4186 *
4187 * Licensed under the Apache License, Version 2.0 (the "License");
4188 * you may not use this file except in compliance with the License.
4189 * You may obtain a copy of the License at
4190 *
4191 * http://www.apache.org/licenses/LICENSE-2.0
4192 *
4193 * Unless required by applicable law or agreed to in writing, software
4194 * distributed under the License is distributed on an "AS IS" BASIS,
4195 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4196 * See the License for the specific language governing permissions and
4197 * limitations under the License.
4198 */
4199var Index = /** @class */ (function () {
4200 function Index() {
4201 }
4202 /**
4203 * @returns A standalone comparison function for
4204 * this index
4205 */
4206 Index.prototype.getCompare = function () {
4207 return this.compare.bind(this);
4208 };
4209 /**
4210 * Given a before and after value for a node, determine if the indexed value has changed. Even if they are different,
4211 * it's possible that the changes are isolated to parts of the snapshot that are not indexed.
4212 *
4213 *
4214 * @returns True if the portion of the snapshot being indexed changed between oldNode and newNode
4215 */
4216 Index.prototype.indexedValueChanged = function (oldNode, newNode) {
4217 var oldWrapped = new NamedNode(MIN_NAME, oldNode);
4218 var newWrapped = new NamedNode(MIN_NAME, newNode);
4219 return this.compare(oldWrapped, newWrapped) !== 0;
4220 };
4221 /**
4222 * @returns a node wrapper that will sort equal to or less than
4223 * any other node wrapper, using this index
4224 */
4225 Index.prototype.minPost = function () {
4226 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4227 return NamedNode.MIN;
4228 };
4229 return Index;
4230}());
4231
4232/**
4233 * @license
4234 * Copyright 2017 Google LLC
4235 *
4236 * Licensed under the Apache License, Version 2.0 (the "License");
4237 * you may not use this file except in compliance with the License.
4238 * You may obtain a copy of the License at
4239 *
4240 * http://www.apache.org/licenses/LICENSE-2.0
4241 *
4242 * Unless required by applicable law or agreed to in writing, software
4243 * distributed under the License is distributed on an "AS IS" BASIS,
4244 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4245 * See the License for the specific language governing permissions and
4246 * limitations under the License.
4247 */
4248var __EMPTY_NODE;
4249var KeyIndex = /** @class */ (function (_super) {
4250 tslib.__extends(KeyIndex, _super);
4251 function KeyIndex() {
4252 return _super !== null && _super.apply(this, arguments) || this;
4253 }
4254 Object.defineProperty(KeyIndex, "__EMPTY_NODE", {
4255 get: function () {
4256 return __EMPTY_NODE;
4257 },
4258 set: function (val) {
4259 __EMPTY_NODE = val;
4260 },
4261 enumerable: false,
4262 configurable: true
4263 });
4264 KeyIndex.prototype.compare = function (a, b) {
4265 return nameCompare(a.name, b.name);
4266 };
4267 KeyIndex.prototype.isDefinedOn = function (node) {
4268 // We could probably return true here (since every node has a key), but it's never called
4269 // so just leaving unimplemented for now.
4270 throw util.assertionError('KeyIndex.isDefinedOn not expected to be called.');
4271 };
4272 KeyIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
4273 return false; // The key for a node never changes.
4274 };
4275 KeyIndex.prototype.minPost = function () {
4276 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4277 return NamedNode.MIN;
4278 };
4279 KeyIndex.prototype.maxPost = function () {
4280 // TODO: This should really be created once and cached in a static property, but
4281 // NamedNode isn't defined yet, so I can't use it in a static. Bleh.
4282 return new NamedNode(MAX_NAME, __EMPTY_NODE);
4283 };
4284 KeyIndex.prototype.makePost = function (indexValue, name) {
4285 util.assert(typeof indexValue === 'string', 'KeyIndex indexValue must always be a string.');
4286 // We just use empty node, but it'll never be compared, since our comparator only looks at name.
4287 return new NamedNode(indexValue, __EMPTY_NODE);
4288 };
4289 /**
4290 * @returns String representation for inclusion in a query spec
4291 */
4292 KeyIndex.prototype.toString = function () {
4293 return '.key';
4294 };
4295 return KeyIndex;
4296}(Index));
4297var KEY_INDEX = new KeyIndex();
4298
4299/**
4300 * @license
4301 * Copyright 2017 Google LLC
4302 *
4303 * Licensed under the Apache License, Version 2.0 (the "License");
4304 * you may not use this file except in compliance with the License.
4305 * You may obtain a copy of the License at
4306 *
4307 * http://www.apache.org/licenses/LICENSE-2.0
4308 *
4309 * Unless required by applicable law or agreed to in writing, software
4310 * distributed under the License is distributed on an "AS IS" BASIS,
4311 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4312 * See the License for the specific language governing permissions and
4313 * limitations under the License.
4314 */
4315/**
4316 * An iterator over an LLRBNode.
4317 */
4318var SortedMapIterator = /** @class */ (function () {
4319 /**
4320 * @param node - Node to iterate.
4321 * @param isReverse_ - Whether or not to iterate in reverse
4322 */
4323 function SortedMapIterator(node, startKey, comparator, isReverse_, resultGenerator_) {
4324 if (resultGenerator_ === void 0) { resultGenerator_ = null; }
4325 this.isReverse_ = isReverse_;
4326 this.resultGenerator_ = resultGenerator_;
4327 this.nodeStack_ = [];
4328 var cmp = 1;
4329 while (!node.isEmpty()) {
4330 node = node;
4331 cmp = startKey ? comparator(node.key, startKey) : 1;
4332 // flip the comparison if we're going in reverse
4333 if (isReverse_) {
4334 cmp *= -1;
4335 }
4336 if (cmp < 0) {
4337 // This node is less than our start key. ignore it
4338 if (this.isReverse_) {
4339 node = node.left;
4340 }
4341 else {
4342 node = node.right;
4343 }
4344 }
4345 else if (cmp === 0) {
4346 // This node is exactly equal to our start key. Push it on the stack, but stop iterating;
4347 this.nodeStack_.push(node);
4348 break;
4349 }
4350 else {
4351 // This node is greater than our start key, add it to the stack and move to the next one
4352 this.nodeStack_.push(node);
4353 if (this.isReverse_) {
4354 node = node.right;
4355 }
4356 else {
4357 node = node.left;
4358 }
4359 }
4360 }
4361 }
4362 SortedMapIterator.prototype.getNext = function () {
4363 if (this.nodeStack_.length === 0) {
4364 return null;
4365 }
4366 var node = this.nodeStack_.pop();
4367 var result;
4368 if (this.resultGenerator_) {
4369 result = this.resultGenerator_(node.key, node.value);
4370 }
4371 else {
4372 result = { key: node.key, value: node.value };
4373 }
4374 if (this.isReverse_) {
4375 node = node.left;
4376 while (!node.isEmpty()) {
4377 this.nodeStack_.push(node);
4378 node = node.right;
4379 }
4380 }
4381 else {
4382 node = node.right;
4383 while (!node.isEmpty()) {
4384 this.nodeStack_.push(node);
4385 node = node.left;
4386 }
4387 }
4388 return result;
4389 };
4390 SortedMapIterator.prototype.hasNext = function () {
4391 return this.nodeStack_.length > 0;
4392 };
4393 SortedMapIterator.prototype.peek = function () {
4394 if (this.nodeStack_.length === 0) {
4395 return null;
4396 }
4397 var node = this.nodeStack_[this.nodeStack_.length - 1];
4398 if (this.resultGenerator_) {
4399 return this.resultGenerator_(node.key, node.value);
4400 }
4401 else {
4402 return { key: node.key, value: node.value };
4403 }
4404 };
4405 return SortedMapIterator;
4406}());
4407/**
4408 * Represents a node in a Left-leaning Red-Black tree.
4409 */
4410var LLRBNode = /** @class */ (function () {
4411 /**
4412 * @param key - Key associated with this node.
4413 * @param value - Value associated with this node.
4414 * @param color - Whether this node is red.
4415 * @param left - Left child.
4416 * @param right - Right child.
4417 */
4418 function LLRBNode(key, value, color, left, right) {
4419 this.key = key;
4420 this.value = value;
4421 this.color = color != null ? color : LLRBNode.RED;
4422 this.left =
4423 left != null ? left : SortedMap.EMPTY_NODE;
4424 this.right =
4425 right != null ? right : SortedMap.EMPTY_NODE;
4426 }
4427 /**
4428 * Returns a copy of the current node, optionally replacing pieces of it.
4429 *
4430 * @param key - New key for the node, or null.
4431 * @param value - New value for the node, or null.
4432 * @param color - New color for the node, or null.
4433 * @param left - New left child for the node, or null.
4434 * @param right - New right child for the node, or null.
4435 * @returns The node copy.
4436 */
4437 LLRBNode.prototype.copy = function (key, value, color, left, right) {
4438 return new LLRBNode(key != null ? key : this.key, value != null ? value : this.value, color != null ? color : this.color, left != null ? left : this.left, right != null ? right : this.right);
4439 };
4440 /**
4441 * @returns The total number of nodes in the tree.
4442 */
4443 LLRBNode.prototype.count = function () {
4444 return this.left.count() + 1 + this.right.count();
4445 };
4446 /**
4447 * @returns True if the tree is empty.
4448 */
4449 LLRBNode.prototype.isEmpty = function () {
4450 return false;
4451 };
4452 /**
4453 * Traverses the tree in key order and calls the specified action function
4454 * for each node.
4455 *
4456 * @param action - Callback function to be called for each
4457 * node. If it returns true, traversal is aborted.
4458 * @returns The first truthy value returned by action, or the last falsey
4459 * value returned by action
4460 */
4461 LLRBNode.prototype.inorderTraversal = function (action) {
4462 return (this.left.inorderTraversal(action) ||
4463 !!action(this.key, this.value) ||
4464 this.right.inorderTraversal(action));
4465 };
4466 /**
4467 * Traverses the tree in reverse key order and calls the specified action function
4468 * for each node.
4469 *
4470 * @param action - Callback function to be called for each
4471 * node. If it returns true, traversal is aborted.
4472 * @returns True if traversal was aborted.
4473 */
4474 LLRBNode.prototype.reverseTraversal = function (action) {
4475 return (this.right.reverseTraversal(action) ||
4476 action(this.key, this.value) ||
4477 this.left.reverseTraversal(action));
4478 };
4479 /**
4480 * @returns The minimum node in the tree.
4481 */
4482 LLRBNode.prototype.min_ = function () {
4483 if (this.left.isEmpty()) {
4484 return this;
4485 }
4486 else {
4487 return this.left.min_();
4488 }
4489 };
4490 /**
4491 * @returns The maximum key in the tree.
4492 */
4493 LLRBNode.prototype.minKey = function () {
4494 return this.min_().key;
4495 };
4496 /**
4497 * @returns The maximum key in the tree.
4498 */
4499 LLRBNode.prototype.maxKey = function () {
4500 if (this.right.isEmpty()) {
4501 return this.key;
4502 }
4503 else {
4504 return this.right.maxKey();
4505 }
4506 };
4507 /**
4508 * @param key - Key to insert.
4509 * @param value - Value to insert.
4510 * @param comparator - Comparator.
4511 * @returns New tree, with the key/value added.
4512 */
4513 LLRBNode.prototype.insert = function (key, value, comparator) {
4514 var n = this;
4515 var cmp = comparator(key, n.key);
4516 if (cmp < 0) {
4517 n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
4518 }
4519 else if (cmp === 0) {
4520 n = n.copy(null, value, null, null, null);
4521 }
4522 else {
4523 n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
4524 }
4525 return n.fixUp_();
4526 };
4527 /**
4528 * @returns New tree, with the minimum key removed.
4529 */
4530 LLRBNode.prototype.removeMin_ = function () {
4531 if (this.left.isEmpty()) {
4532 return SortedMap.EMPTY_NODE;
4533 }
4534 var n = this;
4535 if (!n.left.isRed_() && !n.left.left.isRed_()) {
4536 n = n.moveRedLeft_();
4537 }
4538 n = n.copy(null, null, null, n.left.removeMin_(), null);
4539 return n.fixUp_();
4540 };
4541 /**
4542 * @param key - The key of the item to remove.
4543 * @param comparator - Comparator.
4544 * @returns New tree, with the specified item removed.
4545 */
4546 LLRBNode.prototype.remove = function (key, comparator) {
4547 var n, smallest;
4548 n = this;
4549 if (comparator(key, n.key) < 0) {
4550 if (!n.left.isEmpty() && !n.left.isRed_() && !n.left.left.isRed_()) {
4551 n = n.moveRedLeft_();
4552 }
4553 n = n.copy(null, null, null, n.left.remove(key, comparator), null);
4554 }
4555 else {
4556 if (n.left.isRed_()) {
4557 n = n.rotateRight_();
4558 }
4559 if (!n.right.isEmpty() && !n.right.isRed_() && !n.right.left.isRed_()) {
4560 n = n.moveRedRight_();
4561 }
4562 if (comparator(key, n.key) === 0) {
4563 if (n.right.isEmpty()) {
4564 return SortedMap.EMPTY_NODE;
4565 }
4566 else {
4567 smallest = n.right.min_();
4568 n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin_());
4569 }
4570 }
4571 n = n.copy(null, null, null, null, n.right.remove(key, comparator));
4572 }
4573 return n.fixUp_();
4574 };
4575 /**
4576 * @returns Whether this is a RED node.
4577 */
4578 LLRBNode.prototype.isRed_ = function () {
4579 return this.color;
4580 };
4581 /**
4582 * @returns New tree after performing any needed rotations.
4583 */
4584 LLRBNode.prototype.fixUp_ = function () {
4585 var n = this;
4586 if (n.right.isRed_() && !n.left.isRed_()) {
4587 n = n.rotateLeft_();
4588 }
4589 if (n.left.isRed_() && n.left.left.isRed_()) {
4590 n = n.rotateRight_();
4591 }
4592 if (n.left.isRed_() && n.right.isRed_()) {
4593 n = n.colorFlip_();
4594 }
4595 return n;
4596 };
4597 /**
4598 * @returns New tree, after moveRedLeft.
4599 */
4600 LLRBNode.prototype.moveRedLeft_ = function () {
4601 var n = this.colorFlip_();
4602 if (n.right.left.isRed_()) {
4603 n = n.copy(null, null, null, null, n.right.rotateRight_());
4604 n = n.rotateLeft_();
4605 n = n.colorFlip_();
4606 }
4607 return n;
4608 };
4609 /**
4610 * @returns New tree, after moveRedRight.
4611 */
4612 LLRBNode.prototype.moveRedRight_ = function () {
4613 var n = this.colorFlip_();
4614 if (n.left.left.isRed_()) {
4615 n = n.rotateRight_();
4616 n = n.colorFlip_();
4617 }
4618 return n;
4619 };
4620 /**
4621 * @returns New tree, after rotateLeft.
4622 */
4623 LLRBNode.prototype.rotateLeft_ = function () {
4624 var nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
4625 return this.right.copy(null, null, this.color, nl, null);
4626 };
4627 /**
4628 * @returns New tree, after rotateRight.
4629 */
4630 LLRBNode.prototype.rotateRight_ = function () {
4631 var nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
4632 return this.left.copy(null, null, this.color, null, nr);
4633 };
4634 /**
4635 * @returns Newt ree, after colorFlip.
4636 */
4637 LLRBNode.prototype.colorFlip_ = function () {
4638 var left = this.left.copy(null, null, !this.left.color, null, null);
4639 var right = this.right.copy(null, null, !this.right.color, null, null);
4640 return this.copy(null, null, !this.color, left, right);
4641 };
4642 /**
4643 * For testing.
4644 *
4645 * @returns True if all is well.
4646 */
4647 LLRBNode.prototype.checkMaxDepth_ = function () {
4648 var blackDepth = this.check_();
4649 return Math.pow(2.0, blackDepth) <= this.count() + 1;
4650 };
4651 LLRBNode.prototype.check_ = function () {
4652 if (this.isRed_() && this.left.isRed_()) {
4653 throw new Error('Red node has red child(' + this.key + ',' + this.value + ')');
4654 }
4655 if (this.right.isRed_()) {
4656 throw new Error('Right child of (' + this.key + ',' + this.value + ') is red');
4657 }
4658 var blackDepth = this.left.check_();
4659 if (blackDepth !== this.right.check_()) {
4660 throw new Error('Black depths differ');
4661 }
4662 else {
4663 return blackDepth + (this.isRed_() ? 0 : 1);
4664 }
4665 };
4666 LLRBNode.RED = true;
4667 LLRBNode.BLACK = false;
4668 return LLRBNode;
4669}());
4670/**
4671 * Represents an empty node (a leaf node in the Red-Black Tree).
4672 */
4673var LLRBEmptyNode = /** @class */ (function () {
4674 function LLRBEmptyNode() {
4675 }
4676 /**
4677 * Returns a copy of the current node.
4678 *
4679 * @returns The node copy.
4680 */
4681 LLRBEmptyNode.prototype.copy = function (key, value, color, left, right) {
4682 return this;
4683 };
4684 /**
4685 * Returns a copy of the tree, with the specified key/value added.
4686 *
4687 * @param key - Key to be added.
4688 * @param value - Value to be added.
4689 * @param comparator - Comparator.
4690 * @returns New tree, with item added.
4691 */
4692 LLRBEmptyNode.prototype.insert = function (key, value, comparator) {
4693 return new LLRBNode(key, value, null);
4694 };
4695 /**
4696 * Returns a copy of the tree, with the specified key removed.
4697 *
4698 * @param key - The key to remove.
4699 * @param comparator - Comparator.
4700 * @returns New tree, with item removed.
4701 */
4702 LLRBEmptyNode.prototype.remove = function (key, comparator) {
4703 return this;
4704 };
4705 /**
4706 * @returns The total number of nodes in the tree.
4707 */
4708 LLRBEmptyNode.prototype.count = function () {
4709 return 0;
4710 };
4711 /**
4712 * @returns True if the tree is empty.
4713 */
4714 LLRBEmptyNode.prototype.isEmpty = function () {
4715 return true;
4716 };
4717 /**
4718 * Traverses the tree in key order and calls the specified action function
4719 * for each node.
4720 *
4721 * @param action - Callback function to be called for each
4722 * node. If it returns true, traversal is aborted.
4723 * @returns True if traversal was aborted.
4724 */
4725 LLRBEmptyNode.prototype.inorderTraversal = function (action) {
4726 return false;
4727 };
4728 /**
4729 * Traverses the tree in reverse key order and calls the specified action function
4730 * for each node.
4731 *
4732 * @param action - Callback function to be called for each
4733 * node. If it returns true, traversal is aborted.
4734 * @returns True if traversal was aborted.
4735 */
4736 LLRBEmptyNode.prototype.reverseTraversal = function (action) {
4737 return false;
4738 };
4739 LLRBEmptyNode.prototype.minKey = function () {
4740 return null;
4741 };
4742 LLRBEmptyNode.prototype.maxKey = function () {
4743 return null;
4744 };
4745 LLRBEmptyNode.prototype.check_ = function () {
4746 return 0;
4747 };
4748 /**
4749 * @returns Whether this node is red.
4750 */
4751 LLRBEmptyNode.prototype.isRed_ = function () {
4752 return false;
4753 };
4754 return LLRBEmptyNode;
4755}());
4756/**
4757 * An immutable sorted map implementation, based on a Left-leaning Red-Black
4758 * tree.
4759 */
4760var SortedMap = /** @class */ (function () {
4761 /**
4762 * @param comparator_ - Key comparator.
4763 * @param root_ - Optional root node for the map.
4764 */
4765 function SortedMap(comparator_, root_) {
4766 if (root_ === void 0) { root_ = SortedMap.EMPTY_NODE; }
4767 this.comparator_ = comparator_;
4768 this.root_ = root_;
4769 }
4770 /**
4771 * Returns a copy of the map, with the specified key/value added or replaced.
4772 * (TODO: We should perhaps rename this method to 'put')
4773 *
4774 * @param key - Key to be added.
4775 * @param value - Value to be added.
4776 * @returns New map, with item added.
4777 */
4778 SortedMap.prototype.insert = function (key, value) {
4779 return new SortedMap(this.comparator_, this.root_
4780 .insert(key, value, this.comparator_)
4781 .copy(null, null, LLRBNode.BLACK, null, null));
4782 };
4783 /**
4784 * Returns a copy of the map, with the specified key removed.
4785 *
4786 * @param key - The key to remove.
4787 * @returns New map, with item removed.
4788 */
4789 SortedMap.prototype.remove = function (key) {
4790 return new SortedMap(this.comparator_, this.root_
4791 .remove(key, this.comparator_)
4792 .copy(null, null, LLRBNode.BLACK, null, null));
4793 };
4794 /**
4795 * Returns the value of the node with the given key, or null.
4796 *
4797 * @param key - The key to look up.
4798 * @returns The value of the node with the given key, or null if the
4799 * key doesn't exist.
4800 */
4801 SortedMap.prototype.get = function (key) {
4802 var cmp;
4803 var node = this.root_;
4804 while (!node.isEmpty()) {
4805 cmp = this.comparator_(key, node.key);
4806 if (cmp === 0) {
4807 return node.value;
4808 }
4809 else if (cmp < 0) {
4810 node = node.left;
4811 }
4812 else if (cmp > 0) {
4813 node = node.right;
4814 }
4815 }
4816 return null;
4817 };
4818 /**
4819 * Returns the key of the item *before* the specified key, or null if key is the first item.
4820 * @param key - The key to find the predecessor of
4821 * @returns The predecessor key.
4822 */
4823 SortedMap.prototype.getPredecessorKey = function (key) {
4824 var cmp, node = this.root_, rightParent = null;
4825 while (!node.isEmpty()) {
4826 cmp = this.comparator_(key, node.key);
4827 if (cmp === 0) {
4828 if (!node.left.isEmpty()) {
4829 node = node.left;
4830 while (!node.right.isEmpty()) {
4831 node = node.right;
4832 }
4833 return node.key;
4834 }
4835 else if (rightParent) {
4836 return rightParent.key;
4837 }
4838 else {
4839 return null; // first item.
4840 }
4841 }
4842 else if (cmp < 0) {
4843 node = node.left;
4844 }
4845 else if (cmp > 0) {
4846 rightParent = node;
4847 node = node.right;
4848 }
4849 }
4850 throw new Error('Attempted to find predecessor key for a nonexistent key. What gives?');
4851 };
4852 /**
4853 * @returns True if the map is empty.
4854 */
4855 SortedMap.prototype.isEmpty = function () {
4856 return this.root_.isEmpty();
4857 };
4858 /**
4859 * @returns The total number of nodes in the map.
4860 */
4861 SortedMap.prototype.count = function () {
4862 return this.root_.count();
4863 };
4864 /**
4865 * @returns The minimum key in the map.
4866 */
4867 SortedMap.prototype.minKey = function () {
4868 return this.root_.minKey();
4869 };
4870 /**
4871 * @returns The maximum key in the map.
4872 */
4873 SortedMap.prototype.maxKey = function () {
4874 return this.root_.maxKey();
4875 };
4876 /**
4877 * Traverses the map in key order and calls the specified action function
4878 * for each key/value pair.
4879 *
4880 * @param action - Callback function to be called
4881 * for each key/value pair. If action returns true, traversal is aborted.
4882 * @returns The first truthy value returned by action, or the last falsey
4883 * value returned by action
4884 */
4885 SortedMap.prototype.inorderTraversal = function (action) {
4886 return this.root_.inorderTraversal(action);
4887 };
4888 /**
4889 * Traverses the map in reverse key order and calls the specified action function
4890 * for each key/value pair.
4891 *
4892 * @param action - Callback function to be called
4893 * for each key/value pair. If action returns true, traversal is aborted.
4894 * @returns True if the traversal was aborted.
4895 */
4896 SortedMap.prototype.reverseTraversal = function (action) {
4897 return this.root_.reverseTraversal(action);
4898 };
4899 /**
4900 * Returns an iterator over the SortedMap.
4901 * @returns The iterator.
4902 */
4903 SortedMap.prototype.getIterator = function (resultGenerator) {
4904 return new SortedMapIterator(this.root_, null, this.comparator_, false, resultGenerator);
4905 };
4906 SortedMap.prototype.getIteratorFrom = function (key, resultGenerator) {
4907 return new SortedMapIterator(this.root_, key, this.comparator_, false, resultGenerator);
4908 };
4909 SortedMap.prototype.getReverseIteratorFrom = function (key, resultGenerator) {
4910 return new SortedMapIterator(this.root_, key, this.comparator_, true, resultGenerator);
4911 };
4912 SortedMap.prototype.getReverseIterator = function (resultGenerator) {
4913 return new SortedMapIterator(this.root_, null, this.comparator_, true, resultGenerator);
4914 };
4915 /**
4916 * Always use the same empty node, to reduce memory.
4917 */
4918 SortedMap.EMPTY_NODE = new LLRBEmptyNode();
4919 return SortedMap;
4920}());
4921
4922/**
4923 * @license
4924 * Copyright 2017 Google LLC
4925 *
4926 * Licensed under the Apache License, Version 2.0 (the "License");
4927 * you may not use this file except in compliance with the License.
4928 * You may obtain a copy of the License at
4929 *
4930 * http://www.apache.org/licenses/LICENSE-2.0
4931 *
4932 * Unless required by applicable law or agreed to in writing, software
4933 * distributed under the License is distributed on an "AS IS" BASIS,
4934 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4935 * See the License for the specific language governing permissions and
4936 * limitations under the License.
4937 */
4938function NAME_ONLY_COMPARATOR(left, right) {
4939 return nameCompare(left.name, right.name);
4940}
4941function NAME_COMPARATOR(left, right) {
4942 return nameCompare(left, right);
4943}
4944
4945/**
4946 * @license
4947 * Copyright 2017 Google LLC
4948 *
4949 * Licensed under the Apache License, Version 2.0 (the "License");
4950 * you may not use this file except in compliance with the License.
4951 * You may obtain a copy of the License at
4952 *
4953 * http://www.apache.org/licenses/LICENSE-2.0
4954 *
4955 * Unless required by applicable law or agreed to in writing, software
4956 * distributed under the License is distributed on an "AS IS" BASIS,
4957 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4958 * See the License for the specific language governing permissions and
4959 * limitations under the License.
4960 */
4961var MAX_NODE$2;
4962function setMaxNode$1(val) {
4963 MAX_NODE$2 = val;
4964}
4965var priorityHashText = function (priority) {
4966 if (typeof priority === 'number') {
4967 return 'number:' + doubleToIEEE754String(priority);
4968 }
4969 else {
4970 return 'string:' + priority;
4971 }
4972};
4973/**
4974 * Validates that a priority snapshot Node is valid.
4975 */
4976var validatePriorityNode = function (priorityNode) {
4977 if (priorityNode.isLeafNode()) {
4978 var val = priorityNode.val();
4979 util.assert(typeof val === 'string' ||
4980 typeof val === 'number' ||
4981 (typeof val === 'object' && util.contains(val, '.sv')), 'Priority must be a string or number.');
4982 }
4983 else {
4984 util.assert(priorityNode === MAX_NODE$2 || priorityNode.isEmpty(), 'priority of unexpected type.');
4985 }
4986 // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
4987 util.assert(priorityNode === MAX_NODE$2 || priorityNode.getPriority().isEmpty(), "Priority nodes can't have a priority of their own.");
4988};
4989
4990/**
4991 * @license
4992 * Copyright 2017 Google LLC
4993 *
4994 * Licensed under the Apache License, Version 2.0 (the "License");
4995 * you may not use this file except in compliance with the License.
4996 * You may obtain a copy of the License at
4997 *
4998 * http://www.apache.org/licenses/LICENSE-2.0
4999 *
5000 * Unless required by applicable law or agreed to in writing, software
5001 * distributed under the License is distributed on an "AS IS" BASIS,
5002 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5003 * See the License for the specific language governing permissions and
5004 * limitations under the License.
5005 */
5006var __childrenNodeConstructor;
5007/**
5008 * LeafNode is a class for storing leaf nodes in a DataSnapshot. It
5009 * implements Node and stores the value of the node (a string,
5010 * number, or boolean) accessible via getValue().
5011 */
5012var LeafNode = /** @class */ (function () {
5013 /**
5014 * @param value_ - The value to store in this leaf node. The object type is
5015 * possible in the event of a deferred value
5016 * @param priorityNode_ - The priority of this node.
5017 */
5018 function LeafNode(value_, priorityNode_) {
5019 if (priorityNode_ === void 0) { priorityNode_ = LeafNode.__childrenNodeConstructor.EMPTY_NODE; }
5020 this.value_ = value_;
5021 this.priorityNode_ = priorityNode_;
5022 this.lazyHash_ = null;
5023 util.assert(this.value_ !== undefined && this.value_ !== null, "LeafNode shouldn't be created with null/undefined value.");
5024 validatePriorityNode(this.priorityNode_);
5025 }
5026 Object.defineProperty(LeafNode, "__childrenNodeConstructor", {
5027 get: function () {
5028 return __childrenNodeConstructor;
5029 },
5030 set: function (val) {
5031 __childrenNodeConstructor = val;
5032 },
5033 enumerable: false,
5034 configurable: true
5035 });
5036 /** @inheritDoc */
5037 LeafNode.prototype.isLeafNode = function () {
5038 return true;
5039 };
5040 /** @inheritDoc */
5041 LeafNode.prototype.getPriority = function () {
5042 return this.priorityNode_;
5043 };
5044 /** @inheritDoc */
5045 LeafNode.prototype.updatePriority = function (newPriorityNode) {
5046 return new LeafNode(this.value_, newPriorityNode);
5047 };
5048 /** @inheritDoc */
5049 LeafNode.prototype.getImmediateChild = function (childName) {
5050 // Hack to treat priority as a regular child
5051 if (childName === '.priority') {
5052 return this.priorityNode_;
5053 }
5054 else {
5055 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5056 }
5057 };
5058 /** @inheritDoc */
5059 LeafNode.prototype.getChild = function (path) {
5060 if (pathIsEmpty(path)) {
5061 return this;
5062 }
5063 else if (pathGetFront(path) === '.priority') {
5064 return this.priorityNode_;
5065 }
5066 else {
5067 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5068 }
5069 };
5070 LeafNode.prototype.hasChild = function () {
5071 return false;
5072 };
5073 /** @inheritDoc */
5074 LeafNode.prototype.getPredecessorChildName = function (childName, childNode) {
5075 return null;
5076 };
5077 /** @inheritDoc */
5078 LeafNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5079 if (childName === '.priority') {
5080 return this.updatePriority(newChildNode);
5081 }
5082 else if (newChildNode.isEmpty() && childName !== '.priority') {
5083 return this;
5084 }
5085 else {
5086 return LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateImmediateChild(childName, newChildNode).updatePriority(this.priorityNode_);
5087 }
5088 };
5089 /** @inheritDoc */
5090 LeafNode.prototype.updateChild = function (path, newChildNode) {
5091 var front = pathGetFront(path);
5092 if (front === null) {
5093 return newChildNode;
5094 }
5095 else if (newChildNode.isEmpty() && front !== '.priority') {
5096 return this;
5097 }
5098 else {
5099 util.assert(front !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5100 return this.updateImmediateChild(front, LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateChild(pathPopFront(path), newChildNode));
5101 }
5102 };
5103 /** @inheritDoc */
5104 LeafNode.prototype.isEmpty = function () {
5105 return false;
5106 };
5107 /** @inheritDoc */
5108 LeafNode.prototype.numChildren = function () {
5109 return 0;
5110 };
5111 /** @inheritDoc */
5112 LeafNode.prototype.forEachChild = function (index, action) {
5113 return false;
5114 };
5115 LeafNode.prototype.val = function (exportFormat) {
5116 if (exportFormat && !this.getPriority().isEmpty()) {
5117 return {
5118 '.value': this.getValue(),
5119 '.priority': this.getPriority().val()
5120 };
5121 }
5122 else {
5123 return this.getValue();
5124 }
5125 };
5126 /** @inheritDoc */
5127 LeafNode.prototype.hash = function () {
5128 if (this.lazyHash_ === null) {
5129 var toHash = '';
5130 if (!this.priorityNode_.isEmpty()) {
5131 toHash +=
5132 'priority:' +
5133 priorityHashText(this.priorityNode_.val()) +
5134 ':';
5135 }
5136 var type = typeof this.value_;
5137 toHash += type + ':';
5138 if (type === 'number') {
5139 toHash += doubleToIEEE754String(this.value_);
5140 }
5141 else {
5142 toHash += this.value_;
5143 }
5144 this.lazyHash_ = sha1(toHash);
5145 }
5146 return this.lazyHash_;
5147 };
5148 /**
5149 * Returns the value of the leaf node.
5150 * @returns The value of the node.
5151 */
5152 LeafNode.prototype.getValue = function () {
5153 return this.value_;
5154 };
5155 LeafNode.prototype.compareTo = function (other) {
5156 if (other === LeafNode.__childrenNodeConstructor.EMPTY_NODE) {
5157 return 1;
5158 }
5159 else if (other instanceof LeafNode.__childrenNodeConstructor) {
5160 return -1;
5161 }
5162 else {
5163 util.assert(other.isLeafNode(), 'Unknown node type');
5164 return this.compareToLeafNode_(other);
5165 }
5166 };
5167 /**
5168 * Comparison specifically for two leaf nodes
5169 */
5170 LeafNode.prototype.compareToLeafNode_ = function (otherLeaf) {
5171 var otherLeafType = typeof otherLeaf.value_;
5172 var thisLeafType = typeof this.value_;
5173 var otherIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(otherLeafType);
5174 var thisIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(thisLeafType);
5175 util.assert(otherIndex >= 0, 'Unknown leaf type: ' + otherLeafType);
5176 util.assert(thisIndex >= 0, 'Unknown leaf type: ' + thisLeafType);
5177 if (otherIndex === thisIndex) {
5178 // Same type, compare values
5179 if (thisLeafType === 'object') {
5180 // Deferred value nodes are all equal, but we should also never get to this point...
5181 return 0;
5182 }
5183 else {
5184 // Note that this works because true > false, all others are number or string comparisons
5185 if (this.value_ < otherLeaf.value_) {
5186 return -1;
5187 }
5188 else if (this.value_ === otherLeaf.value_) {
5189 return 0;
5190 }
5191 else {
5192 return 1;
5193 }
5194 }
5195 }
5196 else {
5197 return thisIndex - otherIndex;
5198 }
5199 };
5200 LeafNode.prototype.withIndex = function () {
5201 return this;
5202 };
5203 LeafNode.prototype.isIndexed = function () {
5204 return true;
5205 };
5206 LeafNode.prototype.equals = function (other) {
5207 if (other === this) {
5208 return true;
5209 }
5210 else if (other.isLeafNode()) {
5211 var otherLeaf = other;
5212 return (this.value_ === otherLeaf.value_ &&
5213 this.priorityNode_.equals(otherLeaf.priorityNode_));
5214 }
5215 else {
5216 return false;
5217 }
5218 };
5219 /**
5220 * The sort order for comparing leaf nodes of different types. If two leaf nodes have
5221 * the same type, the comparison falls back to their value
5222 */
5223 LeafNode.VALUE_TYPE_ORDER = ['object', 'boolean', 'number', 'string'];
5224 return LeafNode;
5225}());
5226
5227/**
5228 * @license
5229 * Copyright 2017 Google LLC
5230 *
5231 * Licensed under the Apache License, Version 2.0 (the "License");
5232 * you may not use this file except in compliance with the License.
5233 * You may obtain a copy of the License at
5234 *
5235 * http://www.apache.org/licenses/LICENSE-2.0
5236 *
5237 * Unless required by applicable law or agreed to in writing, software
5238 * distributed under the License is distributed on an "AS IS" BASIS,
5239 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5240 * See the License for the specific language governing permissions and
5241 * limitations under the License.
5242 */
5243var nodeFromJSON$1;
5244var MAX_NODE$1;
5245function setNodeFromJSON(val) {
5246 nodeFromJSON$1 = val;
5247}
5248function setMaxNode(val) {
5249 MAX_NODE$1 = val;
5250}
5251var PriorityIndex = /** @class */ (function (_super) {
5252 tslib.__extends(PriorityIndex, _super);
5253 function PriorityIndex() {
5254 return _super !== null && _super.apply(this, arguments) || this;
5255 }
5256 PriorityIndex.prototype.compare = function (a, b) {
5257 var aPriority = a.node.getPriority();
5258 var bPriority = b.node.getPriority();
5259 var indexCmp = aPriority.compareTo(bPriority);
5260 if (indexCmp === 0) {
5261 return nameCompare(a.name, b.name);
5262 }
5263 else {
5264 return indexCmp;
5265 }
5266 };
5267 PriorityIndex.prototype.isDefinedOn = function (node) {
5268 return !node.getPriority().isEmpty();
5269 };
5270 PriorityIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
5271 return !oldNode.getPriority().equals(newNode.getPriority());
5272 };
5273 PriorityIndex.prototype.minPost = function () {
5274 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5275 return NamedNode.MIN;
5276 };
5277 PriorityIndex.prototype.maxPost = function () {
5278 return new NamedNode(MAX_NAME, new LeafNode('[PRIORITY-POST]', MAX_NODE$1));
5279 };
5280 PriorityIndex.prototype.makePost = function (indexValue, name) {
5281 var priorityNode = nodeFromJSON$1(indexValue);
5282 return new NamedNode(name, new LeafNode('[PRIORITY-POST]', priorityNode));
5283 };
5284 /**
5285 * @returns String representation for inclusion in a query spec
5286 */
5287 PriorityIndex.prototype.toString = function () {
5288 return '.priority';
5289 };
5290 return PriorityIndex;
5291}(Index));
5292var PRIORITY_INDEX = new PriorityIndex();
5293
5294/**
5295 * @license
5296 * Copyright 2017 Google LLC
5297 *
5298 * Licensed under the Apache License, Version 2.0 (the "License");
5299 * you may not use this file except in compliance with the License.
5300 * You may obtain a copy of the License at
5301 *
5302 * http://www.apache.org/licenses/LICENSE-2.0
5303 *
5304 * Unless required by applicable law or agreed to in writing, software
5305 * distributed under the License is distributed on an "AS IS" BASIS,
5306 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5307 * See the License for the specific language governing permissions and
5308 * limitations under the License.
5309 */
5310var LOG_2 = Math.log(2);
5311var Base12Num = /** @class */ (function () {
5312 function Base12Num(length) {
5313 var logBase2 = function (num) {
5314 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5315 return parseInt((Math.log(num) / LOG_2), 10);
5316 };
5317 var bitMask = function (bits) { return parseInt(Array(bits + 1).join('1'), 2); };
5318 this.count = logBase2(length + 1);
5319 this.current_ = this.count - 1;
5320 var mask = bitMask(this.count);
5321 this.bits_ = (length + 1) & mask;
5322 }
5323 Base12Num.prototype.nextBitIsOne = function () {
5324 //noinspection JSBitwiseOperatorUsage
5325 var result = !(this.bits_ & (0x1 << this.current_));
5326 this.current_--;
5327 return result;
5328 };
5329 return Base12Num;
5330}());
5331/**
5332 * Takes a list of child nodes and constructs a SortedSet using the given comparison
5333 * function
5334 *
5335 * Uses the algorithm described in the paper linked here:
5336 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458
5337 *
5338 * @param childList - Unsorted list of children
5339 * @param cmp - The comparison method to be used
5340 * @param keyFn - An optional function to extract K from a node wrapper, if K's
5341 * type is not NamedNode
5342 * @param mapSortFn - An optional override for comparator used by the generated sorted map
5343 */
5344var buildChildSet = function (childList, cmp, keyFn, mapSortFn) {
5345 childList.sort(cmp);
5346 var buildBalancedTree = function (low, high) {
5347 var length = high - low;
5348 var namedNode;
5349 var key;
5350 if (length === 0) {
5351 return null;
5352 }
5353 else if (length === 1) {
5354 namedNode = childList[low];
5355 key = keyFn ? keyFn(namedNode) : namedNode;
5356 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, null, null);
5357 }
5358 else {
5359 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5360 var middle = parseInt((length / 2), 10) + low;
5361 var left = buildBalancedTree(low, middle);
5362 var right = buildBalancedTree(middle + 1, high);
5363 namedNode = childList[middle];
5364 key = keyFn ? keyFn(namedNode) : namedNode;
5365 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, left, right);
5366 }
5367 };
5368 var buildFrom12Array = function (base12) {
5369 var node = null;
5370 var root = null;
5371 var index = childList.length;
5372 var buildPennant = function (chunkSize, color) {
5373 var low = index - chunkSize;
5374 var high = index;
5375 index -= chunkSize;
5376 var childTree = buildBalancedTree(low + 1, high);
5377 var namedNode = childList[low];
5378 var key = keyFn ? keyFn(namedNode) : namedNode;
5379 attachPennant(new LLRBNode(key, namedNode.node, color, null, childTree));
5380 };
5381 var attachPennant = function (pennant) {
5382 if (node) {
5383 node.left = pennant;
5384 node = pennant;
5385 }
5386 else {
5387 root = pennant;
5388 node = pennant;
5389 }
5390 };
5391 for (var i = 0; i < base12.count; ++i) {
5392 var isOne = base12.nextBitIsOne();
5393 // The number of nodes taken in each slice is 2^(arr.length - (i + 1))
5394 var chunkSize = Math.pow(2, base12.count - (i + 1));
5395 if (isOne) {
5396 buildPennant(chunkSize, LLRBNode.BLACK);
5397 }
5398 else {
5399 // current == 2
5400 buildPennant(chunkSize, LLRBNode.BLACK);
5401 buildPennant(chunkSize, LLRBNode.RED);
5402 }
5403 }
5404 return root;
5405 };
5406 var base12 = new Base12Num(childList.length);
5407 var root = buildFrom12Array(base12);
5408 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5409 return new SortedMap(mapSortFn || cmp, root);
5410};
5411
5412/**
5413 * @license
5414 * Copyright 2017 Google LLC
5415 *
5416 * Licensed under the Apache License, Version 2.0 (the "License");
5417 * you may not use this file except in compliance with the License.
5418 * You may obtain a copy of the License at
5419 *
5420 * http://www.apache.org/licenses/LICENSE-2.0
5421 *
5422 * Unless required by applicable law or agreed to in writing, software
5423 * distributed under the License is distributed on an "AS IS" BASIS,
5424 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5425 * See the License for the specific language governing permissions and
5426 * limitations under the License.
5427 */
5428var _defaultIndexMap;
5429var fallbackObject = {};
5430var IndexMap = /** @class */ (function () {
5431 function IndexMap(indexes_, indexSet_) {
5432 this.indexes_ = indexes_;
5433 this.indexSet_ = indexSet_;
5434 }
5435 Object.defineProperty(IndexMap, "Default", {
5436 /**
5437 * The default IndexMap for nodes without a priority
5438 */
5439 get: function () {
5440 util.assert(fallbackObject && PRIORITY_INDEX, 'ChildrenNode.ts has not been loaded');
5441 _defaultIndexMap =
5442 _defaultIndexMap ||
5443 new IndexMap({ '.priority': fallbackObject }, { '.priority': PRIORITY_INDEX });
5444 return _defaultIndexMap;
5445 },
5446 enumerable: false,
5447 configurable: true
5448 });
5449 IndexMap.prototype.get = function (indexKey) {
5450 var sortedMap = util.safeGet(this.indexes_, indexKey);
5451 if (!sortedMap) {
5452 throw new Error('No index defined for ' + indexKey);
5453 }
5454 if (sortedMap instanceof SortedMap) {
5455 return sortedMap;
5456 }
5457 else {
5458 // The index exists, but it falls back to just name comparison. Return null so that the calling code uses the
5459 // regular child map
5460 return null;
5461 }
5462 };
5463 IndexMap.prototype.hasIndex = function (indexDefinition) {
5464 return util.contains(this.indexSet_, indexDefinition.toString());
5465 };
5466 IndexMap.prototype.addIndex = function (indexDefinition, existingChildren) {
5467 util.assert(indexDefinition !== KEY_INDEX, "KeyIndex always exists and isn't meant to be added to the IndexMap.");
5468 var childList = [];
5469 var sawIndexedValue = false;
5470 var iter = existingChildren.getIterator(NamedNode.Wrap);
5471 var next = iter.getNext();
5472 while (next) {
5473 sawIndexedValue =
5474 sawIndexedValue || indexDefinition.isDefinedOn(next.node);
5475 childList.push(next);
5476 next = iter.getNext();
5477 }
5478 var newIndex;
5479 if (sawIndexedValue) {
5480 newIndex = buildChildSet(childList, indexDefinition.getCompare());
5481 }
5482 else {
5483 newIndex = fallbackObject;
5484 }
5485 var indexName = indexDefinition.toString();
5486 var newIndexSet = tslib.__assign({}, this.indexSet_);
5487 newIndexSet[indexName] = indexDefinition;
5488 var newIndexes = tslib.__assign({}, this.indexes_);
5489 newIndexes[indexName] = newIndex;
5490 return new IndexMap(newIndexes, newIndexSet);
5491 };
5492 /**
5493 * Ensure that this node is properly tracked in any indexes that we're maintaining
5494 */
5495 IndexMap.prototype.addToIndexes = function (namedNode, existingChildren) {
5496 var _this = this;
5497 var newIndexes = util.map(this.indexes_, function (indexedChildren, indexName) {
5498 var index = util.safeGet(_this.indexSet_, indexName);
5499 util.assert(index, 'Missing index implementation for ' + indexName);
5500 if (indexedChildren === fallbackObject) {
5501 // Check to see if we need to index everything
5502 if (index.isDefinedOn(namedNode.node)) {
5503 // We need to build this index
5504 var childList = [];
5505 var iter = existingChildren.getIterator(NamedNode.Wrap);
5506 var next = iter.getNext();
5507 while (next) {
5508 if (next.name !== namedNode.name) {
5509 childList.push(next);
5510 }
5511 next = iter.getNext();
5512 }
5513 childList.push(namedNode);
5514 return buildChildSet(childList, index.getCompare());
5515 }
5516 else {
5517 // No change, this remains a fallback
5518 return fallbackObject;
5519 }
5520 }
5521 else {
5522 var existingSnap = existingChildren.get(namedNode.name);
5523 var newChildren = indexedChildren;
5524 if (existingSnap) {
5525 newChildren = newChildren.remove(new NamedNode(namedNode.name, existingSnap));
5526 }
5527 return newChildren.insert(namedNode, namedNode.node);
5528 }
5529 });
5530 return new IndexMap(newIndexes, this.indexSet_);
5531 };
5532 /**
5533 * Create a new IndexMap instance with the given value removed
5534 */
5535 IndexMap.prototype.removeFromIndexes = function (namedNode, existingChildren) {
5536 var newIndexes = util.map(this.indexes_, function (indexedChildren) {
5537 if (indexedChildren === fallbackObject) {
5538 // This is the fallback. Just return it, nothing to do in this case
5539 return indexedChildren;
5540 }
5541 else {
5542 var existingSnap = existingChildren.get(namedNode.name);
5543 if (existingSnap) {
5544 return indexedChildren.remove(new NamedNode(namedNode.name, existingSnap));
5545 }
5546 else {
5547 // No record of this child
5548 return indexedChildren;
5549 }
5550 }
5551 });
5552 return new IndexMap(newIndexes, this.indexSet_);
5553 };
5554 return IndexMap;
5555}());
5556
5557/**
5558 * @license
5559 * Copyright 2017 Google LLC
5560 *
5561 * Licensed under the Apache License, Version 2.0 (the "License");
5562 * you may not use this file except in compliance with the License.
5563 * You may obtain a copy of the License at
5564 *
5565 * http://www.apache.org/licenses/LICENSE-2.0
5566 *
5567 * Unless required by applicable law or agreed to in writing, software
5568 * distributed under the License is distributed on an "AS IS" BASIS,
5569 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5570 * See the License for the specific language governing permissions and
5571 * limitations under the License.
5572 */
5573// TODO: For memory savings, don't store priorityNode_ if it's empty.
5574var EMPTY_NODE;
5575/**
5576 * ChildrenNode is a class for storing internal nodes in a DataSnapshot
5577 * (i.e. nodes with children). It implements Node and stores the
5578 * list of children in the children property, sorted by child name.
5579 */
5580var ChildrenNode = /** @class */ (function () {
5581 /**
5582 * @param children_ - List of children of this node..
5583 * @param priorityNode_ - The priority of this node (as a snapshot node).
5584 */
5585 function ChildrenNode(children_, priorityNode_, indexMap_) {
5586 this.children_ = children_;
5587 this.priorityNode_ = priorityNode_;
5588 this.indexMap_ = indexMap_;
5589 this.lazyHash_ = null;
5590 /**
5591 * Note: The only reason we allow null priority is for EMPTY_NODE, since we can't use
5592 * EMPTY_NODE as the priority of EMPTY_NODE. We might want to consider making EMPTY_NODE its own
5593 * class instead of an empty ChildrenNode.
5594 */
5595 if (this.priorityNode_) {
5596 validatePriorityNode(this.priorityNode_);
5597 }
5598 if (this.children_.isEmpty()) {
5599 util.assert(!this.priorityNode_ || this.priorityNode_.isEmpty(), 'An empty node cannot have a priority');
5600 }
5601 }
5602 Object.defineProperty(ChildrenNode, "EMPTY_NODE", {
5603 get: function () {
5604 return (EMPTY_NODE ||
5605 (EMPTY_NODE = new ChildrenNode(new SortedMap(NAME_COMPARATOR), null, IndexMap.Default)));
5606 },
5607 enumerable: false,
5608 configurable: true
5609 });
5610 /** @inheritDoc */
5611 ChildrenNode.prototype.isLeafNode = function () {
5612 return false;
5613 };
5614 /** @inheritDoc */
5615 ChildrenNode.prototype.getPriority = function () {
5616 return this.priorityNode_ || EMPTY_NODE;
5617 };
5618 /** @inheritDoc */
5619 ChildrenNode.prototype.updatePriority = function (newPriorityNode) {
5620 if (this.children_.isEmpty()) {
5621 // Don't allow priorities on empty nodes
5622 return this;
5623 }
5624 else {
5625 return new ChildrenNode(this.children_, newPriorityNode, this.indexMap_);
5626 }
5627 };
5628 /** @inheritDoc */
5629 ChildrenNode.prototype.getImmediateChild = function (childName) {
5630 // Hack to treat priority as a regular child
5631 if (childName === '.priority') {
5632 return this.getPriority();
5633 }
5634 else {
5635 var child = this.children_.get(childName);
5636 return child === null ? EMPTY_NODE : child;
5637 }
5638 };
5639 /** @inheritDoc */
5640 ChildrenNode.prototype.getChild = function (path) {
5641 var front = pathGetFront(path);
5642 if (front === null) {
5643 return this;
5644 }
5645 return this.getImmediateChild(front).getChild(pathPopFront(path));
5646 };
5647 /** @inheritDoc */
5648 ChildrenNode.prototype.hasChild = function (childName) {
5649 return this.children_.get(childName) !== null;
5650 };
5651 /** @inheritDoc */
5652 ChildrenNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5653 util.assert(newChildNode, 'We should always be passing snapshot nodes');
5654 if (childName === '.priority') {
5655 return this.updatePriority(newChildNode);
5656 }
5657 else {
5658 var namedNode = new NamedNode(childName, newChildNode);
5659 var newChildren = void 0, newIndexMap = void 0;
5660 if (newChildNode.isEmpty()) {
5661 newChildren = this.children_.remove(childName);
5662 newIndexMap = this.indexMap_.removeFromIndexes(namedNode, this.children_);
5663 }
5664 else {
5665 newChildren = this.children_.insert(childName, newChildNode);
5666 newIndexMap = this.indexMap_.addToIndexes(namedNode, this.children_);
5667 }
5668 var newPriority = newChildren.isEmpty()
5669 ? EMPTY_NODE
5670 : this.priorityNode_;
5671 return new ChildrenNode(newChildren, newPriority, newIndexMap);
5672 }
5673 };
5674 /** @inheritDoc */
5675 ChildrenNode.prototype.updateChild = function (path, newChildNode) {
5676 var front = pathGetFront(path);
5677 if (front === null) {
5678 return newChildNode;
5679 }
5680 else {
5681 util.assert(pathGetFront(path) !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5682 var newImmediateChild = this.getImmediateChild(front).updateChild(pathPopFront(path), newChildNode);
5683 return this.updateImmediateChild(front, newImmediateChild);
5684 }
5685 };
5686 /** @inheritDoc */
5687 ChildrenNode.prototype.isEmpty = function () {
5688 return this.children_.isEmpty();
5689 };
5690 /** @inheritDoc */
5691 ChildrenNode.prototype.numChildren = function () {
5692 return this.children_.count();
5693 };
5694 /** @inheritDoc */
5695 ChildrenNode.prototype.val = function (exportFormat) {
5696 if (this.isEmpty()) {
5697 return null;
5698 }
5699 var obj = {};
5700 var numKeys = 0, maxKey = 0, allIntegerKeys = true;
5701 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5702 obj[key] = childNode.val(exportFormat);
5703 numKeys++;
5704 if (allIntegerKeys && ChildrenNode.INTEGER_REGEXP_.test(key)) {
5705 maxKey = Math.max(maxKey, Number(key));
5706 }
5707 else {
5708 allIntegerKeys = false;
5709 }
5710 });
5711 if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
5712 // convert to array.
5713 var array = [];
5714 // eslint-disable-next-line guard-for-in
5715 for (var key in obj) {
5716 array[key] = obj[key];
5717 }
5718 return array;
5719 }
5720 else {
5721 if (exportFormat && !this.getPriority().isEmpty()) {
5722 obj['.priority'] = this.getPriority().val();
5723 }
5724 return obj;
5725 }
5726 };
5727 /** @inheritDoc */
5728 ChildrenNode.prototype.hash = function () {
5729 if (this.lazyHash_ === null) {
5730 var toHash_1 = '';
5731 if (!this.getPriority().isEmpty()) {
5732 toHash_1 +=
5733 'priority:' +
5734 priorityHashText(this.getPriority().val()) +
5735 ':';
5736 }
5737 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5738 var childHash = childNode.hash();
5739 if (childHash !== '') {
5740 toHash_1 += ':' + key + ':' + childHash;
5741 }
5742 });
5743 this.lazyHash_ = toHash_1 === '' ? '' : sha1(toHash_1);
5744 }
5745 return this.lazyHash_;
5746 };
5747 /** @inheritDoc */
5748 ChildrenNode.prototype.getPredecessorChildName = function (childName, childNode, index) {
5749 var idx = this.resolveIndex_(index);
5750 if (idx) {
5751 var predecessor = idx.getPredecessorKey(new NamedNode(childName, childNode));
5752 return predecessor ? predecessor.name : null;
5753 }
5754 else {
5755 return this.children_.getPredecessorKey(childName);
5756 }
5757 };
5758 ChildrenNode.prototype.getFirstChildName = function (indexDefinition) {
5759 var idx = this.resolveIndex_(indexDefinition);
5760 if (idx) {
5761 var minKey = idx.minKey();
5762 return minKey && minKey.name;
5763 }
5764 else {
5765 return this.children_.minKey();
5766 }
5767 };
5768 ChildrenNode.prototype.getFirstChild = function (indexDefinition) {
5769 var minKey = this.getFirstChildName(indexDefinition);
5770 if (minKey) {
5771 return new NamedNode(minKey, this.children_.get(minKey));
5772 }
5773 else {
5774 return null;
5775 }
5776 };
5777 /**
5778 * Given an index, return the key name of the largest value we have, according to that index
5779 */
5780 ChildrenNode.prototype.getLastChildName = function (indexDefinition) {
5781 var idx = this.resolveIndex_(indexDefinition);
5782 if (idx) {
5783 var maxKey = idx.maxKey();
5784 return maxKey && maxKey.name;
5785 }
5786 else {
5787 return this.children_.maxKey();
5788 }
5789 };
5790 ChildrenNode.prototype.getLastChild = function (indexDefinition) {
5791 var maxKey = this.getLastChildName(indexDefinition);
5792 if (maxKey) {
5793 return new NamedNode(maxKey, this.children_.get(maxKey));
5794 }
5795 else {
5796 return null;
5797 }
5798 };
5799 ChildrenNode.prototype.forEachChild = function (index, action) {
5800 var idx = this.resolveIndex_(index);
5801 if (idx) {
5802 return idx.inorderTraversal(function (wrappedNode) {
5803 return action(wrappedNode.name, wrappedNode.node);
5804 });
5805 }
5806 else {
5807 return this.children_.inorderTraversal(action);
5808 }
5809 };
5810 ChildrenNode.prototype.getIterator = function (indexDefinition) {
5811 return this.getIteratorFrom(indexDefinition.minPost(), indexDefinition);
5812 };
5813 ChildrenNode.prototype.getIteratorFrom = function (startPost, indexDefinition) {
5814 var idx = this.resolveIndex_(indexDefinition);
5815 if (idx) {
5816 return idx.getIteratorFrom(startPost, function (key) { return key; });
5817 }
5818 else {
5819 var iterator = this.children_.getIteratorFrom(startPost.name, NamedNode.Wrap);
5820 var next = iterator.peek();
5821 while (next != null && indexDefinition.compare(next, startPost) < 0) {
5822 iterator.getNext();
5823 next = iterator.peek();
5824 }
5825 return iterator;
5826 }
5827 };
5828 ChildrenNode.prototype.getReverseIterator = function (indexDefinition) {
5829 return this.getReverseIteratorFrom(indexDefinition.maxPost(), indexDefinition);
5830 };
5831 ChildrenNode.prototype.getReverseIteratorFrom = function (endPost, indexDefinition) {
5832 var idx = this.resolveIndex_(indexDefinition);
5833 if (idx) {
5834 return idx.getReverseIteratorFrom(endPost, function (key) {
5835 return key;
5836 });
5837 }
5838 else {
5839 var iterator = this.children_.getReverseIteratorFrom(endPost.name, NamedNode.Wrap);
5840 var next = iterator.peek();
5841 while (next != null && indexDefinition.compare(next, endPost) > 0) {
5842 iterator.getNext();
5843 next = iterator.peek();
5844 }
5845 return iterator;
5846 }
5847 };
5848 ChildrenNode.prototype.compareTo = function (other) {
5849 if (this.isEmpty()) {
5850 if (other.isEmpty()) {
5851 return 0;
5852 }
5853 else {
5854 return -1;
5855 }
5856 }
5857 else if (other.isLeafNode() || other.isEmpty()) {
5858 return 1;
5859 }
5860 else if (other === MAX_NODE) {
5861 return -1;
5862 }
5863 else {
5864 // Must be another node with children.
5865 return 0;
5866 }
5867 };
5868 ChildrenNode.prototype.withIndex = function (indexDefinition) {
5869 if (indexDefinition === KEY_INDEX ||
5870 this.indexMap_.hasIndex(indexDefinition)) {
5871 return this;
5872 }
5873 else {
5874 var newIndexMap = this.indexMap_.addIndex(indexDefinition, this.children_);
5875 return new ChildrenNode(this.children_, this.priorityNode_, newIndexMap);
5876 }
5877 };
5878 ChildrenNode.prototype.isIndexed = function (index) {
5879 return index === KEY_INDEX || this.indexMap_.hasIndex(index);
5880 };
5881 ChildrenNode.prototype.equals = function (other) {
5882 if (other === this) {
5883 return true;
5884 }
5885 else if (other.isLeafNode()) {
5886 return false;
5887 }
5888 else {
5889 var otherChildrenNode = other;
5890 if (!this.getPriority().equals(otherChildrenNode.getPriority())) {
5891 return false;
5892 }
5893 else if (this.children_.count() === otherChildrenNode.children_.count()) {
5894 var thisIter = this.getIterator(PRIORITY_INDEX);
5895 var otherIter = otherChildrenNode.getIterator(PRIORITY_INDEX);
5896 var thisCurrent = thisIter.getNext();
5897 var otherCurrent = otherIter.getNext();
5898 while (thisCurrent && otherCurrent) {
5899 if (thisCurrent.name !== otherCurrent.name ||
5900 !thisCurrent.node.equals(otherCurrent.node)) {
5901 return false;
5902 }
5903 thisCurrent = thisIter.getNext();
5904 otherCurrent = otherIter.getNext();
5905 }
5906 return thisCurrent === null && otherCurrent === null;
5907 }
5908 else {
5909 return false;
5910 }
5911 }
5912 };
5913 /**
5914 * Returns a SortedMap ordered by index, or null if the default (by-key) ordering can be used
5915 * instead.
5916 *
5917 */
5918 ChildrenNode.prototype.resolveIndex_ = function (indexDefinition) {
5919 if (indexDefinition === KEY_INDEX) {
5920 return null;
5921 }
5922 else {
5923 return this.indexMap_.get(indexDefinition.toString());
5924 }
5925 };
5926 ChildrenNode.INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;
5927 return ChildrenNode;
5928}());
5929var MaxNode = /** @class */ (function (_super) {
5930 tslib.__extends(MaxNode, _super);
5931 function MaxNode() {
5932 return _super.call(this, new SortedMap(NAME_COMPARATOR), ChildrenNode.EMPTY_NODE, IndexMap.Default) || this;
5933 }
5934 MaxNode.prototype.compareTo = function (other) {
5935 if (other === this) {
5936 return 0;
5937 }
5938 else {
5939 return 1;
5940 }
5941 };
5942 MaxNode.prototype.equals = function (other) {
5943 // Not that we every compare it, but MAX_NODE is only ever equal to itself
5944 return other === this;
5945 };
5946 MaxNode.prototype.getPriority = function () {
5947 return this;
5948 };
5949 MaxNode.prototype.getImmediateChild = function (childName) {
5950 return ChildrenNode.EMPTY_NODE;
5951 };
5952 MaxNode.prototype.isEmpty = function () {
5953 return false;
5954 };
5955 return MaxNode;
5956}(ChildrenNode));
5957/**
5958 * Marker that will sort higher than any other snapshot.
5959 */
5960var MAX_NODE = new MaxNode();
5961Object.defineProperties(NamedNode, {
5962 MIN: {
5963 value: new NamedNode(MIN_NAME, ChildrenNode.EMPTY_NODE)
5964 },
5965 MAX: {
5966 value: new NamedNode(MAX_NAME, MAX_NODE)
5967 }
5968});
5969/**
5970 * Reference Extensions
5971 */
5972KeyIndex.__EMPTY_NODE = ChildrenNode.EMPTY_NODE;
5973LeafNode.__childrenNodeConstructor = ChildrenNode;
5974setMaxNode$1(MAX_NODE);
5975setMaxNode(MAX_NODE);
5976
5977/**
5978 * @license
5979 * Copyright 2017 Google LLC
5980 *
5981 * Licensed under the Apache License, Version 2.0 (the "License");
5982 * you may not use this file except in compliance with the License.
5983 * You may obtain a copy of the License at
5984 *
5985 * http://www.apache.org/licenses/LICENSE-2.0
5986 *
5987 * Unless required by applicable law or agreed to in writing, software
5988 * distributed under the License is distributed on an "AS IS" BASIS,
5989 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5990 * See the License for the specific language governing permissions and
5991 * limitations under the License.
5992 */
5993var USE_HINZE = true;
5994/**
5995 * Constructs a snapshot node representing the passed JSON and returns it.
5996 * @param json - JSON to create a node for.
5997 * @param priority - Optional priority to use. This will be ignored if the
5998 * passed JSON contains a .priority property.
5999 */
6000function nodeFromJSON(json, priority) {
6001 if (priority === void 0) { priority = null; }
6002 if (json === null) {
6003 return ChildrenNode.EMPTY_NODE;
6004 }
6005 if (typeof json === 'object' && '.priority' in json) {
6006 priority = json['.priority'];
6007 }
6008 util.assert(priority === null ||
6009 typeof priority === 'string' ||
6010 typeof priority === 'number' ||
6011 (typeof priority === 'object' && '.sv' in priority), 'Invalid priority type found: ' + typeof priority);
6012 if (typeof json === 'object' && '.value' in json && json['.value'] !== null) {
6013 json = json['.value'];
6014 }
6015 // Valid leaf nodes include non-objects or server-value wrapper objects
6016 if (typeof json !== 'object' || '.sv' in json) {
6017 var jsonLeaf = json;
6018 return new LeafNode(jsonLeaf, nodeFromJSON(priority));
6019 }
6020 if (!(json instanceof Array) && USE_HINZE) {
6021 var children_1 = [];
6022 var childrenHavePriority_1 = false;
6023 var hinzeJsonObj = json;
6024 each(hinzeJsonObj, function (key, child) {
6025 if (key.substring(0, 1) !== '.') {
6026 // Ignore metadata nodes
6027 var childNode = nodeFromJSON(child);
6028 if (!childNode.isEmpty()) {
6029 childrenHavePriority_1 =
6030 childrenHavePriority_1 || !childNode.getPriority().isEmpty();
6031 children_1.push(new NamedNode(key, childNode));
6032 }
6033 }
6034 });
6035 if (children_1.length === 0) {
6036 return ChildrenNode.EMPTY_NODE;
6037 }
6038 var childSet = buildChildSet(children_1, NAME_ONLY_COMPARATOR, function (namedNode) { return namedNode.name; }, NAME_COMPARATOR);
6039 if (childrenHavePriority_1) {
6040 var sortedChildSet = buildChildSet(children_1, PRIORITY_INDEX.getCompare());
6041 return new ChildrenNode(childSet, nodeFromJSON(priority), new IndexMap({ '.priority': sortedChildSet }, { '.priority': PRIORITY_INDEX }));
6042 }
6043 else {
6044 return new ChildrenNode(childSet, nodeFromJSON(priority), IndexMap.Default);
6045 }
6046 }
6047 else {
6048 var node_1 = ChildrenNode.EMPTY_NODE;
6049 each(json, function (key, childData) {
6050 if (util.contains(json, key)) {
6051 if (key.substring(0, 1) !== '.') {
6052 // ignore metadata nodes.
6053 var childNode = nodeFromJSON(childData);
6054 if (childNode.isLeafNode() || !childNode.isEmpty()) {
6055 node_1 = node_1.updateImmediateChild(key, childNode);
6056 }
6057 }
6058 }
6059 });
6060 return node_1.updatePriority(nodeFromJSON(priority));
6061 }
6062}
6063setNodeFromJSON(nodeFromJSON);
6064
6065/**
6066 * @license
6067 * Copyright 2017 Google LLC
6068 *
6069 * Licensed under the Apache License, Version 2.0 (the "License");
6070 * you may not use this file except in compliance with the License.
6071 * You may obtain a copy of the License at
6072 *
6073 * http://www.apache.org/licenses/LICENSE-2.0
6074 *
6075 * Unless required by applicable law or agreed to in writing, software
6076 * distributed under the License is distributed on an "AS IS" BASIS,
6077 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6078 * See the License for the specific language governing permissions and
6079 * limitations under the License.
6080 */
6081var PathIndex = /** @class */ (function (_super) {
6082 tslib.__extends(PathIndex, _super);
6083 function PathIndex(indexPath_) {
6084 var _this = _super.call(this) || this;
6085 _this.indexPath_ = indexPath_;
6086 util.assert(!pathIsEmpty(indexPath_) && pathGetFront(indexPath_) !== '.priority', "Can't create PathIndex with empty path or .priority key");
6087 return _this;
6088 }
6089 PathIndex.prototype.extractChild = function (snap) {
6090 return snap.getChild(this.indexPath_);
6091 };
6092 PathIndex.prototype.isDefinedOn = function (node) {
6093 return !node.getChild(this.indexPath_).isEmpty();
6094 };
6095 PathIndex.prototype.compare = function (a, b) {
6096 var aChild = this.extractChild(a.node);
6097 var bChild = this.extractChild(b.node);
6098 var indexCmp = aChild.compareTo(bChild);
6099 if (indexCmp === 0) {
6100 return nameCompare(a.name, b.name);
6101 }
6102 else {
6103 return indexCmp;
6104 }
6105 };
6106 PathIndex.prototype.makePost = function (indexValue, name) {
6107 var valueNode = nodeFromJSON(indexValue);
6108 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, valueNode);
6109 return new NamedNode(name, node);
6110 };
6111 PathIndex.prototype.maxPost = function () {
6112 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, MAX_NODE);
6113 return new NamedNode(MAX_NAME, node);
6114 };
6115 PathIndex.prototype.toString = function () {
6116 return pathSlice(this.indexPath_, 0).join('/');
6117 };
6118 return PathIndex;
6119}(Index));
6120
6121/**
6122 * @license
6123 * Copyright 2017 Google LLC
6124 *
6125 * Licensed under the Apache License, Version 2.0 (the "License");
6126 * you may not use this file except in compliance with the License.
6127 * You may obtain a copy of the License at
6128 *
6129 * http://www.apache.org/licenses/LICENSE-2.0
6130 *
6131 * Unless required by applicable law or agreed to in writing, software
6132 * distributed under the License is distributed on an "AS IS" BASIS,
6133 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6134 * See the License for the specific language governing permissions and
6135 * limitations under the License.
6136 */
6137var ValueIndex = /** @class */ (function (_super) {
6138 tslib.__extends(ValueIndex, _super);
6139 function ValueIndex() {
6140 return _super !== null && _super.apply(this, arguments) || this;
6141 }
6142 ValueIndex.prototype.compare = function (a, b) {
6143 var indexCmp = a.node.compareTo(b.node);
6144 if (indexCmp === 0) {
6145 return nameCompare(a.name, b.name);
6146 }
6147 else {
6148 return indexCmp;
6149 }
6150 };
6151 ValueIndex.prototype.isDefinedOn = function (node) {
6152 return true;
6153 };
6154 ValueIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
6155 return !oldNode.equals(newNode);
6156 };
6157 ValueIndex.prototype.minPost = function () {
6158 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6159 return NamedNode.MIN;
6160 };
6161 ValueIndex.prototype.maxPost = function () {
6162 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6163 return NamedNode.MAX;
6164 };
6165 ValueIndex.prototype.makePost = function (indexValue, name) {
6166 var valueNode = nodeFromJSON(indexValue);
6167 return new NamedNode(name, valueNode);
6168 };
6169 /**
6170 * @returns String representation for inclusion in a query spec
6171 */
6172 ValueIndex.prototype.toString = function () {
6173 return '.value';
6174 };
6175 return ValueIndex;
6176}(Index));
6177var VALUE_INDEX = new ValueIndex();
6178
6179/**
6180 * @license
6181 * Copyright 2017 Google LLC
6182 *
6183 * Licensed under the Apache License, Version 2.0 (the "License");
6184 * you may not use this file except in compliance with the License.
6185 * You may obtain a copy of the License at
6186 *
6187 * http://www.apache.org/licenses/LICENSE-2.0
6188 *
6189 * Unless required by applicable law or agreed to in writing, software
6190 * distributed under the License is distributed on an "AS IS" BASIS,
6191 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6192 * See the License for the specific language governing permissions and
6193 * limitations under the License.
6194 */
6195// Modeled after base64 web-safe chars, but ordered by ASCII.
6196var PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
6197var MIN_PUSH_CHAR = '-';
6198var MAX_PUSH_CHAR = 'z';
6199var MAX_KEY_LEN = 786;
6200/**
6201 * Fancy ID generator that creates 20-character string identifiers with the
6202 * following properties:
6203 *
6204 * 1. They're based on timestamp so that they sort *after* any existing ids.
6205 * 2. They contain 72-bits of random data after the timestamp so that IDs won't
6206 * collide with other clients' IDs.
6207 * 3. They sort *lexicographically* (so the timestamp is converted to characters
6208 * that will sort properly).
6209 * 4. They're monotonically increasing. Even if you generate more than one in
6210 * the same timestamp, the latter ones will sort after the former ones. We do
6211 * this by using the previous random bits but "incrementing" them by 1 (only
6212 * in the case of a timestamp collision).
6213 */
6214var nextPushId = (function () {
6215 // Timestamp of last push, used to prevent local collisions if you push twice
6216 // in one ms.
6217 var lastPushTime = 0;
6218 // We generate 72-bits of randomness which get turned into 12 characters and
6219 // appended to the timestamp to prevent collisions with other clients. We
6220 // store the last characters we generated because in the event of a collision,
6221 // we'll use those same characters except "incremented" by one.
6222 var lastRandChars = [];
6223 return function (now) {
6224 var duplicateTime = now === lastPushTime;
6225 lastPushTime = now;
6226 var i;
6227 var timeStampChars = new Array(8);
6228 for (i = 7; i >= 0; i--) {
6229 timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
6230 // NOTE: Can't use << here because javascript will convert to int and lose
6231 // the upper bits.
6232 now = Math.floor(now / 64);
6233 }
6234 util.assert(now === 0, 'Cannot push at time == 0');
6235 var id = timeStampChars.join('');
6236 if (!duplicateTime) {
6237 for (i = 0; i < 12; i++) {
6238 lastRandChars[i] = Math.floor(Math.random() * 64);
6239 }
6240 }
6241 else {
6242 // If the timestamp hasn't changed since last push, use the same random
6243 // number, except incremented by 1.
6244 for (i = 11; i >= 0 && lastRandChars[i] === 63; i--) {
6245 lastRandChars[i] = 0;
6246 }
6247 lastRandChars[i]++;
6248 }
6249 for (i = 0; i < 12; i++) {
6250 id += PUSH_CHARS.charAt(lastRandChars[i]);
6251 }
6252 util.assert(id.length === 20, 'nextPushId: Length should be 20.');
6253 return id;
6254 };
6255})();
6256var successor = function (key) {
6257 if (key === '' + INTEGER_32_MAX) {
6258 // See https://firebase.google.com/docs/database/web/lists-of-data#data-order
6259 return MIN_PUSH_CHAR;
6260 }
6261 var keyAsInt = tryParseInt(key);
6262 if (keyAsInt != null) {
6263 return '' + (keyAsInt + 1);
6264 }
6265 var next = new Array(key.length);
6266 for (var i_1 = 0; i_1 < next.length; i_1++) {
6267 next[i_1] = key.charAt(i_1);
6268 }
6269 if (next.length < MAX_KEY_LEN) {
6270 next.push(MIN_PUSH_CHAR);
6271 return next.join('');
6272 }
6273 var i = next.length - 1;
6274 while (i >= 0 && next[i] === MAX_PUSH_CHAR) {
6275 i--;
6276 }
6277 // `successor` was called on the largest possible key, so return the
6278 // MAX_NAME, which sorts larger than all keys.
6279 if (i === -1) {
6280 return MAX_NAME;
6281 }
6282 var source = next[i];
6283 var sourcePlusOne = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(source) + 1);
6284 next[i] = sourcePlusOne;
6285 return next.slice(0, i + 1).join('');
6286};
6287// `key` is assumed to be non-empty.
6288var predecessor = function (key) {
6289 if (key === '' + INTEGER_32_MIN) {
6290 return MIN_NAME;
6291 }
6292 var keyAsInt = tryParseInt(key);
6293 if (keyAsInt != null) {
6294 return '' + (keyAsInt - 1);
6295 }
6296 var next = new Array(key.length);
6297 for (var i = 0; i < next.length; i++) {
6298 next[i] = key.charAt(i);
6299 }
6300 // If `key` ends in `MIN_PUSH_CHAR`, the largest key lexicographically
6301 // smaller than `key`, is `key[0:key.length - 1]`. The next key smaller
6302 // than that, `predecessor(predecessor(key))`, is
6303 //
6304 // `key[0:key.length - 2] + (key[key.length - 1] - 1) + \
6305 // { MAX_PUSH_CHAR repeated MAX_KEY_LEN - (key.length - 1) times }
6306 //
6307 // analogous to increment/decrement for base-10 integers.
6308 //
6309 // This works because lexigographic comparison works character-by-character,
6310 // using length as a tie-breaker if one key is a prefix of the other.
6311 if (next[next.length - 1] === MIN_PUSH_CHAR) {
6312 if (next.length === 1) {
6313 // See https://firebase.google.com/docs/database/web/lists-of-data#orderbykey
6314 return '' + INTEGER_32_MAX;
6315 }
6316 delete next[next.length - 1];
6317 return next.join('');
6318 }
6319 // Replace the last character with it's immediate predecessor, and
6320 // fill the suffix of the key with MAX_PUSH_CHAR. This is the
6321 // lexicographically largest possible key smaller than `key`.
6322 next[next.length - 1] = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(next[next.length - 1]) - 1);
6323 return next.join('') + MAX_PUSH_CHAR.repeat(MAX_KEY_LEN - next.length);
6324};
6325
6326/**
6327 * @license
6328 * Copyright 2017 Google LLC
6329 *
6330 * Licensed under the Apache License, Version 2.0 (the "License");
6331 * you may not use this file except in compliance with the License.
6332 * You may obtain a copy of the License at
6333 *
6334 * http://www.apache.org/licenses/LICENSE-2.0
6335 *
6336 * Unless required by applicable law or agreed to in writing, software
6337 * distributed under the License is distributed on an "AS IS" BASIS,
6338 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6339 * See the License for the specific language governing permissions and
6340 * limitations under the License.
6341 */
6342function changeValue(snapshotNode) {
6343 return { type: "value" /* VALUE */, snapshotNode: snapshotNode };
6344}
6345function changeChildAdded(childName, snapshotNode) {
6346 return { type: "child_added" /* CHILD_ADDED */, snapshotNode: snapshotNode, childName: childName };
6347}
6348function changeChildRemoved(childName, snapshotNode) {
6349 return { type: "child_removed" /* CHILD_REMOVED */, snapshotNode: snapshotNode, childName: childName };
6350}
6351function changeChildChanged(childName, snapshotNode, oldSnap) {
6352 return {
6353 type: "child_changed" /* CHILD_CHANGED */,
6354 snapshotNode: snapshotNode,
6355 childName: childName,
6356 oldSnap: oldSnap
6357 };
6358}
6359function changeChildMoved(childName, snapshotNode) {
6360 return { type: "child_moved" /* CHILD_MOVED */, snapshotNode: snapshotNode, childName: childName };
6361}
6362
6363/**
6364 * @license
6365 * Copyright 2017 Google LLC
6366 *
6367 * Licensed under the Apache License, Version 2.0 (the "License");
6368 * you may not use this file except in compliance with the License.
6369 * You may obtain a copy of the License at
6370 *
6371 * http://www.apache.org/licenses/LICENSE-2.0
6372 *
6373 * Unless required by applicable law or agreed to in writing, software
6374 * distributed under the License is distributed on an "AS IS" BASIS,
6375 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6376 * See the License for the specific language governing permissions and
6377 * limitations under the License.
6378 */
6379/**
6380 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
6381 */
6382var IndexedFilter = /** @class */ (function () {
6383 function IndexedFilter(index_) {
6384 this.index_ = index_;
6385 }
6386 IndexedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6387 util.assert(snap.isIndexed(this.index_), 'A node must be indexed if only a child is updated');
6388 var oldChild = snap.getImmediateChild(key);
6389 // Check if anything actually changed.
6390 if (oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))) {
6391 // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
6392 // In this case, affectedPath will appear null in both the old and new snapshots. So we need
6393 // to avoid treating these cases as "nothing changed."
6394 if (oldChild.isEmpty() === newChild.isEmpty()) {
6395 // Nothing changed.
6396 // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
6397 //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
6398 return snap;
6399 }
6400 }
6401 if (optChangeAccumulator != null) {
6402 if (newChild.isEmpty()) {
6403 if (snap.hasChild(key)) {
6404 optChangeAccumulator.trackChildChange(changeChildRemoved(key, oldChild));
6405 }
6406 else {
6407 util.assert(snap.isLeafNode(), 'A child remove without an old child only makes sense on a leaf node');
6408 }
6409 }
6410 else if (oldChild.isEmpty()) {
6411 optChangeAccumulator.trackChildChange(changeChildAdded(key, newChild));
6412 }
6413 else {
6414 optChangeAccumulator.trackChildChange(changeChildChanged(key, newChild, oldChild));
6415 }
6416 }
6417 if (snap.isLeafNode() && newChild.isEmpty()) {
6418 return snap;
6419 }
6420 else {
6421 // Make sure the node is indexed
6422 return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
6423 }
6424 };
6425 IndexedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6426 if (optChangeAccumulator != null) {
6427 if (!oldSnap.isLeafNode()) {
6428 oldSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6429 if (!newSnap.hasChild(key)) {
6430 optChangeAccumulator.trackChildChange(changeChildRemoved(key, childNode));
6431 }
6432 });
6433 }
6434 if (!newSnap.isLeafNode()) {
6435 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6436 if (oldSnap.hasChild(key)) {
6437 var oldChild = oldSnap.getImmediateChild(key);
6438 if (!oldChild.equals(childNode)) {
6439 optChangeAccumulator.trackChildChange(changeChildChanged(key, childNode, oldChild));
6440 }
6441 }
6442 else {
6443 optChangeAccumulator.trackChildChange(changeChildAdded(key, childNode));
6444 }
6445 });
6446 }
6447 }
6448 return newSnap.withIndex(this.index_);
6449 };
6450 IndexedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6451 if (oldSnap.isEmpty()) {
6452 return ChildrenNode.EMPTY_NODE;
6453 }
6454 else {
6455 return oldSnap.updatePriority(newPriority);
6456 }
6457 };
6458 IndexedFilter.prototype.filtersNodes = function () {
6459 return false;
6460 };
6461 IndexedFilter.prototype.getIndexedFilter = function () {
6462 return this;
6463 };
6464 IndexedFilter.prototype.getIndex = function () {
6465 return this.index_;
6466 };
6467 return IndexedFilter;
6468}());
6469
6470/**
6471 * @license
6472 * Copyright 2017 Google LLC
6473 *
6474 * Licensed under the Apache License, Version 2.0 (the "License");
6475 * you may not use this file except in compliance with the License.
6476 * You may obtain a copy of the License at
6477 *
6478 * http://www.apache.org/licenses/LICENSE-2.0
6479 *
6480 * Unless required by applicable law or agreed to in writing, software
6481 * distributed under the License is distributed on an "AS IS" BASIS,
6482 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6483 * See the License for the specific language governing permissions and
6484 * limitations under the License.
6485 */
6486/**
6487 * Filters nodes by range and uses an IndexFilter to track any changes after filtering the node
6488 */
6489var RangedFilter = /** @class */ (function () {
6490 function RangedFilter(params) {
6491 this.indexedFilter_ = new IndexedFilter(params.getIndex());
6492 this.index_ = params.getIndex();
6493 this.startPost_ = RangedFilter.getStartPost_(params);
6494 this.endPost_ = RangedFilter.getEndPost_(params);
6495 }
6496 RangedFilter.prototype.getStartPost = function () {
6497 return this.startPost_;
6498 };
6499 RangedFilter.prototype.getEndPost = function () {
6500 return this.endPost_;
6501 };
6502 RangedFilter.prototype.matches = function (node) {
6503 return (this.index_.compare(this.getStartPost(), node) <= 0 &&
6504 this.index_.compare(node, this.getEndPost()) <= 0);
6505 };
6506 RangedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6507 if (!this.matches(new NamedNode(key, newChild))) {
6508 newChild = ChildrenNode.EMPTY_NODE;
6509 }
6510 return this.indexedFilter_.updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6511 };
6512 RangedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6513 if (newSnap.isLeafNode()) {
6514 // Make sure we have a children node with the correct index, not a leaf node;
6515 newSnap = ChildrenNode.EMPTY_NODE;
6516 }
6517 var filtered = newSnap.withIndex(this.index_);
6518 // Don't support priorities on queries
6519 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6520 var self = this;
6521 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6522 if (!self.matches(new NamedNode(key, childNode))) {
6523 filtered = filtered.updateImmediateChild(key, ChildrenNode.EMPTY_NODE);
6524 }
6525 });
6526 return this.indexedFilter_.updateFullNode(oldSnap, filtered, optChangeAccumulator);
6527 };
6528 RangedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6529 // Don't support priorities on queries
6530 return oldSnap;
6531 };
6532 RangedFilter.prototype.filtersNodes = function () {
6533 return true;
6534 };
6535 RangedFilter.prototype.getIndexedFilter = function () {
6536 return this.indexedFilter_;
6537 };
6538 RangedFilter.prototype.getIndex = function () {
6539 return this.index_;
6540 };
6541 RangedFilter.getStartPost_ = function (params) {
6542 if (params.hasStart()) {
6543 var startName = params.getIndexStartName();
6544 return params.getIndex().makePost(params.getIndexStartValue(), startName);
6545 }
6546 else {
6547 return params.getIndex().minPost();
6548 }
6549 };
6550 RangedFilter.getEndPost_ = function (params) {
6551 if (params.hasEnd()) {
6552 var endName = params.getIndexEndName();
6553 return params.getIndex().makePost(params.getIndexEndValue(), endName);
6554 }
6555 else {
6556 return params.getIndex().maxPost();
6557 }
6558 };
6559 return RangedFilter;
6560}());
6561
6562/**
6563 * @license
6564 * Copyright 2017 Google LLC
6565 *
6566 * Licensed under the Apache License, Version 2.0 (the "License");
6567 * you may not use this file except in compliance with the License.
6568 * You may obtain a copy of the License at
6569 *
6570 * http://www.apache.org/licenses/LICENSE-2.0
6571 *
6572 * Unless required by applicable law or agreed to in writing, software
6573 * distributed under the License is distributed on an "AS IS" BASIS,
6574 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6575 * See the License for the specific language governing permissions and
6576 * limitations under the License.
6577 */
6578/**
6579 * Applies a limit and a range to a node and uses RangedFilter to do the heavy lifting where possible
6580 */
6581var LimitedFilter = /** @class */ (function () {
6582 function LimitedFilter(params) {
6583 this.rangedFilter_ = new RangedFilter(params);
6584 this.index_ = params.getIndex();
6585 this.limit_ = params.getLimit();
6586 this.reverse_ = !params.isViewFromLeft();
6587 }
6588 LimitedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6589 if (!this.rangedFilter_.matches(new NamedNode(key, newChild))) {
6590 newChild = ChildrenNode.EMPTY_NODE;
6591 }
6592 if (snap.getImmediateChild(key).equals(newChild)) {
6593 // No change
6594 return snap;
6595 }
6596 else if (snap.numChildren() < this.limit_) {
6597 return this.rangedFilter_
6598 .getIndexedFilter()
6599 .updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6600 }
6601 else {
6602 return this.fullLimitUpdateChild_(snap, key, newChild, source, optChangeAccumulator);
6603 }
6604 };
6605 LimitedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6606 var filtered;
6607 if (newSnap.isLeafNode() || newSnap.isEmpty()) {
6608 // Make sure we have a children node with the correct index, not a leaf node;
6609 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6610 }
6611 else {
6612 if (this.limit_ * 2 < newSnap.numChildren() &&
6613 newSnap.isIndexed(this.index_)) {
6614 // Easier to build up a snapshot, since what we're given has more than twice the elements we want
6615 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6616 // anchor to the startPost, endPost, or last element as appropriate
6617 var iterator = void 0;
6618 if (this.reverse_) {
6619 iterator = newSnap.getReverseIteratorFrom(this.rangedFilter_.getEndPost(), this.index_);
6620 }
6621 else {
6622 iterator = newSnap.getIteratorFrom(this.rangedFilter_.getStartPost(), this.index_);
6623 }
6624 var count = 0;
6625 while (iterator.hasNext() && count < this.limit_) {
6626 var next = iterator.getNext();
6627 var inRange = void 0;
6628 if (this.reverse_) {
6629 inRange =
6630 this.index_.compare(this.rangedFilter_.getStartPost(), next) <= 0;
6631 }
6632 else {
6633 inRange =
6634 this.index_.compare(next, this.rangedFilter_.getEndPost()) <= 0;
6635 }
6636 if (inRange) {
6637 filtered = filtered.updateImmediateChild(next.name, next.node);
6638 count++;
6639 }
6640 else {
6641 // if we have reached the end post, we cannot keep adding elemments
6642 break;
6643 }
6644 }
6645 }
6646 else {
6647 // The snap contains less than twice the limit. Faster to delete from the snap than build up a new one
6648 filtered = newSnap.withIndex(this.index_);
6649 // Don't support priorities on queries
6650 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6651 var startPost = void 0;
6652 var endPost = void 0;
6653 var cmp = void 0;
6654 var iterator = void 0;
6655 if (this.reverse_) {
6656 iterator = filtered.getReverseIterator(this.index_);
6657 startPost = this.rangedFilter_.getEndPost();
6658 endPost = this.rangedFilter_.getStartPost();
6659 var indexCompare_1 = this.index_.getCompare();
6660 cmp = function (a, b) { return indexCompare_1(b, a); };
6661 }
6662 else {
6663 iterator = filtered.getIterator(this.index_);
6664 startPost = this.rangedFilter_.getStartPost();
6665 endPost = this.rangedFilter_.getEndPost();
6666 cmp = this.index_.getCompare();
6667 }
6668 var count = 0;
6669 var foundStartPost = false;
6670 while (iterator.hasNext()) {
6671 var next = iterator.getNext();
6672 if (!foundStartPost && cmp(startPost, next) <= 0) {
6673 // start adding
6674 foundStartPost = true;
6675 }
6676 var inRange = foundStartPost && count < this.limit_ && cmp(next, endPost) <= 0;
6677 if (inRange) {
6678 count++;
6679 }
6680 else {
6681 filtered = filtered.updateImmediateChild(next.name, ChildrenNode.EMPTY_NODE);
6682 }
6683 }
6684 }
6685 }
6686 return this.rangedFilter_
6687 .getIndexedFilter()
6688 .updateFullNode(oldSnap, filtered, optChangeAccumulator);
6689 };
6690 LimitedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6691 // Don't support priorities on queries
6692 return oldSnap;
6693 };
6694 LimitedFilter.prototype.filtersNodes = function () {
6695 return true;
6696 };
6697 LimitedFilter.prototype.getIndexedFilter = function () {
6698 return this.rangedFilter_.getIndexedFilter();
6699 };
6700 LimitedFilter.prototype.getIndex = function () {
6701 return this.index_;
6702 };
6703 LimitedFilter.prototype.fullLimitUpdateChild_ = function (snap, childKey, childSnap, source, changeAccumulator) {
6704 // TODO: rename all cache stuff etc to general snap terminology
6705 var cmp;
6706 if (this.reverse_) {
6707 var indexCmp_1 = this.index_.getCompare();
6708 cmp = function (a, b) { return indexCmp_1(b, a); };
6709 }
6710 else {
6711 cmp = this.index_.getCompare();
6712 }
6713 var oldEventCache = snap;
6714 util.assert(oldEventCache.numChildren() === this.limit_, '');
6715 var newChildNamedNode = new NamedNode(childKey, childSnap);
6716 var windowBoundary = this.reverse_
6717 ? oldEventCache.getFirstChild(this.index_)
6718 : oldEventCache.getLastChild(this.index_);
6719 var inRange = this.rangedFilter_.matches(newChildNamedNode);
6720 if (oldEventCache.hasChild(childKey)) {
6721 var oldChildSnap = oldEventCache.getImmediateChild(childKey);
6722 var nextChild = source.getChildAfterChild(this.index_, windowBoundary, this.reverse_);
6723 while (nextChild != null &&
6724 (nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))) {
6725 // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
6726 // been applied to the limited filter yet. Ignore this next child which will be updated later in
6727 // the limited filter...
6728 nextChild = source.getChildAfterChild(this.index_, nextChild, this.reverse_);
6729 }
6730 var compareNext = nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
6731 var remainsInWindow = inRange && !childSnap.isEmpty() && compareNext >= 0;
6732 if (remainsInWindow) {
6733 if (changeAccumulator != null) {
6734 changeAccumulator.trackChildChange(changeChildChanged(childKey, childSnap, oldChildSnap));
6735 }
6736 return oldEventCache.updateImmediateChild(childKey, childSnap);
6737 }
6738 else {
6739 if (changeAccumulator != null) {
6740 changeAccumulator.trackChildChange(changeChildRemoved(childKey, oldChildSnap));
6741 }
6742 var newEventCache = oldEventCache.updateImmediateChild(childKey, ChildrenNode.EMPTY_NODE);
6743 var nextChildInRange = nextChild != null && this.rangedFilter_.matches(nextChild);
6744 if (nextChildInRange) {
6745 if (changeAccumulator != null) {
6746 changeAccumulator.trackChildChange(changeChildAdded(nextChild.name, nextChild.node));
6747 }
6748 return newEventCache.updateImmediateChild(nextChild.name, nextChild.node);
6749 }
6750 else {
6751 return newEventCache;
6752 }
6753 }
6754 }
6755 else if (childSnap.isEmpty()) {
6756 // we're deleting a node, but it was not in the window, so ignore it
6757 return snap;
6758 }
6759 else if (inRange) {
6760 if (cmp(windowBoundary, newChildNamedNode) >= 0) {
6761 if (changeAccumulator != null) {
6762 changeAccumulator.trackChildChange(changeChildRemoved(windowBoundary.name, windowBoundary.node));
6763 changeAccumulator.trackChildChange(changeChildAdded(childKey, childSnap));
6764 }
6765 return oldEventCache
6766 .updateImmediateChild(childKey, childSnap)
6767 .updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
6768 }
6769 else {
6770 return snap;
6771 }
6772 }
6773 else {
6774 return snap;
6775 }
6776 };
6777 return LimitedFilter;
6778}());
6779
6780/**
6781 * @license
6782 * Copyright 2017 Google LLC
6783 *
6784 * Licensed under the Apache License, Version 2.0 (the "License");
6785 * you may not use this file except in compliance with the License.
6786 * You may obtain a copy of the License at
6787 *
6788 * http://www.apache.org/licenses/LICENSE-2.0
6789 *
6790 * Unless required by applicable law or agreed to in writing, software
6791 * distributed under the License is distributed on an "AS IS" BASIS,
6792 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6793 * See the License for the specific language governing permissions and
6794 * limitations under the License.
6795 */
6796/**
6797 * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a
6798 * range to be returned for a particular location. It is assumed that validation of parameters is done at the
6799 * user-facing API level, so it is not done here.
6800 *
6801 * @internal
6802 */
6803var QueryParams = /** @class */ (function () {
6804 function QueryParams() {
6805 this.limitSet_ = false;
6806 this.startSet_ = false;
6807 this.startNameSet_ = false;
6808 this.startAfterSet_ = false;
6809 this.endSet_ = false;
6810 this.endNameSet_ = false;
6811 this.endBeforeSet_ = false;
6812 this.limit_ = 0;
6813 this.viewFrom_ = '';
6814 this.indexStartValue_ = null;
6815 this.indexStartName_ = '';
6816 this.indexEndValue_ = null;
6817 this.indexEndName_ = '';
6818 this.index_ = PRIORITY_INDEX;
6819 }
6820 QueryParams.prototype.hasStart = function () {
6821 return this.startSet_;
6822 };
6823 QueryParams.prototype.hasStartAfter = function () {
6824 return this.startAfterSet_;
6825 };
6826 QueryParams.prototype.hasEndBefore = function () {
6827 return this.endBeforeSet_;
6828 };
6829 /**
6830 * @returns True if it would return from left.
6831 */
6832 QueryParams.prototype.isViewFromLeft = function () {
6833 if (this.viewFrom_ === '') {
6834 // limit(), rather than limitToFirst or limitToLast was called.
6835 // This means that only one of startSet_ and endSet_ is true. Use them
6836 // to calculate which side of the view to anchor to. If neither is set,
6837 // anchor to the end.
6838 return this.startSet_;
6839 }
6840 else {
6841 return this.viewFrom_ === "l" /* VIEW_FROM_LEFT */;
6842 }
6843 };
6844 /**
6845 * Only valid to call if hasStart() returns true
6846 */
6847 QueryParams.prototype.getIndexStartValue = function () {
6848 util.assert(this.startSet_, 'Only valid if start has been set');
6849 return this.indexStartValue_;
6850 };
6851 /**
6852 * Only valid to call if hasStart() returns true.
6853 * Returns the starting key name for the range defined by these query parameters
6854 */
6855 QueryParams.prototype.getIndexStartName = function () {
6856 util.assert(this.startSet_, 'Only valid if start has been set');
6857 if (this.startNameSet_) {
6858 return this.indexStartName_;
6859 }
6860 else {
6861 return MIN_NAME;
6862 }
6863 };
6864 QueryParams.prototype.hasEnd = function () {
6865 return this.endSet_;
6866 };
6867 /**
6868 * Only valid to call if hasEnd() returns true.
6869 */
6870 QueryParams.prototype.getIndexEndValue = function () {
6871 util.assert(this.endSet_, 'Only valid if end has been set');
6872 return this.indexEndValue_;
6873 };
6874 /**
6875 * Only valid to call if hasEnd() returns true.
6876 * Returns the end key name for the range defined by these query parameters
6877 */
6878 QueryParams.prototype.getIndexEndName = function () {
6879 util.assert(this.endSet_, 'Only valid if end has been set');
6880 if (this.endNameSet_) {
6881 return this.indexEndName_;
6882 }
6883 else {
6884 return MAX_NAME;
6885 }
6886 };
6887 QueryParams.prototype.hasLimit = function () {
6888 return this.limitSet_;
6889 };
6890 /**
6891 * @returns True if a limit has been set and it has been explicitly anchored
6892 */
6893 QueryParams.prototype.hasAnchoredLimit = function () {
6894 return this.limitSet_ && this.viewFrom_ !== '';
6895 };
6896 /**
6897 * Only valid to call if hasLimit() returns true
6898 */
6899 QueryParams.prototype.getLimit = function () {
6900 util.assert(this.limitSet_, 'Only valid if limit has been set');
6901 return this.limit_;
6902 };
6903 QueryParams.prototype.getIndex = function () {
6904 return this.index_;
6905 };
6906 QueryParams.prototype.loadsAllData = function () {
6907 return !(this.startSet_ || this.endSet_ || this.limitSet_);
6908 };
6909 QueryParams.prototype.isDefault = function () {
6910 return this.loadsAllData() && this.index_ === PRIORITY_INDEX;
6911 };
6912 QueryParams.prototype.copy = function () {
6913 var copy = new QueryParams();
6914 copy.limitSet_ = this.limitSet_;
6915 copy.limit_ = this.limit_;
6916 copy.startSet_ = this.startSet_;
6917 copy.indexStartValue_ = this.indexStartValue_;
6918 copy.startNameSet_ = this.startNameSet_;
6919 copy.indexStartName_ = this.indexStartName_;
6920 copy.endSet_ = this.endSet_;
6921 copy.indexEndValue_ = this.indexEndValue_;
6922 copy.endNameSet_ = this.endNameSet_;
6923 copy.indexEndName_ = this.indexEndName_;
6924 copy.index_ = this.index_;
6925 copy.viewFrom_ = this.viewFrom_;
6926 return copy;
6927 };
6928 return QueryParams;
6929}());
6930function queryParamsGetNodeFilter(queryParams) {
6931 if (queryParams.loadsAllData()) {
6932 return new IndexedFilter(queryParams.getIndex());
6933 }
6934 else if (queryParams.hasLimit()) {
6935 return new LimitedFilter(queryParams);
6936 }
6937 else {
6938 return new RangedFilter(queryParams);
6939 }
6940}
6941function queryParamsLimitToFirst(queryParams, newLimit) {
6942 var newParams = queryParams.copy();
6943 newParams.limitSet_ = true;
6944 newParams.limit_ = newLimit;
6945 newParams.viewFrom_ = "l" /* VIEW_FROM_LEFT */;
6946 return newParams;
6947}
6948function queryParamsLimitToLast(queryParams, newLimit) {
6949 var newParams = queryParams.copy();
6950 newParams.limitSet_ = true;
6951 newParams.limit_ = newLimit;
6952 newParams.viewFrom_ = "r" /* VIEW_FROM_RIGHT */;
6953 return newParams;
6954}
6955function queryParamsStartAt(queryParams, indexValue, key) {
6956 var newParams = queryParams.copy();
6957 newParams.startSet_ = true;
6958 if (indexValue === undefined) {
6959 indexValue = null;
6960 }
6961 newParams.indexStartValue_ = indexValue;
6962 if (key != null) {
6963 newParams.startNameSet_ = true;
6964 newParams.indexStartName_ = key;
6965 }
6966 else {
6967 newParams.startNameSet_ = false;
6968 newParams.indexStartName_ = '';
6969 }
6970 return newParams;
6971}
6972function queryParamsStartAfter(queryParams, indexValue, key) {
6973 var params;
6974 if (queryParams.index_ === KEY_INDEX) {
6975 if (typeof indexValue === 'string') {
6976 indexValue = successor(indexValue);
6977 }
6978 params = queryParamsStartAt(queryParams, indexValue, key);
6979 }
6980 else {
6981 var childKey = void 0;
6982 if (key == null) {
6983 childKey = MAX_NAME;
6984 }
6985 else {
6986 childKey = successor(key);
6987 }
6988 params = queryParamsStartAt(queryParams, indexValue, childKey);
6989 }
6990 params.startAfterSet_ = true;
6991 return params;
6992}
6993function queryParamsEndAt(queryParams, indexValue, key) {
6994 var newParams = queryParams.copy();
6995 newParams.endSet_ = true;
6996 if (indexValue === undefined) {
6997 indexValue = null;
6998 }
6999 newParams.indexEndValue_ = indexValue;
7000 if (key !== undefined) {
7001 newParams.endNameSet_ = true;
7002 newParams.indexEndName_ = key;
7003 }
7004 else {
7005 newParams.endNameSet_ = false;
7006 newParams.indexEndName_ = '';
7007 }
7008 return newParams;
7009}
7010function queryParamsEndBefore(queryParams, indexValue, key) {
7011 var childKey;
7012 var params;
7013 if (queryParams.index_ === KEY_INDEX) {
7014 if (typeof indexValue === 'string') {
7015 indexValue = predecessor(indexValue);
7016 }
7017 params = queryParamsEndAt(queryParams, indexValue, key);
7018 }
7019 else {
7020 if (key == null) {
7021 childKey = MIN_NAME;
7022 }
7023 else {
7024 childKey = predecessor(key);
7025 }
7026 params = queryParamsEndAt(queryParams, indexValue, childKey);
7027 }
7028 params.endBeforeSet_ = true;
7029 return params;
7030}
7031function queryParamsOrderBy(queryParams, index) {
7032 var newParams = queryParams.copy();
7033 newParams.index_ = index;
7034 return newParams;
7035}
7036/**
7037 * Returns a set of REST query string parameters representing this query.
7038 *
7039 * @returns query string parameters
7040 */
7041function queryParamsToRestQueryStringParameters(queryParams) {
7042 var qs = {};
7043 if (queryParams.isDefault()) {
7044 return qs;
7045 }
7046 var orderBy;
7047 if (queryParams.index_ === PRIORITY_INDEX) {
7048 orderBy = "$priority" /* PRIORITY_INDEX */;
7049 }
7050 else if (queryParams.index_ === VALUE_INDEX) {
7051 orderBy = "$value" /* VALUE_INDEX */;
7052 }
7053 else if (queryParams.index_ === KEY_INDEX) {
7054 orderBy = "$key" /* KEY_INDEX */;
7055 }
7056 else {
7057 util.assert(queryParams.index_ instanceof PathIndex, 'Unrecognized index type!');
7058 orderBy = queryParams.index_.toString();
7059 }
7060 qs["orderBy" /* ORDER_BY */] = util.stringify(orderBy);
7061 if (queryParams.startSet_) {
7062 qs["startAt" /* START_AT */] = util.stringify(queryParams.indexStartValue_);
7063 if (queryParams.startNameSet_) {
7064 qs["startAt" /* START_AT */] +=
7065 ',' + util.stringify(queryParams.indexStartName_);
7066 }
7067 }
7068 if (queryParams.endSet_) {
7069 qs["endAt" /* END_AT */] = util.stringify(queryParams.indexEndValue_);
7070 if (queryParams.endNameSet_) {
7071 qs["endAt" /* END_AT */] +=
7072 ',' + util.stringify(queryParams.indexEndName_);
7073 }
7074 }
7075 if (queryParams.limitSet_) {
7076 if (queryParams.isViewFromLeft()) {
7077 qs["limitToFirst" /* LIMIT_TO_FIRST */] = queryParams.limit_;
7078 }
7079 else {
7080 qs["limitToLast" /* LIMIT_TO_LAST */] = queryParams.limit_;
7081 }
7082 }
7083 return qs;
7084}
7085function queryParamsGetQueryObject(queryParams) {
7086 var obj = {};
7087 if (queryParams.startSet_) {
7088 obj["sp" /* INDEX_START_VALUE */] =
7089 queryParams.indexStartValue_;
7090 if (queryParams.startNameSet_) {
7091 obj["sn" /* INDEX_START_NAME */] =
7092 queryParams.indexStartName_;
7093 }
7094 }
7095 if (queryParams.endSet_) {
7096 obj["ep" /* INDEX_END_VALUE */] = queryParams.indexEndValue_;
7097 if (queryParams.endNameSet_) {
7098 obj["en" /* INDEX_END_NAME */] = queryParams.indexEndName_;
7099 }
7100 }
7101 if (queryParams.limitSet_) {
7102 obj["l" /* LIMIT */] = queryParams.limit_;
7103 var viewFrom = queryParams.viewFrom_;
7104 if (viewFrom === '') {
7105 if (queryParams.isViewFromLeft()) {
7106 viewFrom = "l" /* VIEW_FROM_LEFT */;
7107 }
7108 else {
7109 viewFrom = "r" /* VIEW_FROM_RIGHT */;
7110 }
7111 }
7112 obj["vf" /* VIEW_FROM */] = viewFrom;
7113 }
7114 // For now, priority index is the default, so we only specify if it's some other index
7115 if (queryParams.index_ !== PRIORITY_INDEX) {
7116 obj["i" /* INDEX */] = queryParams.index_.toString();
7117 }
7118 return obj;
7119}
7120
7121/**
7122 * @license
7123 * Copyright 2017 Google LLC
7124 *
7125 * Licensed under the Apache License, Version 2.0 (the "License");
7126 * you may not use this file except in compliance with the License.
7127 * You may obtain a copy of the License at
7128 *
7129 * http://www.apache.org/licenses/LICENSE-2.0
7130 *
7131 * Unless required by applicable law or agreed to in writing, software
7132 * distributed under the License is distributed on an "AS IS" BASIS,
7133 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7134 * See the License for the specific language governing permissions and
7135 * limitations under the License.
7136 */
7137/**
7138 * An implementation of ServerActions that communicates with the server via REST requests.
7139 * This is mostly useful for compatibility with crawlers, where we don't want to spin up a full
7140 * persistent connection (using WebSockets or long-polling)
7141 */
7142var ReadonlyRestClient = /** @class */ (function (_super) {
7143 tslib.__extends(ReadonlyRestClient, _super);
7144 /**
7145 * @param repoInfo_ - Data about the namespace we are connecting to
7146 * @param onDataUpdate_ - A callback for new data from the server
7147 */
7148 function ReadonlyRestClient(repoInfo_, onDataUpdate_, authTokenProvider_, appCheckTokenProvider_) {
7149 var _this = _super.call(this) || this;
7150 _this.repoInfo_ = repoInfo_;
7151 _this.onDataUpdate_ = onDataUpdate_;
7152 _this.authTokenProvider_ = authTokenProvider_;
7153 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
7154 /** @private {function(...[*])} */
7155 _this.log_ = logWrapper('p:rest:');
7156 /**
7157 * We don't actually need to track listens, except to prevent us calling an onComplete for a listen
7158 * that's been removed. :-/
7159 */
7160 _this.listens_ = {};
7161 return _this;
7162 }
7163 ReadonlyRestClient.prototype.reportStats = function (stats) {
7164 throw new Error('Method not implemented.');
7165 };
7166 ReadonlyRestClient.getListenId_ = function (query, tag) {
7167 if (tag !== undefined) {
7168 return 'tag$' + tag;
7169 }
7170 else {
7171 util.assert(query._queryParams.isDefault(), "should have a tag if it's not a default query.");
7172 return query._path.toString();
7173 }
7174 };
7175 /** @inheritDoc */
7176 ReadonlyRestClient.prototype.listen = function (query, currentHashFn, tag, onComplete) {
7177 var _this = this;
7178 var pathString = query._path.toString();
7179 this.log_('Listen called for ' + pathString + ' ' + query._queryIdentifier);
7180 // Mark this listener so we can tell if it's removed.
7181 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7182 var thisListen = {};
7183 this.listens_[listenId] = thisListen;
7184 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7185 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7186 var data = result;
7187 if (error === 404) {
7188 data = null;
7189 error = null;
7190 }
7191 if (error === null) {
7192 _this.onDataUpdate_(pathString, data, /*isMerge=*/ false, tag);
7193 }
7194 if (util.safeGet(_this.listens_, listenId) === thisListen) {
7195 var status_1;
7196 if (!error) {
7197 status_1 = 'ok';
7198 }
7199 else if (error === 401) {
7200 status_1 = 'permission_denied';
7201 }
7202 else {
7203 status_1 = 'rest_error:' + error;
7204 }
7205 onComplete(status_1, null);
7206 }
7207 });
7208 };
7209 /** @inheritDoc */
7210 ReadonlyRestClient.prototype.unlisten = function (query, tag) {
7211 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7212 delete this.listens_[listenId];
7213 };
7214 ReadonlyRestClient.prototype.get = function (query) {
7215 var _this = this;
7216 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7217 var pathString = query._path.toString();
7218 var deferred = new util.Deferred();
7219 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7220 var data = result;
7221 if (error === 404) {
7222 data = null;
7223 error = null;
7224 }
7225 if (error === null) {
7226 _this.onDataUpdate_(pathString, data,
7227 /*isMerge=*/ false,
7228 /*tag=*/ null);
7229 deferred.resolve(data);
7230 }
7231 else {
7232 deferred.reject(new Error(data));
7233 }
7234 });
7235 return deferred.promise;
7236 };
7237 /** @inheritDoc */
7238 ReadonlyRestClient.prototype.refreshAuthToken = function (token) {
7239 // no-op since we just always call getToken.
7240 };
7241 /**
7242 * Performs a REST request to the given path, with the provided query string parameters,
7243 * and any auth credentials we have.
7244 */
7245 ReadonlyRestClient.prototype.restRequest_ = function (pathString, queryStringParameters, callback) {
7246 var _this = this;
7247 if (queryStringParameters === void 0) { queryStringParameters = {}; }
7248 queryStringParameters['format'] = 'export';
7249 return Promise.all([
7250 this.authTokenProvider_.getToken(/*forceRefresh=*/ false),
7251 this.appCheckTokenProvider_.getToken(/*forceRefresh=*/ false)
7252 ]).then(function (_a) {
7253 var _b = tslib.__read(_a, 2), authToken = _b[0], appCheckToken = _b[1];
7254 if (authToken && authToken.accessToken) {
7255 queryStringParameters['auth'] = authToken.accessToken;
7256 }
7257 if (appCheckToken && appCheckToken.token) {
7258 queryStringParameters['ac'] = appCheckToken.token;
7259 }
7260 var url = (_this.repoInfo_.secure ? 'https://' : 'http://') +
7261 _this.repoInfo_.host +
7262 pathString +
7263 '?' +
7264 'ns=' +
7265 _this.repoInfo_.namespace +
7266 util.querystring(queryStringParameters);
7267 _this.log_('Sending REST request for ' + url);
7268 var xhr = new XMLHttpRequest();
7269 xhr.onreadystatechange = function () {
7270 if (callback && xhr.readyState === 4) {
7271 _this.log_('REST Response for ' + url + ' received. status:', xhr.status, 'response:', xhr.responseText);
7272 var res = null;
7273 if (xhr.status >= 200 && xhr.status < 300) {
7274 try {
7275 res = util.jsonEval(xhr.responseText);
7276 }
7277 catch (e) {
7278 warn('Failed to parse JSON response for ' +
7279 url +
7280 ': ' +
7281 xhr.responseText);
7282 }
7283 callback(null, res);
7284 }
7285 else {
7286 // 401 and 404 are expected.
7287 if (xhr.status !== 401 && xhr.status !== 404) {
7288 warn('Got unsuccessful REST response for ' +
7289 url +
7290 ' Status: ' +
7291 xhr.status);
7292 }
7293 callback(xhr.status);
7294 }
7295 callback = null;
7296 }
7297 };
7298 xhr.open('GET', url, /*asynchronous=*/ true);
7299 xhr.send();
7300 });
7301 };
7302 return ReadonlyRestClient;
7303}(ServerActions));
7304
7305/**
7306 * @license
7307 * Copyright 2017 Google LLC
7308 *
7309 * Licensed under the Apache License, Version 2.0 (the "License");
7310 * you may not use this file except in compliance with the License.
7311 * You may obtain a copy of the License at
7312 *
7313 * http://www.apache.org/licenses/LICENSE-2.0
7314 *
7315 * Unless required by applicable law or agreed to in writing, software
7316 * distributed under the License is distributed on an "AS IS" BASIS,
7317 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7318 * See the License for the specific language governing permissions and
7319 * limitations under the License.
7320 */
7321/**
7322 * Mutable object which basically just stores a reference to the "latest" immutable snapshot.
7323 */
7324var SnapshotHolder = /** @class */ (function () {
7325 function SnapshotHolder() {
7326 this.rootNode_ = ChildrenNode.EMPTY_NODE;
7327 }
7328 SnapshotHolder.prototype.getNode = function (path) {
7329 return this.rootNode_.getChild(path);
7330 };
7331 SnapshotHolder.prototype.updateSnapshot = function (path, newSnapshotNode) {
7332 this.rootNode_ = this.rootNode_.updateChild(path, newSnapshotNode);
7333 };
7334 return SnapshotHolder;
7335}());
7336
7337/**
7338 * @license
7339 * Copyright 2017 Google LLC
7340 *
7341 * Licensed under the Apache License, Version 2.0 (the "License");
7342 * you may not use this file except in compliance with the License.
7343 * You may obtain a copy of the License at
7344 *
7345 * http://www.apache.org/licenses/LICENSE-2.0
7346 *
7347 * Unless required by applicable law or agreed to in writing, software
7348 * distributed under the License is distributed on an "AS IS" BASIS,
7349 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7350 * See the License for the specific language governing permissions and
7351 * limitations under the License.
7352 */
7353function newSparseSnapshotTree() {
7354 return {
7355 value: null,
7356 children: new Map()
7357 };
7358}
7359/**
7360 * Stores the given node at the specified path. If there is already a node
7361 * at a shallower path, it merges the new data into that snapshot node.
7362 *
7363 * @param path - Path to look up snapshot for.
7364 * @param data - The new data, or null.
7365 */
7366function sparseSnapshotTreeRemember(sparseSnapshotTree, path, data) {
7367 if (pathIsEmpty(path)) {
7368 sparseSnapshotTree.value = data;
7369 sparseSnapshotTree.children.clear();
7370 }
7371 else if (sparseSnapshotTree.value !== null) {
7372 sparseSnapshotTree.value = sparseSnapshotTree.value.updateChild(path, data);
7373 }
7374 else {
7375 var childKey = pathGetFront(path);
7376 if (!sparseSnapshotTree.children.has(childKey)) {
7377 sparseSnapshotTree.children.set(childKey, newSparseSnapshotTree());
7378 }
7379 var child = sparseSnapshotTree.children.get(childKey);
7380 path = pathPopFront(path);
7381 sparseSnapshotTreeRemember(child, path, data);
7382 }
7383}
7384/**
7385 * Purge the data at path from the cache.
7386 *
7387 * @param path - Path to look up snapshot for.
7388 * @returns True if this node should now be removed.
7389 */
7390function sparseSnapshotTreeForget(sparseSnapshotTree, path) {
7391 if (pathIsEmpty(path)) {
7392 sparseSnapshotTree.value = null;
7393 sparseSnapshotTree.children.clear();
7394 return true;
7395 }
7396 else {
7397 if (sparseSnapshotTree.value !== null) {
7398 if (sparseSnapshotTree.value.isLeafNode()) {
7399 // We're trying to forget a node that doesn't exist
7400 return false;
7401 }
7402 else {
7403 var value = sparseSnapshotTree.value;
7404 sparseSnapshotTree.value = null;
7405 value.forEachChild(PRIORITY_INDEX, function (key, tree) {
7406 sparseSnapshotTreeRemember(sparseSnapshotTree, new Path(key), tree);
7407 });
7408 return sparseSnapshotTreeForget(sparseSnapshotTree, path);
7409 }
7410 }
7411 else if (sparseSnapshotTree.children.size > 0) {
7412 var childKey = pathGetFront(path);
7413 path = pathPopFront(path);
7414 if (sparseSnapshotTree.children.has(childKey)) {
7415 var safeToRemove = sparseSnapshotTreeForget(sparseSnapshotTree.children.get(childKey), path);
7416 if (safeToRemove) {
7417 sparseSnapshotTree.children.delete(childKey);
7418 }
7419 }
7420 return sparseSnapshotTree.children.size === 0;
7421 }
7422 else {
7423 return true;
7424 }
7425 }
7426}
7427/**
7428 * Recursively iterates through all of the stored tree and calls the
7429 * callback on each one.
7430 *
7431 * @param prefixPath - Path to look up node for.
7432 * @param func - The function to invoke for each tree.
7433 */
7434function sparseSnapshotTreeForEachTree(sparseSnapshotTree, prefixPath, func) {
7435 if (sparseSnapshotTree.value !== null) {
7436 func(prefixPath, sparseSnapshotTree.value);
7437 }
7438 else {
7439 sparseSnapshotTreeForEachChild(sparseSnapshotTree, function (key, tree) {
7440 var path = new Path(prefixPath.toString() + '/' + key);
7441 sparseSnapshotTreeForEachTree(tree, path, func);
7442 });
7443 }
7444}
7445/**
7446 * Iterates through each immediate child and triggers the callback.
7447 * Only seems to be used in tests.
7448 *
7449 * @param func - The function to invoke for each child.
7450 */
7451function sparseSnapshotTreeForEachChild(sparseSnapshotTree, func) {
7452 sparseSnapshotTree.children.forEach(function (tree, key) {
7453 func(key, tree);
7454 });
7455}
7456
7457/**
7458 * @license
7459 * Copyright 2017 Google LLC
7460 *
7461 * Licensed under the Apache License, Version 2.0 (the "License");
7462 * you may not use this file except in compliance with the License.
7463 * You may obtain a copy of the License at
7464 *
7465 * http://www.apache.org/licenses/LICENSE-2.0
7466 *
7467 * Unless required by applicable law or agreed to in writing, software
7468 * distributed under the License is distributed on an "AS IS" BASIS,
7469 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7470 * See the License for the specific language governing permissions and
7471 * limitations under the License.
7472 */
7473/**
7474 * Returns the delta from the previous call to get stats.
7475 *
7476 * @param collection_ - The collection to "listen" to.
7477 */
7478var StatsListener = /** @class */ (function () {
7479 function StatsListener(collection_) {
7480 this.collection_ = collection_;
7481 this.last_ = null;
7482 }
7483 StatsListener.prototype.get = function () {
7484 var newStats = this.collection_.get();
7485 var delta = tslib.__assign({}, newStats);
7486 if (this.last_) {
7487 each(this.last_, function (stat, value) {
7488 delta[stat] = delta[stat] - value;
7489 });
7490 }
7491 this.last_ = newStats;
7492 return delta;
7493 };
7494 return StatsListener;
7495}());
7496
7497/**
7498 * @license
7499 * Copyright 2017 Google LLC
7500 *
7501 * Licensed under the Apache License, Version 2.0 (the "License");
7502 * you may not use this file except in compliance with the License.
7503 * You may obtain a copy of the License at
7504 *
7505 * http://www.apache.org/licenses/LICENSE-2.0
7506 *
7507 * Unless required by applicable law or agreed to in writing, software
7508 * distributed under the License is distributed on an "AS IS" BASIS,
7509 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7510 * See the License for the specific language governing permissions and
7511 * limitations under the License.
7512 */
7513// Assuming some apps may have a short amount of time on page, and a bulk of firebase operations probably
7514// happen on page load, we try to report our first set of stats pretty quickly, but we wait at least 10
7515// seconds to try to ensure the Firebase connection is established / settled.
7516var FIRST_STATS_MIN_TIME = 10 * 1000;
7517var FIRST_STATS_MAX_TIME = 30 * 1000;
7518// We'll continue to report stats on average every 5 minutes.
7519var REPORT_STATS_INTERVAL = 5 * 60 * 1000;
7520var StatsReporter = /** @class */ (function () {
7521 function StatsReporter(collection, server_) {
7522 this.server_ = server_;
7523 this.statsToReport_ = {};
7524 this.statsListener_ = new StatsListener(collection);
7525 var timeout = FIRST_STATS_MIN_TIME +
7526 (FIRST_STATS_MAX_TIME - FIRST_STATS_MIN_TIME) * Math.random();
7527 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(timeout));
7528 }
7529 StatsReporter.prototype.reportStats_ = function () {
7530 var _this = this;
7531 var stats = this.statsListener_.get();
7532 var reportedStats = {};
7533 var haveStatsToReport = false;
7534 each(stats, function (stat, value) {
7535 if (value > 0 && util.contains(_this.statsToReport_, stat)) {
7536 reportedStats[stat] = value;
7537 haveStatsToReport = true;
7538 }
7539 });
7540 if (haveStatsToReport) {
7541 this.server_.reportStats(reportedStats);
7542 }
7543 // queue our next run.
7544 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(Math.random() * 2 * REPORT_STATS_INTERVAL));
7545 };
7546 return StatsReporter;
7547}());
7548
7549/**
7550 * @license
7551 * Copyright 2017 Google LLC
7552 *
7553 * Licensed under the Apache License, Version 2.0 (the "License");
7554 * you may not use this file except in compliance with the License.
7555 * You may obtain a copy of the License at
7556 *
7557 * http://www.apache.org/licenses/LICENSE-2.0
7558 *
7559 * Unless required by applicable law or agreed to in writing, software
7560 * distributed under the License is distributed on an "AS IS" BASIS,
7561 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7562 * See the License for the specific language governing permissions and
7563 * limitations under the License.
7564 */
7565/**
7566 *
7567 * @enum
7568 */
7569var OperationType;
7570(function (OperationType) {
7571 OperationType[OperationType["OVERWRITE"] = 0] = "OVERWRITE";
7572 OperationType[OperationType["MERGE"] = 1] = "MERGE";
7573 OperationType[OperationType["ACK_USER_WRITE"] = 2] = "ACK_USER_WRITE";
7574 OperationType[OperationType["LISTEN_COMPLETE"] = 3] = "LISTEN_COMPLETE";
7575})(OperationType || (OperationType = {}));
7576function newOperationSourceUser() {
7577 return {
7578 fromUser: true,
7579 fromServer: false,
7580 queryId: null,
7581 tagged: false
7582 };
7583}
7584function newOperationSourceServer() {
7585 return {
7586 fromUser: false,
7587 fromServer: true,
7588 queryId: null,
7589 tagged: false
7590 };
7591}
7592function newOperationSourceServerTaggedQuery(queryId) {
7593 return {
7594 fromUser: false,
7595 fromServer: true,
7596 queryId: queryId,
7597 tagged: true
7598 };
7599}
7600
7601/**
7602 * @license
7603 * Copyright 2017 Google LLC
7604 *
7605 * Licensed under the Apache License, Version 2.0 (the "License");
7606 * you may not use this file except in compliance with the License.
7607 * You may obtain a copy of the License at
7608 *
7609 * http://www.apache.org/licenses/LICENSE-2.0
7610 *
7611 * Unless required by applicable law or agreed to in writing, software
7612 * distributed under the License is distributed on an "AS IS" BASIS,
7613 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7614 * See the License for the specific language governing permissions and
7615 * limitations under the License.
7616 */
7617var AckUserWrite = /** @class */ (function () {
7618 /**
7619 * @param affectedTree - A tree containing true for each affected path. Affected paths can't overlap.
7620 */
7621 function AckUserWrite(
7622 /** @inheritDoc */ path,
7623 /** @inheritDoc */ affectedTree,
7624 /** @inheritDoc */ revert) {
7625 this.path = path;
7626 this.affectedTree = affectedTree;
7627 this.revert = revert;
7628 /** @inheritDoc */
7629 this.type = OperationType.ACK_USER_WRITE;
7630 /** @inheritDoc */
7631 this.source = newOperationSourceUser();
7632 }
7633 AckUserWrite.prototype.operationForChild = function (childName) {
7634 if (!pathIsEmpty(this.path)) {
7635 util.assert(pathGetFront(this.path) === childName, 'operationForChild called for unrelated child.');
7636 return new AckUserWrite(pathPopFront(this.path), this.affectedTree, this.revert);
7637 }
7638 else if (this.affectedTree.value != null) {
7639 util.assert(this.affectedTree.children.isEmpty(), 'affectedTree should not have overlapping affected paths.');
7640 // All child locations are affected as well; just return same operation.
7641 return this;
7642 }
7643 else {
7644 var childTree = this.affectedTree.subtree(new Path(childName));
7645 return new AckUserWrite(newEmptyPath(), childTree, this.revert);
7646 }
7647 };
7648 return AckUserWrite;
7649}());
7650
7651/**
7652 * @license
7653 * Copyright 2017 Google LLC
7654 *
7655 * Licensed under the Apache License, Version 2.0 (the "License");
7656 * you may not use this file except in compliance with the License.
7657 * You may obtain a copy of the License at
7658 *
7659 * http://www.apache.org/licenses/LICENSE-2.0
7660 *
7661 * Unless required by applicable law or agreed to in writing, software
7662 * distributed under the License is distributed on an "AS IS" BASIS,
7663 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7664 * See the License for the specific language governing permissions and
7665 * limitations under the License.
7666 */
7667var ListenComplete = /** @class */ (function () {
7668 function ListenComplete(source, path) {
7669 this.source = source;
7670 this.path = path;
7671 /** @inheritDoc */
7672 this.type = OperationType.LISTEN_COMPLETE;
7673 }
7674 ListenComplete.prototype.operationForChild = function (childName) {
7675 if (pathIsEmpty(this.path)) {
7676 return new ListenComplete(this.source, newEmptyPath());
7677 }
7678 else {
7679 return new ListenComplete(this.source, pathPopFront(this.path));
7680 }
7681 };
7682 return ListenComplete;
7683}());
7684
7685/**
7686 * @license
7687 * Copyright 2017 Google LLC
7688 *
7689 * Licensed under the Apache License, Version 2.0 (the "License");
7690 * you may not use this file except in compliance with the License.
7691 * You may obtain a copy of the License at
7692 *
7693 * http://www.apache.org/licenses/LICENSE-2.0
7694 *
7695 * Unless required by applicable law or agreed to in writing, software
7696 * distributed under the License is distributed on an "AS IS" BASIS,
7697 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7698 * See the License for the specific language governing permissions and
7699 * limitations under the License.
7700 */
7701var Overwrite = /** @class */ (function () {
7702 function Overwrite(source, path, snap) {
7703 this.source = source;
7704 this.path = path;
7705 this.snap = snap;
7706 /** @inheritDoc */
7707 this.type = OperationType.OVERWRITE;
7708 }
7709 Overwrite.prototype.operationForChild = function (childName) {
7710 if (pathIsEmpty(this.path)) {
7711 return new Overwrite(this.source, newEmptyPath(), this.snap.getImmediateChild(childName));
7712 }
7713 else {
7714 return new Overwrite(this.source, pathPopFront(this.path), this.snap);
7715 }
7716 };
7717 return Overwrite;
7718}());
7719
7720/**
7721 * @license
7722 * Copyright 2017 Google LLC
7723 *
7724 * Licensed under the Apache License, Version 2.0 (the "License");
7725 * you may not use this file except in compliance with the License.
7726 * You may obtain a copy of the License at
7727 *
7728 * http://www.apache.org/licenses/LICENSE-2.0
7729 *
7730 * Unless required by applicable law or agreed to in writing, software
7731 * distributed under the License is distributed on an "AS IS" BASIS,
7732 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7733 * See the License for the specific language governing permissions and
7734 * limitations under the License.
7735 */
7736var Merge = /** @class */ (function () {
7737 function Merge(
7738 /** @inheritDoc */ source,
7739 /** @inheritDoc */ path,
7740 /** @inheritDoc */ children) {
7741 this.source = source;
7742 this.path = path;
7743 this.children = children;
7744 /** @inheritDoc */
7745 this.type = OperationType.MERGE;
7746 }
7747 Merge.prototype.operationForChild = function (childName) {
7748 if (pathIsEmpty(this.path)) {
7749 var childTree = this.children.subtree(new Path(childName));
7750 if (childTree.isEmpty()) {
7751 // This child is unaffected
7752 return null;
7753 }
7754 else if (childTree.value) {
7755 // We have a snapshot for the child in question. This becomes an overwrite of the child.
7756 return new Overwrite(this.source, newEmptyPath(), childTree.value);
7757 }
7758 else {
7759 // This is a merge at a deeper level
7760 return new Merge(this.source, newEmptyPath(), childTree);
7761 }
7762 }
7763 else {
7764 util.assert(pathGetFront(this.path) === childName, "Can't get a merge for a child not on the path of the operation");
7765 return new Merge(this.source, pathPopFront(this.path), this.children);
7766 }
7767 };
7768 Merge.prototype.toString = function () {
7769 return ('Operation(' +
7770 this.path +
7771 ': ' +
7772 this.source.toString() +
7773 ' merge: ' +
7774 this.children.toString() +
7775 ')');
7776 };
7777 return Merge;
7778}());
7779
7780/**
7781 * @license
7782 * Copyright 2017 Google LLC
7783 *
7784 * Licensed under the Apache License, Version 2.0 (the "License");
7785 * you may not use this file except in compliance with the License.
7786 * You may obtain a copy of the License at
7787 *
7788 * http://www.apache.org/licenses/LICENSE-2.0
7789 *
7790 * Unless required by applicable law or agreed to in writing, software
7791 * distributed under the License is distributed on an "AS IS" BASIS,
7792 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7793 * See the License for the specific language governing permissions and
7794 * limitations under the License.
7795 */
7796/**
7797 * A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
7798 * initialized in the sense that we know at one point in time this represented a valid state of the world, e.g.
7799 * initialized with data from the server, or a complete overwrite by the client. The filtered flag also tracks
7800 * whether a node potentially had children removed due to a filter.
7801 */
7802var CacheNode = /** @class */ (function () {
7803 function CacheNode(node_, fullyInitialized_, filtered_) {
7804 this.node_ = node_;
7805 this.fullyInitialized_ = fullyInitialized_;
7806 this.filtered_ = filtered_;
7807 }
7808 /**
7809 * Returns whether this node was fully initialized with either server data or a complete overwrite by the client
7810 */
7811 CacheNode.prototype.isFullyInitialized = function () {
7812 return this.fullyInitialized_;
7813 };
7814 /**
7815 * Returns whether this node is potentially missing children due to a filter applied to the node
7816 */
7817 CacheNode.prototype.isFiltered = function () {
7818 return this.filtered_;
7819 };
7820 CacheNode.prototype.isCompleteForPath = function (path) {
7821 if (pathIsEmpty(path)) {
7822 return this.isFullyInitialized() && !this.filtered_;
7823 }
7824 var childKey = pathGetFront(path);
7825 return this.isCompleteForChild(childKey);
7826 };
7827 CacheNode.prototype.isCompleteForChild = function (key) {
7828 return ((this.isFullyInitialized() && !this.filtered_) || this.node_.hasChild(key));
7829 };
7830 CacheNode.prototype.getNode = function () {
7831 return this.node_;
7832 };
7833 return CacheNode;
7834}());
7835
7836/**
7837 * @license
7838 * Copyright 2017 Google LLC
7839 *
7840 * Licensed under the Apache License, Version 2.0 (the "License");
7841 * you may not use this file except in compliance with the License.
7842 * You may obtain a copy of the License at
7843 *
7844 * http://www.apache.org/licenses/LICENSE-2.0
7845 *
7846 * Unless required by applicable law or agreed to in writing, software
7847 * distributed under the License is distributed on an "AS IS" BASIS,
7848 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7849 * See the License for the specific language governing permissions and
7850 * limitations under the License.
7851 */
7852/**
7853 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
7854 * CacheDiffer into actual events (Event) that can be raised. See generateEventsForChanges()
7855 * for details.
7856 *
7857 */
7858var EventGenerator = /** @class */ (function () {
7859 function EventGenerator(query_) {
7860 this.query_ = query_;
7861 this.index_ = this.query_._queryParams.getIndex();
7862 }
7863 return EventGenerator;
7864}());
7865/**
7866 * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
7867 * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
7868 *
7869 * Notes:
7870 * - child_moved events will be synthesized at this time for any child_changed events that affect
7871 * our index.
7872 * - prevName will be calculated based on the index ordering.
7873 */
7874function eventGeneratorGenerateEventsForChanges(eventGenerator, changes, eventCache, eventRegistrations) {
7875 var events = [];
7876 var moves = [];
7877 changes.forEach(function (change) {
7878 if (change.type === "child_changed" /* CHILD_CHANGED */ &&
7879 eventGenerator.index_.indexedValueChanged(change.oldSnap, change.snapshotNode)) {
7880 moves.push(changeChildMoved(change.childName, change.snapshotNode));
7881 }
7882 });
7883 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_removed" /* CHILD_REMOVED */, changes, eventRegistrations, eventCache);
7884 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_added" /* CHILD_ADDED */, changes, eventRegistrations, eventCache);
7885 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_moved" /* CHILD_MOVED */, moves, eventRegistrations, eventCache);
7886 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_changed" /* CHILD_CHANGED */, changes, eventRegistrations, eventCache);
7887 eventGeneratorGenerateEventsForType(eventGenerator, events, "value" /* VALUE */, changes, eventRegistrations, eventCache);
7888 return events;
7889}
7890/**
7891 * Given changes of a single change type, generate the corresponding events.
7892 */
7893function eventGeneratorGenerateEventsForType(eventGenerator, events, eventType, changes, registrations, eventCache) {
7894 var filteredChanges = changes.filter(function (change) { return change.type === eventType; });
7895 filteredChanges.sort(function (a, b) {
7896 return eventGeneratorCompareChanges(eventGenerator, a, b);
7897 });
7898 filteredChanges.forEach(function (change) {
7899 var materializedChange = eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache);
7900 registrations.forEach(function (registration) {
7901 if (registration.respondsTo(change.type)) {
7902 events.push(registration.createEvent(materializedChange, eventGenerator.query_));
7903 }
7904 });
7905 });
7906}
7907function eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache) {
7908 if (change.type === 'value' || change.type === 'child_removed') {
7909 return change;
7910 }
7911 else {
7912 change.prevName = eventCache.getPredecessorChildName(change.childName, change.snapshotNode, eventGenerator.index_);
7913 return change;
7914 }
7915}
7916function eventGeneratorCompareChanges(eventGenerator, a, b) {
7917 if (a.childName == null || b.childName == null) {
7918 throw util.assertionError('Should only compare child_ events.');
7919 }
7920 var aWrapped = new NamedNode(a.childName, a.snapshotNode);
7921 var bWrapped = new NamedNode(b.childName, b.snapshotNode);
7922 return eventGenerator.index_.compare(aWrapped, bWrapped);
7923}
7924
7925/**
7926 * @license
7927 * Copyright 2017 Google LLC
7928 *
7929 * Licensed under the Apache License, Version 2.0 (the "License");
7930 * you may not use this file except in compliance with the License.
7931 * You may obtain a copy of the License at
7932 *
7933 * http://www.apache.org/licenses/LICENSE-2.0
7934 *
7935 * Unless required by applicable law or agreed to in writing, software
7936 * distributed under the License is distributed on an "AS IS" BASIS,
7937 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7938 * See the License for the specific language governing permissions and
7939 * limitations under the License.
7940 */
7941function newViewCache(eventCache, serverCache) {
7942 return { eventCache: eventCache, serverCache: serverCache };
7943}
7944function viewCacheUpdateEventSnap(viewCache, eventSnap, complete, filtered) {
7945 return newViewCache(new CacheNode(eventSnap, complete, filtered), viewCache.serverCache);
7946}
7947function viewCacheUpdateServerSnap(viewCache, serverSnap, complete, filtered) {
7948 return newViewCache(viewCache.eventCache, new CacheNode(serverSnap, complete, filtered));
7949}
7950function viewCacheGetCompleteEventSnap(viewCache) {
7951 return viewCache.eventCache.isFullyInitialized()
7952 ? viewCache.eventCache.getNode()
7953 : null;
7954}
7955function viewCacheGetCompleteServerSnap(viewCache) {
7956 return viewCache.serverCache.isFullyInitialized()
7957 ? viewCache.serverCache.getNode()
7958 : null;
7959}
7960
7961/**
7962 * @license
7963 * Copyright 2017 Google LLC
7964 *
7965 * Licensed under the Apache License, Version 2.0 (the "License");
7966 * you may not use this file except in compliance with the License.
7967 * You may obtain a copy of the License at
7968 *
7969 * http://www.apache.org/licenses/LICENSE-2.0
7970 *
7971 * Unless required by applicable law or agreed to in writing, software
7972 * distributed under the License is distributed on an "AS IS" BASIS,
7973 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7974 * See the License for the specific language governing permissions and
7975 * limitations under the License.
7976 */
7977var emptyChildrenSingleton;
7978/**
7979 * Singleton empty children collection.
7980 *
7981 */
7982var EmptyChildren = function () {
7983 if (!emptyChildrenSingleton) {
7984 emptyChildrenSingleton = new SortedMap(stringCompare);
7985 }
7986 return emptyChildrenSingleton;
7987};
7988/**
7989 * A tree with immutable elements.
7990 */
7991var ImmutableTree = /** @class */ (function () {
7992 function ImmutableTree(value, children) {
7993 if (children === void 0) { children = EmptyChildren(); }
7994 this.value = value;
7995 this.children = children;
7996 }
7997 ImmutableTree.fromObject = function (obj) {
7998 var tree = new ImmutableTree(null);
7999 each(obj, function (childPath, childSnap) {
8000 tree = tree.set(new Path(childPath), childSnap);
8001 });
8002 return tree;
8003 };
8004 /**
8005 * True if the value is empty and there are no children
8006 */
8007 ImmutableTree.prototype.isEmpty = function () {
8008 return this.value === null && this.children.isEmpty();
8009 };
8010 /**
8011 * Given a path and predicate, return the first node and the path to that node
8012 * where the predicate returns true.
8013 *
8014 * TODO Do a perf test -- If we're creating a bunch of `{path: value:}`
8015 * objects on the way back out, it may be better to pass down a pathSoFar obj.
8016 *
8017 * @param relativePath - The remainder of the path
8018 * @param predicate - The predicate to satisfy to return a node
8019 */
8020 ImmutableTree.prototype.findRootMostMatchingPathAndValue = function (relativePath, predicate) {
8021 if (this.value != null && predicate(this.value)) {
8022 return { path: newEmptyPath(), value: this.value };
8023 }
8024 else {
8025 if (pathIsEmpty(relativePath)) {
8026 return null;
8027 }
8028 else {
8029 var front = pathGetFront(relativePath);
8030 var child = this.children.get(front);
8031 if (child !== null) {
8032 var childExistingPathAndValue = child.findRootMostMatchingPathAndValue(pathPopFront(relativePath), predicate);
8033 if (childExistingPathAndValue != null) {
8034 var fullPath = pathChild(new Path(front), childExistingPathAndValue.path);
8035 return { path: fullPath, value: childExistingPathAndValue.value };
8036 }
8037 else {
8038 return null;
8039 }
8040 }
8041 else {
8042 return null;
8043 }
8044 }
8045 }
8046 };
8047 /**
8048 * Find, if it exists, the shortest subpath of the given path that points a defined
8049 * value in the tree
8050 */
8051 ImmutableTree.prototype.findRootMostValueAndPath = function (relativePath) {
8052 return this.findRootMostMatchingPathAndValue(relativePath, function () { return true; });
8053 };
8054 /**
8055 * @returns The subtree at the given path
8056 */
8057 ImmutableTree.prototype.subtree = function (relativePath) {
8058 if (pathIsEmpty(relativePath)) {
8059 return this;
8060 }
8061 else {
8062 var front = pathGetFront(relativePath);
8063 var childTree = this.children.get(front);
8064 if (childTree !== null) {
8065 return childTree.subtree(pathPopFront(relativePath));
8066 }
8067 else {
8068 return new ImmutableTree(null);
8069 }
8070 }
8071 };
8072 /**
8073 * Sets a value at the specified path.
8074 *
8075 * @param relativePath - Path to set value at.
8076 * @param toSet - Value to set.
8077 * @returns Resulting tree.
8078 */
8079 ImmutableTree.prototype.set = function (relativePath, toSet) {
8080 if (pathIsEmpty(relativePath)) {
8081 return new ImmutableTree(toSet, this.children);
8082 }
8083 else {
8084 var front = pathGetFront(relativePath);
8085 var child = this.children.get(front) || new ImmutableTree(null);
8086 var newChild = child.set(pathPopFront(relativePath), toSet);
8087 var newChildren = this.children.insert(front, newChild);
8088 return new ImmutableTree(this.value, newChildren);
8089 }
8090 };
8091 /**
8092 * Removes the value at the specified path.
8093 *
8094 * @param relativePath - Path to value to remove.
8095 * @returns Resulting tree.
8096 */
8097 ImmutableTree.prototype.remove = function (relativePath) {
8098 if (pathIsEmpty(relativePath)) {
8099 if (this.children.isEmpty()) {
8100 return new ImmutableTree(null);
8101 }
8102 else {
8103 return new ImmutableTree(null, this.children);
8104 }
8105 }
8106 else {
8107 var front = pathGetFront(relativePath);
8108 var child = this.children.get(front);
8109 if (child) {
8110 var newChild = child.remove(pathPopFront(relativePath));
8111 var newChildren = void 0;
8112 if (newChild.isEmpty()) {
8113 newChildren = this.children.remove(front);
8114 }
8115 else {
8116 newChildren = this.children.insert(front, newChild);
8117 }
8118 if (this.value === null && newChildren.isEmpty()) {
8119 return new ImmutableTree(null);
8120 }
8121 else {
8122 return new ImmutableTree(this.value, newChildren);
8123 }
8124 }
8125 else {
8126 return this;
8127 }
8128 }
8129 };
8130 /**
8131 * Gets a value from the tree.
8132 *
8133 * @param relativePath - Path to get value for.
8134 * @returns Value at path, or null.
8135 */
8136 ImmutableTree.prototype.get = function (relativePath) {
8137 if (pathIsEmpty(relativePath)) {
8138 return this.value;
8139 }
8140 else {
8141 var front = pathGetFront(relativePath);
8142 var child = this.children.get(front);
8143 if (child) {
8144 return child.get(pathPopFront(relativePath));
8145 }
8146 else {
8147 return null;
8148 }
8149 }
8150 };
8151 /**
8152 * Replace the subtree at the specified path with the given new tree.
8153 *
8154 * @param relativePath - Path to replace subtree for.
8155 * @param newTree - New tree.
8156 * @returns Resulting tree.
8157 */
8158 ImmutableTree.prototype.setTree = function (relativePath, newTree) {
8159 if (pathIsEmpty(relativePath)) {
8160 return newTree;
8161 }
8162 else {
8163 var front = pathGetFront(relativePath);
8164 var child = this.children.get(front) || new ImmutableTree(null);
8165 var newChild = child.setTree(pathPopFront(relativePath), newTree);
8166 var newChildren = void 0;
8167 if (newChild.isEmpty()) {
8168 newChildren = this.children.remove(front);
8169 }
8170 else {
8171 newChildren = this.children.insert(front, newChild);
8172 }
8173 return new ImmutableTree(this.value, newChildren);
8174 }
8175 };
8176 /**
8177 * Performs a depth first fold on this tree. Transforms a tree into a single
8178 * value, given a function that operates on the path to a node, an optional
8179 * current value, and a map of child names to folded subtrees
8180 */
8181 ImmutableTree.prototype.fold = function (fn) {
8182 return this.fold_(newEmptyPath(), fn);
8183 };
8184 /**
8185 * Recursive helper for public-facing fold() method
8186 */
8187 ImmutableTree.prototype.fold_ = function (pathSoFar, fn) {
8188 var accum = {};
8189 this.children.inorderTraversal(function (childKey, childTree) {
8190 accum[childKey] = childTree.fold_(pathChild(pathSoFar, childKey), fn);
8191 });
8192 return fn(pathSoFar, this.value, accum);
8193 };
8194 /**
8195 * Find the first matching value on the given path. Return the result of applying f to it.
8196 */
8197 ImmutableTree.prototype.findOnPath = function (path, f) {
8198 return this.findOnPath_(path, newEmptyPath(), f);
8199 };
8200 ImmutableTree.prototype.findOnPath_ = function (pathToFollow, pathSoFar, f) {
8201 var result = this.value ? f(pathSoFar, this.value) : false;
8202 if (result) {
8203 return result;
8204 }
8205 else {
8206 if (pathIsEmpty(pathToFollow)) {
8207 return null;
8208 }
8209 else {
8210 var front = pathGetFront(pathToFollow);
8211 var nextChild = this.children.get(front);
8212 if (nextChild) {
8213 return nextChild.findOnPath_(pathPopFront(pathToFollow), pathChild(pathSoFar, front), f);
8214 }
8215 else {
8216 return null;
8217 }
8218 }
8219 }
8220 };
8221 ImmutableTree.prototype.foreachOnPath = function (path, f) {
8222 return this.foreachOnPath_(path, newEmptyPath(), f);
8223 };
8224 ImmutableTree.prototype.foreachOnPath_ = function (pathToFollow, currentRelativePath, f) {
8225 if (pathIsEmpty(pathToFollow)) {
8226 return this;
8227 }
8228 else {
8229 if (this.value) {
8230 f(currentRelativePath, this.value);
8231 }
8232 var front = pathGetFront(pathToFollow);
8233 var nextChild = this.children.get(front);
8234 if (nextChild) {
8235 return nextChild.foreachOnPath_(pathPopFront(pathToFollow), pathChild(currentRelativePath, front), f);
8236 }
8237 else {
8238 return new ImmutableTree(null);
8239 }
8240 }
8241 };
8242 /**
8243 * Calls the given function for each node in the tree that has a value.
8244 *
8245 * @param f - A function to be called with the path from the root of the tree to
8246 * a node, and the value at that node. Called in depth-first order.
8247 */
8248 ImmutableTree.prototype.foreach = function (f) {
8249 this.foreach_(newEmptyPath(), f);
8250 };
8251 ImmutableTree.prototype.foreach_ = function (currentRelativePath, f) {
8252 this.children.inorderTraversal(function (childName, childTree) {
8253 childTree.foreach_(pathChild(currentRelativePath, childName), f);
8254 });
8255 if (this.value) {
8256 f(currentRelativePath, this.value);
8257 }
8258 };
8259 ImmutableTree.prototype.foreachChild = function (f) {
8260 this.children.inorderTraversal(function (childName, childTree) {
8261 if (childTree.value) {
8262 f(childName, childTree.value);
8263 }
8264 });
8265 };
8266 return ImmutableTree;
8267}());
8268
8269/**
8270 * @license
8271 * Copyright 2017 Google LLC
8272 *
8273 * Licensed under the Apache License, Version 2.0 (the "License");
8274 * you may not use this file except in compliance with the License.
8275 * You may obtain a copy of the License at
8276 *
8277 * http://www.apache.org/licenses/LICENSE-2.0
8278 *
8279 * Unless required by applicable law or agreed to in writing, software
8280 * distributed under the License is distributed on an "AS IS" BASIS,
8281 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8282 * See the License for the specific language governing permissions and
8283 * limitations under the License.
8284 */
8285/**
8286 * This class holds a collection of writes that can be applied to nodes in unison. It abstracts away the logic with
8287 * dealing with priority writes and multiple nested writes. At any given path there is only allowed to be one write
8288 * modifying that path. Any write to an existing path or shadowing an existing path will modify that existing write
8289 * to reflect the write added.
8290 */
8291var CompoundWrite = /** @class */ (function () {
8292 function CompoundWrite(writeTree_) {
8293 this.writeTree_ = writeTree_;
8294 }
8295 CompoundWrite.empty = function () {
8296 return new CompoundWrite(new ImmutableTree(null));
8297 };
8298 return CompoundWrite;
8299}());
8300function compoundWriteAddWrite(compoundWrite, path, node) {
8301 if (pathIsEmpty(path)) {
8302 return new CompoundWrite(new ImmutableTree(node));
8303 }
8304 else {
8305 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8306 if (rootmost != null) {
8307 var rootMostPath = rootmost.path;
8308 var value = rootmost.value;
8309 var relativePath = newRelativePath(rootMostPath, path);
8310 value = value.updateChild(relativePath, node);
8311 return new CompoundWrite(compoundWrite.writeTree_.set(rootMostPath, value));
8312 }
8313 else {
8314 var subtree = new ImmutableTree(node);
8315 var newWriteTree = compoundWrite.writeTree_.setTree(path, subtree);
8316 return new CompoundWrite(newWriteTree);
8317 }
8318 }
8319}
8320function compoundWriteAddWrites(compoundWrite, path, updates) {
8321 var newWrite = compoundWrite;
8322 each(updates, function (childKey, node) {
8323 newWrite = compoundWriteAddWrite(newWrite, pathChild(path, childKey), node);
8324 });
8325 return newWrite;
8326}
8327/**
8328 * Will remove a write at the given path and deeper paths. This will <em>not</em> modify a write at a higher
8329 * location, which must be removed by calling this method with that path.
8330 *
8331 * @param compoundWrite - The CompoundWrite to remove.
8332 * @param path - The path at which a write and all deeper writes should be removed
8333 * @returns The new CompoundWrite with the removed path
8334 */
8335function compoundWriteRemoveWrite(compoundWrite, path) {
8336 if (pathIsEmpty(path)) {
8337 return CompoundWrite.empty();
8338 }
8339 else {
8340 var newWriteTree = compoundWrite.writeTree_.setTree(path, new ImmutableTree(null));
8341 return new CompoundWrite(newWriteTree);
8342 }
8343}
8344/**
8345 * Returns whether this CompoundWrite will fully overwrite a node at a given location and can therefore be
8346 * considered "complete".
8347 *
8348 * @param compoundWrite - The CompoundWrite to check.
8349 * @param path - The path to check for
8350 * @returns Whether there is a complete write at that path
8351 */
8352function compoundWriteHasCompleteWrite(compoundWrite, path) {
8353 return compoundWriteGetCompleteNode(compoundWrite, path) != null;
8354}
8355/**
8356 * Returns a node for a path if and only if the node is a "complete" overwrite at that path. This will not aggregate
8357 * writes from deeper paths, but will return child nodes from a more shallow path.
8358 *
8359 * @param compoundWrite - The CompoundWrite to get the node from.
8360 * @param path - The path to get a complete write
8361 * @returns The node if complete at that path, or null otherwise.
8362 */
8363function compoundWriteGetCompleteNode(compoundWrite, path) {
8364 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8365 if (rootmost != null) {
8366 return compoundWrite.writeTree_
8367 .get(rootmost.path)
8368 .getChild(newRelativePath(rootmost.path, path));
8369 }
8370 else {
8371 return null;
8372 }
8373}
8374/**
8375 * Returns all children that are guaranteed to be a complete overwrite.
8376 *
8377 * @param compoundWrite - The CompoundWrite to get children from.
8378 * @returns A list of all complete children.
8379 */
8380function compoundWriteGetCompleteChildren(compoundWrite) {
8381 var children = [];
8382 var node = compoundWrite.writeTree_.value;
8383 if (node != null) {
8384 // If it's a leaf node, it has no children; so nothing to do.
8385 if (!node.isLeafNode()) {
8386 node.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8387 children.push(new NamedNode(childName, childNode));
8388 });
8389 }
8390 }
8391 else {
8392 compoundWrite.writeTree_.children.inorderTraversal(function (childName, childTree) {
8393 if (childTree.value != null) {
8394 children.push(new NamedNode(childName, childTree.value));
8395 }
8396 });
8397 }
8398 return children;
8399}
8400function compoundWriteChildCompoundWrite(compoundWrite, path) {
8401 if (pathIsEmpty(path)) {
8402 return compoundWrite;
8403 }
8404 else {
8405 var shadowingNode = compoundWriteGetCompleteNode(compoundWrite, path);
8406 if (shadowingNode != null) {
8407 return new CompoundWrite(new ImmutableTree(shadowingNode));
8408 }
8409 else {
8410 return new CompoundWrite(compoundWrite.writeTree_.subtree(path));
8411 }
8412 }
8413}
8414/**
8415 * Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
8416 * @returns Whether this CompoundWrite is empty
8417 */
8418function compoundWriteIsEmpty(compoundWrite) {
8419 return compoundWrite.writeTree_.isEmpty();
8420}
8421/**
8422 * Applies this CompoundWrite to a node. The node is returned with all writes from this CompoundWrite applied to the
8423 * node
8424 * @param node - The node to apply this CompoundWrite to
8425 * @returns The node with all writes applied
8426 */
8427function compoundWriteApply(compoundWrite, node) {
8428 return applySubtreeWrite(newEmptyPath(), compoundWrite.writeTree_, node);
8429}
8430function applySubtreeWrite(relativePath, writeTree, node) {
8431 if (writeTree.value != null) {
8432 // Since there a write is always a leaf, we're done here
8433 return node.updateChild(relativePath, writeTree.value);
8434 }
8435 else {
8436 var priorityWrite_1 = null;
8437 writeTree.children.inorderTraversal(function (childKey, childTree) {
8438 if (childKey === '.priority') {
8439 // Apply priorities at the end so we don't update priorities for either empty nodes or forget
8440 // to apply priorities to empty nodes that are later filled
8441 util.assert(childTree.value !== null, 'Priority writes must always be leaf nodes');
8442 priorityWrite_1 = childTree.value;
8443 }
8444 else {
8445 node = applySubtreeWrite(pathChild(relativePath, childKey), childTree, node);
8446 }
8447 });
8448 // If there was a priority write, we only apply it if the node is not empty
8449 if (!node.getChild(relativePath).isEmpty() && priorityWrite_1 !== null) {
8450 node = node.updateChild(pathChild(relativePath, '.priority'), priorityWrite_1);
8451 }
8452 return node;
8453 }
8454}
8455
8456/**
8457 * @license
8458 * Copyright 2017 Google LLC
8459 *
8460 * Licensed under the Apache License, Version 2.0 (the "License");
8461 * you may not use this file except in compliance with the License.
8462 * You may obtain a copy of the License at
8463 *
8464 * http://www.apache.org/licenses/LICENSE-2.0
8465 *
8466 * Unless required by applicable law or agreed to in writing, software
8467 * distributed under the License is distributed on an "AS IS" BASIS,
8468 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8469 * See the License for the specific language governing permissions and
8470 * limitations under the License.
8471 */
8472/**
8473 * Create a new WriteTreeRef for the given path. For use with a new sync point at the given path.
8474 *
8475 */
8476function writeTreeChildWrites(writeTree, path) {
8477 return newWriteTreeRef(path, writeTree);
8478}
8479/**
8480 * Record a new overwrite from user code.
8481 *
8482 * @param visible - This is set to false by some transactions. It should be excluded from event caches
8483 */
8484function writeTreeAddOverwrite(writeTree, path, snap, writeId, visible) {
8485 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older write on top of newer ones');
8486 if (visible === undefined) {
8487 visible = true;
8488 }
8489 writeTree.allWrites.push({
8490 path: path,
8491 snap: snap,
8492 writeId: writeId,
8493 visible: visible
8494 });
8495 if (visible) {
8496 writeTree.visibleWrites = compoundWriteAddWrite(writeTree.visibleWrites, path, snap);
8497 }
8498 writeTree.lastWriteId = writeId;
8499}
8500/**
8501 * Record a new merge from user code.
8502 */
8503function writeTreeAddMerge(writeTree, path, changedChildren, writeId) {
8504 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older merge on top of newer ones');
8505 writeTree.allWrites.push({
8506 path: path,
8507 children: changedChildren,
8508 writeId: writeId,
8509 visible: true
8510 });
8511 writeTree.visibleWrites = compoundWriteAddWrites(writeTree.visibleWrites, path, changedChildren);
8512 writeTree.lastWriteId = writeId;
8513}
8514function writeTreeGetWrite(writeTree, writeId) {
8515 for (var i = 0; i < writeTree.allWrites.length; i++) {
8516 var record = writeTree.allWrites[i];
8517 if (record.writeId === writeId) {
8518 return record;
8519 }
8520 }
8521 return null;
8522}
8523/**
8524 * Remove a write (either an overwrite or merge) that has been successfully acknowledge by the server. Recalculates
8525 * the tree if necessary. We return true if it may have been visible, meaning views need to reevaluate.
8526 *
8527 * @returns true if the write may have been visible (meaning we'll need to reevaluate / raise
8528 * events as a result).
8529 */
8530function writeTreeRemoveWrite(writeTree, writeId) {
8531 // Note: disabling this check. It could be a transaction that preempted another transaction, and thus was applied
8532 // out of order.
8533 //const validClear = revert || this.allWrites_.length === 0 || writeId <= this.allWrites_[0].writeId;
8534 //assert(validClear, "Either we don't have this write, or it's the first one in the queue");
8535 var idx = writeTree.allWrites.findIndex(function (s) {
8536 return s.writeId === writeId;
8537 });
8538 util.assert(idx >= 0, 'removeWrite called with nonexistent writeId.');
8539 var writeToRemove = writeTree.allWrites[idx];
8540 writeTree.allWrites.splice(idx, 1);
8541 var removedWriteWasVisible = writeToRemove.visible;
8542 var removedWriteOverlapsWithOtherWrites = false;
8543 var i = writeTree.allWrites.length - 1;
8544 while (removedWriteWasVisible && i >= 0) {
8545 var currentWrite = writeTree.allWrites[i];
8546 if (currentWrite.visible) {
8547 if (i >= idx &&
8548 writeTreeRecordContainsPath_(currentWrite, writeToRemove.path)) {
8549 // The removed write was completely shadowed by a subsequent write.
8550 removedWriteWasVisible = false;
8551 }
8552 else if (pathContains(writeToRemove.path, currentWrite.path)) {
8553 // Either we're covering some writes or they're covering part of us (depending on which came first).
8554 removedWriteOverlapsWithOtherWrites = true;
8555 }
8556 }
8557 i--;
8558 }
8559 if (!removedWriteWasVisible) {
8560 return false;
8561 }
8562 else if (removedWriteOverlapsWithOtherWrites) {
8563 // There's some shadowing going on. Just rebuild the visible writes from scratch.
8564 writeTreeResetTree_(writeTree);
8565 return true;
8566 }
8567 else {
8568 // There's no shadowing. We can safely just remove the write(s) from visibleWrites.
8569 if (writeToRemove.snap) {
8570 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, writeToRemove.path);
8571 }
8572 else {
8573 var children = writeToRemove.children;
8574 each(children, function (childName) {
8575 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, pathChild(writeToRemove.path, childName));
8576 });
8577 }
8578 return true;
8579 }
8580}
8581function writeTreeRecordContainsPath_(writeRecord, path) {
8582 if (writeRecord.snap) {
8583 return pathContains(writeRecord.path, path);
8584 }
8585 else {
8586 for (var childName in writeRecord.children) {
8587 if (writeRecord.children.hasOwnProperty(childName) &&
8588 pathContains(pathChild(writeRecord.path, childName), path)) {
8589 return true;
8590 }
8591 }
8592 return false;
8593 }
8594}
8595/**
8596 * Re-layer the writes and merges into a tree so we can efficiently calculate event snapshots
8597 */
8598function writeTreeResetTree_(writeTree) {
8599 writeTree.visibleWrites = writeTreeLayerTree_(writeTree.allWrites, writeTreeDefaultFilter_, newEmptyPath());
8600 if (writeTree.allWrites.length > 0) {
8601 writeTree.lastWriteId =
8602 writeTree.allWrites[writeTree.allWrites.length - 1].writeId;
8603 }
8604 else {
8605 writeTree.lastWriteId = -1;
8606 }
8607}
8608/**
8609 * The default filter used when constructing the tree. Keep everything that's visible.
8610 */
8611function writeTreeDefaultFilter_(write) {
8612 return write.visible;
8613}
8614/**
8615 * Static method. Given an array of WriteRecords, a filter for which ones to include, and a path, construct the tree of
8616 * event data at that path.
8617 */
8618function writeTreeLayerTree_(writes, filter, treeRoot) {
8619 var compoundWrite = CompoundWrite.empty();
8620 for (var i = 0; i < writes.length; ++i) {
8621 var write = writes[i];
8622 // Theory, a later set will either:
8623 // a) abort a relevant transaction, so no need to worry about excluding it from calculating that transaction
8624 // b) not be relevant to a transaction (separate branch), so again will not affect the data for that transaction
8625 if (filter(write)) {
8626 var writePath = write.path;
8627 var relativePath = void 0;
8628 if (write.snap) {
8629 if (pathContains(treeRoot, writePath)) {
8630 relativePath = newRelativePath(treeRoot, writePath);
8631 compoundWrite = compoundWriteAddWrite(compoundWrite, relativePath, write.snap);
8632 }
8633 else if (pathContains(writePath, treeRoot)) {
8634 relativePath = newRelativePath(writePath, treeRoot);
8635 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), write.snap.getChild(relativePath));
8636 }
8637 else ;
8638 }
8639 else if (write.children) {
8640 if (pathContains(treeRoot, writePath)) {
8641 relativePath = newRelativePath(treeRoot, writePath);
8642 compoundWrite = compoundWriteAddWrites(compoundWrite, relativePath, write.children);
8643 }
8644 else if (pathContains(writePath, treeRoot)) {
8645 relativePath = newRelativePath(writePath, treeRoot);
8646 if (pathIsEmpty(relativePath)) {
8647 compoundWrite = compoundWriteAddWrites(compoundWrite, newEmptyPath(), write.children);
8648 }
8649 else {
8650 var child = util.safeGet(write.children, pathGetFront(relativePath));
8651 if (child) {
8652 // There exists a child in this node that matches the root path
8653 var deepNode = child.getChild(pathPopFront(relativePath));
8654 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), deepNode);
8655 }
8656 }
8657 }
8658 else ;
8659 }
8660 else {
8661 throw util.assertionError('WriteRecord should have .snap or .children');
8662 }
8663 }
8664 }
8665 return compoundWrite;
8666}
8667/**
8668 * Given optional, underlying server data, and an optional set of constraints (exclude some sets, include hidden
8669 * writes), attempt to calculate a complete snapshot for the given path
8670 *
8671 * @param writeIdsToExclude - An optional set to be excluded
8672 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8673 */
8674function writeTreeCalcCompleteEventCache(writeTree, treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8675 if (!writeIdsToExclude && !includeHiddenWrites) {
8676 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8677 if (shadowingNode != null) {
8678 return shadowingNode;
8679 }
8680 else {
8681 var subMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8682 if (compoundWriteIsEmpty(subMerge)) {
8683 return completeServerCache;
8684 }
8685 else if (completeServerCache == null &&
8686 !compoundWriteHasCompleteWrite(subMerge, newEmptyPath())) {
8687 // We wouldn't have a complete snapshot, since there's no underlying data and no complete shadow
8688 return null;
8689 }
8690 else {
8691 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8692 return compoundWriteApply(subMerge, layeredCache);
8693 }
8694 }
8695 }
8696 else {
8697 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8698 if (!includeHiddenWrites && compoundWriteIsEmpty(merge)) {
8699 return completeServerCache;
8700 }
8701 else {
8702 // If the server cache is null, and we don't have a complete cache, we need to return null
8703 if (!includeHiddenWrites &&
8704 completeServerCache == null &&
8705 !compoundWriteHasCompleteWrite(merge, newEmptyPath())) {
8706 return null;
8707 }
8708 else {
8709 var filter = function (write) {
8710 return ((write.visible || includeHiddenWrites) &&
8711 (!writeIdsToExclude ||
8712 !~writeIdsToExclude.indexOf(write.writeId)) &&
8713 (pathContains(write.path, treePath) ||
8714 pathContains(treePath, write.path)));
8715 };
8716 var mergeAtPath = writeTreeLayerTree_(writeTree.allWrites, filter, treePath);
8717 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8718 return compoundWriteApply(mergeAtPath, layeredCache);
8719 }
8720 }
8721 }
8722}
8723/**
8724 * With optional, underlying server data, attempt to return a children node of children that we have complete data for.
8725 * Used when creating new views, to pre-fill their complete event children snapshot.
8726 */
8727function writeTreeCalcCompleteEventChildren(writeTree, treePath, completeServerChildren) {
8728 var completeChildren = ChildrenNode.EMPTY_NODE;
8729 var topLevelSet = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8730 if (topLevelSet) {
8731 if (!topLevelSet.isLeafNode()) {
8732 // we're shadowing everything. Return the children.
8733 topLevelSet.forEachChild(PRIORITY_INDEX, function (childName, childSnap) {
8734 completeChildren = completeChildren.updateImmediateChild(childName, childSnap);
8735 });
8736 }
8737 return completeChildren;
8738 }
8739 else if (completeServerChildren) {
8740 // Layer any children we have on top of this
8741 // We know we don't have a top-level set, so just enumerate existing children
8742 var merge_1 = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8743 completeServerChildren.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8744 var node = compoundWriteApply(compoundWriteChildCompoundWrite(merge_1, new Path(childName)), childNode);
8745 completeChildren = completeChildren.updateImmediateChild(childName, node);
8746 });
8747 // Add any complete children we have from the set
8748 compoundWriteGetCompleteChildren(merge_1).forEach(function (namedNode) {
8749 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8750 });
8751 return completeChildren;
8752 }
8753 else {
8754 // We don't have anything to layer on top of. Layer on any children we have
8755 // Note that we can return an empty snap if we have a defined delete
8756 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8757 compoundWriteGetCompleteChildren(merge).forEach(function (namedNode) {
8758 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8759 });
8760 return completeChildren;
8761 }
8762}
8763/**
8764 * Given that the underlying server data has updated, determine what, if anything, needs to be
8765 * applied to the event cache.
8766 *
8767 * Possibilities:
8768 *
8769 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8770 *
8771 * 2. Some write is completely shadowing. No events to be raised
8772 *
8773 * 3. Is partially shadowed. Events
8774 *
8775 * Either existingEventSnap or existingServerSnap must exist
8776 */
8777function writeTreeCalcEventCacheAfterServerOverwrite(writeTree, treePath, childPath, existingEventSnap, existingServerSnap) {
8778 util.assert(existingEventSnap || existingServerSnap, 'Either existingEventSnap or existingServerSnap must exist');
8779 var path = pathChild(treePath, childPath);
8780 if (compoundWriteHasCompleteWrite(writeTree.visibleWrites, path)) {
8781 // At this point we can probably guarantee that we're in case 2, meaning no events
8782 // May need to check visibility while doing the findRootMostValueAndPath call
8783 return null;
8784 }
8785 else {
8786 // No complete shadowing. We're either partially shadowing or not shadowing at all.
8787 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8788 if (compoundWriteIsEmpty(childMerge)) {
8789 // We're not shadowing at all. Case 1
8790 return existingServerSnap.getChild(childPath);
8791 }
8792 else {
8793 // This could be more efficient if the serverNode + updates doesn't change the eventSnap
8794 // However this is tricky to find out, since user updates don't necessary change the server
8795 // snap, e.g. priority updates on empty nodes, or deep deletes. Another special case is if the server
8796 // adds nodes, but doesn't change any existing writes. It is therefore not enough to
8797 // only check if the updates change the serverNode.
8798 // Maybe check if the merge tree contains these special cases and only do a full overwrite in that case?
8799 return compoundWriteApply(childMerge, existingServerSnap.getChild(childPath));
8800 }
8801 }
8802}
8803/**
8804 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8805 * complete child for this ChildKey.
8806 */
8807function writeTreeCalcCompleteChild(writeTree, treePath, childKey, existingServerSnap) {
8808 var path = pathChild(treePath, childKey);
8809 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8810 if (shadowingNode != null) {
8811 return shadowingNode;
8812 }
8813 else {
8814 if (existingServerSnap.isCompleteForChild(childKey)) {
8815 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8816 return compoundWriteApply(childMerge, existingServerSnap.getNode().getImmediateChild(childKey));
8817 }
8818 else {
8819 return null;
8820 }
8821 }
8822}
8823/**
8824 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8825 * a higher path, this will return the child of that write relative to the write and this path.
8826 * Returns null if there is no write at this path.
8827 */
8828function writeTreeShadowingWrite(writeTree, path) {
8829 return compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8830}
8831/**
8832 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8833 * the window, but may now be in the window.
8834 */
8835function writeTreeCalcIndexedSlice(writeTree, treePath, completeServerData, startPost, count, reverse, index) {
8836 var toIterate;
8837 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8838 var shadowingNode = compoundWriteGetCompleteNode(merge, newEmptyPath());
8839 if (shadowingNode != null) {
8840 toIterate = shadowingNode;
8841 }
8842 else if (completeServerData != null) {
8843 toIterate = compoundWriteApply(merge, completeServerData);
8844 }
8845 else {
8846 // no children to iterate on
8847 return [];
8848 }
8849 toIterate = toIterate.withIndex(index);
8850 if (!toIterate.isEmpty() && !toIterate.isLeafNode()) {
8851 var nodes = [];
8852 var cmp = index.getCompare();
8853 var iter = reverse
8854 ? toIterate.getReverseIteratorFrom(startPost, index)
8855 : toIterate.getIteratorFrom(startPost, index);
8856 var next = iter.getNext();
8857 while (next && nodes.length < count) {
8858 if (cmp(next, startPost) !== 0) {
8859 nodes.push(next);
8860 }
8861 next = iter.getNext();
8862 }
8863 return nodes;
8864 }
8865 else {
8866 return [];
8867 }
8868}
8869function newWriteTree() {
8870 return {
8871 visibleWrites: CompoundWrite.empty(),
8872 allWrites: [],
8873 lastWriteId: -1
8874 };
8875}
8876/**
8877 * If possible, returns a complete event cache, using the underlying server data if possible. In addition, can be used
8878 * to get a cache that includes hidden writes, and excludes arbitrary writes. Note that customizing the returned node
8879 * can lead to a more expensive calculation.
8880 *
8881 * @param writeIdsToExclude - Optional writes to exclude.
8882 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8883 */
8884function writeTreeRefCalcCompleteEventCache(writeTreeRef, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8885 return writeTreeCalcCompleteEventCache(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites);
8886}
8887/**
8888 * If possible, returns a children node containing all of the complete children we have data for. The returned data is a
8889 * mix of the given server data and write data.
8890 *
8891 */
8892function writeTreeRefCalcCompleteEventChildren(writeTreeRef, completeServerChildren) {
8893 return writeTreeCalcCompleteEventChildren(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerChildren);
8894}
8895/**
8896 * Given that either the underlying server data has updated or the outstanding writes have updated, determine what,
8897 * if anything, needs to be applied to the event cache.
8898 *
8899 * Possibilities:
8900 *
8901 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8902 *
8903 * 2. Some write is completely shadowing. No events to be raised
8904 *
8905 * 3. Is partially shadowed. Events should be raised
8906 *
8907 * Either existingEventSnap or existingServerSnap must exist, this is validated via an assert
8908 *
8909 *
8910 */
8911function writeTreeRefCalcEventCacheAfterServerOverwrite(writeTreeRef, path, existingEventSnap, existingServerSnap) {
8912 return writeTreeCalcEventCacheAfterServerOverwrite(writeTreeRef.writeTree, writeTreeRef.treePath, path, existingEventSnap, existingServerSnap);
8913}
8914/**
8915 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8916 * a higher path, this will return the child of that write relative to the write and this path.
8917 * Returns null if there is no write at this path.
8918 *
8919 */
8920function writeTreeRefShadowingWrite(writeTreeRef, path) {
8921 return writeTreeShadowingWrite(writeTreeRef.writeTree, pathChild(writeTreeRef.treePath, path));
8922}
8923/**
8924 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8925 * the window, but may now be in the window
8926 */
8927function writeTreeRefCalcIndexedSlice(writeTreeRef, completeServerData, startPost, count, reverse, index) {
8928 return writeTreeCalcIndexedSlice(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerData, startPost, count, reverse, index);
8929}
8930/**
8931 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8932 * complete child for this ChildKey.
8933 */
8934function writeTreeRefCalcCompleteChild(writeTreeRef, childKey, existingServerCache) {
8935 return writeTreeCalcCompleteChild(writeTreeRef.writeTree, writeTreeRef.treePath, childKey, existingServerCache);
8936}
8937/**
8938 * Return a WriteTreeRef for a child.
8939 */
8940function writeTreeRefChild(writeTreeRef, childName) {
8941 return newWriteTreeRef(pathChild(writeTreeRef.treePath, childName), writeTreeRef.writeTree);
8942}
8943function newWriteTreeRef(path, writeTree) {
8944 return {
8945 treePath: path,
8946 writeTree: writeTree
8947 };
8948}
8949
8950/**
8951 * @license
8952 * Copyright 2017 Google LLC
8953 *
8954 * Licensed under the Apache License, Version 2.0 (the "License");
8955 * you may not use this file except in compliance with the License.
8956 * You may obtain a copy of the License at
8957 *
8958 * http://www.apache.org/licenses/LICENSE-2.0
8959 *
8960 * Unless required by applicable law or agreed to in writing, software
8961 * distributed under the License is distributed on an "AS IS" BASIS,
8962 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8963 * See the License for the specific language governing permissions and
8964 * limitations under the License.
8965 */
8966var ChildChangeAccumulator = /** @class */ (function () {
8967 function ChildChangeAccumulator() {
8968 this.changeMap = new Map();
8969 }
8970 ChildChangeAccumulator.prototype.trackChildChange = function (change) {
8971 var type = change.type;
8972 var childKey = change.childName;
8973 util.assert(type === "child_added" /* CHILD_ADDED */ ||
8974 type === "child_changed" /* CHILD_CHANGED */ ||
8975 type === "child_removed" /* CHILD_REMOVED */, 'Only child changes supported for tracking');
8976 util.assert(childKey !== '.priority', 'Only non-priority child changes can be tracked.');
8977 var oldChange = this.changeMap.get(childKey);
8978 if (oldChange) {
8979 var oldType = oldChange.type;
8980 if (type === "child_added" /* CHILD_ADDED */ &&
8981 oldType === "child_removed" /* CHILD_REMOVED */) {
8982 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.snapshotNode));
8983 }
8984 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8985 oldType === "child_added" /* CHILD_ADDED */) {
8986 this.changeMap.delete(childKey);
8987 }
8988 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8989 oldType === "child_changed" /* CHILD_CHANGED */) {
8990 this.changeMap.set(childKey, changeChildRemoved(childKey, oldChange.oldSnap));
8991 }
8992 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8993 oldType === "child_added" /* CHILD_ADDED */) {
8994 this.changeMap.set(childKey, changeChildAdded(childKey, change.snapshotNode));
8995 }
8996 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8997 oldType === "child_changed" /* CHILD_CHANGED */) {
8998 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.oldSnap));
8999 }
9000 else {
9001 throw util.assertionError('Illegal combination of changes: ' +
9002 change +
9003 ' occurred after ' +
9004 oldChange);
9005 }
9006 }
9007 else {
9008 this.changeMap.set(childKey, change);
9009 }
9010 };
9011 ChildChangeAccumulator.prototype.getChanges = function () {
9012 return Array.from(this.changeMap.values());
9013 };
9014 return ChildChangeAccumulator;
9015}());
9016
9017/**
9018 * @license
9019 * Copyright 2017 Google LLC
9020 *
9021 * Licensed under the Apache License, Version 2.0 (the "License");
9022 * you may not use this file except in compliance with the License.
9023 * You may obtain a copy of the License at
9024 *
9025 * http://www.apache.org/licenses/LICENSE-2.0
9026 *
9027 * Unless required by applicable law or agreed to in writing, software
9028 * distributed under the License is distributed on an "AS IS" BASIS,
9029 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9030 * See the License for the specific language governing permissions and
9031 * limitations under the License.
9032 */
9033/**
9034 * An implementation of CompleteChildSource that never returns any additional children
9035 */
9036// eslint-disable-next-line @typescript-eslint/naming-convention
9037var NoCompleteChildSource_ = /** @class */ (function () {
9038 function NoCompleteChildSource_() {
9039 }
9040 NoCompleteChildSource_.prototype.getCompleteChild = function (childKey) {
9041 return null;
9042 };
9043 NoCompleteChildSource_.prototype.getChildAfterChild = function (index, child, reverse) {
9044 return null;
9045 };
9046 return NoCompleteChildSource_;
9047}());
9048/**
9049 * Singleton instance.
9050 */
9051var NO_COMPLETE_CHILD_SOURCE = new NoCompleteChildSource_();
9052/**
9053 * An implementation of CompleteChildSource that uses a WriteTree in addition to any other server data or
9054 * old event caches available to calculate complete children.
9055 */
9056var WriteTreeCompleteChildSource = /** @class */ (function () {
9057 function WriteTreeCompleteChildSource(writes_, viewCache_, optCompleteServerCache_) {
9058 if (optCompleteServerCache_ === void 0) { optCompleteServerCache_ = null; }
9059 this.writes_ = writes_;
9060 this.viewCache_ = viewCache_;
9061 this.optCompleteServerCache_ = optCompleteServerCache_;
9062 }
9063 WriteTreeCompleteChildSource.prototype.getCompleteChild = function (childKey) {
9064 var node = this.viewCache_.eventCache;
9065 if (node.isCompleteForChild(childKey)) {
9066 return node.getNode().getImmediateChild(childKey);
9067 }
9068 else {
9069 var serverNode = this.optCompleteServerCache_ != null
9070 ? new CacheNode(this.optCompleteServerCache_, true, false)
9071 : this.viewCache_.serverCache;
9072 return writeTreeRefCalcCompleteChild(this.writes_, childKey, serverNode);
9073 }
9074 };
9075 WriteTreeCompleteChildSource.prototype.getChildAfterChild = function (index, child, reverse) {
9076 var completeServerData = this.optCompleteServerCache_ != null
9077 ? this.optCompleteServerCache_
9078 : viewCacheGetCompleteServerSnap(this.viewCache_);
9079 var nodes = writeTreeRefCalcIndexedSlice(this.writes_, completeServerData, child, 1, reverse, index);
9080 if (nodes.length === 0) {
9081 return null;
9082 }
9083 else {
9084 return nodes[0];
9085 }
9086 };
9087 return WriteTreeCompleteChildSource;
9088}());
9089
9090/**
9091 * @license
9092 * Copyright 2017 Google LLC
9093 *
9094 * Licensed under the Apache License, Version 2.0 (the "License");
9095 * you may not use this file except in compliance with the License.
9096 * You may obtain a copy of the License at
9097 *
9098 * http://www.apache.org/licenses/LICENSE-2.0
9099 *
9100 * Unless required by applicable law or agreed to in writing, software
9101 * distributed under the License is distributed on an "AS IS" BASIS,
9102 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9103 * See the License for the specific language governing permissions and
9104 * limitations under the License.
9105 */
9106function newViewProcessor(filter) {
9107 return { filter: filter };
9108}
9109function viewProcessorAssertIndexed(viewProcessor, viewCache) {
9110 util.assert(viewCache.eventCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Event snap not indexed');
9111 util.assert(viewCache.serverCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Server snap not indexed');
9112}
9113function viewProcessorApplyOperation(viewProcessor, oldViewCache, operation, writesCache, completeCache) {
9114 var accumulator = new ChildChangeAccumulator();
9115 var newViewCache, filterServerNode;
9116 if (operation.type === OperationType.OVERWRITE) {
9117 var overwrite = operation;
9118 if (overwrite.source.fromUser) {
9119 newViewCache = viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, accumulator);
9120 }
9121 else {
9122 util.assert(overwrite.source.fromServer, 'Unknown source.');
9123 // We filter the node if it's a tagged update or the node has been previously filtered and the
9124 // update is not at the root in which case it is ok (and necessary) to mark the node unfiltered
9125 // again
9126 filterServerNode =
9127 overwrite.source.tagged ||
9128 (oldViewCache.serverCache.isFiltered() && !pathIsEmpty(overwrite.path));
9129 newViewCache = viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, filterServerNode, accumulator);
9130 }
9131 }
9132 else if (operation.type === OperationType.MERGE) {
9133 var merge = operation;
9134 if (merge.source.fromUser) {
9135 newViewCache = viewProcessorApplyUserMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, accumulator);
9136 }
9137 else {
9138 util.assert(merge.source.fromServer, 'Unknown source.');
9139 // We filter the node if it's a tagged update or the node has been previously filtered
9140 filterServerNode =
9141 merge.source.tagged || oldViewCache.serverCache.isFiltered();
9142 newViewCache = viewProcessorApplyServerMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, filterServerNode, accumulator);
9143 }
9144 }
9145 else if (operation.type === OperationType.ACK_USER_WRITE) {
9146 var ackUserWrite = operation;
9147 if (!ackUserWrite.revert) {
9148 newViewCache = viewProcessorAckUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, ackUserWrite.affectedTree, writesCache, completeCache, accumulator);
9149 }
9150 else {
9151 newViewCache = viewProcessorRevertUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, writesCache, completeCache, accumulator);
9152 }
9153 }
9154 else if (operation.type === OperationType.LISTEN_COMPLETE) {
9155 newViewCache = viewProcessorListenComplete(viewProcessor, oldViewCache, operation.path, writesCache, accumulator);
9156 }
9157 else {
9158 throw util.assertionError('Unknown operation type: ' + operation.type);
9159 }
9160 var changes = accumulator.getChanges();
9161 viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, changes);
9162 return { viewCache: newViewCache, changes: changes };
9163}
9164function viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, accumulator) {
9165 var eventSnap = newViewCache.eventCache;
9166 if (eventSnap.isFullyInitialized()) {
9167 var isLeafOrEmpty = eventSnap.getNode().isLeafNode() || eventSnap.getNode().isEmpty();
9168 var oldCompleteSnap = viewCacheGetCompleteEventSnap(oldViewCache);
9169 if (accumulator.length > 0 ||
9170 !oldViewCache.eventCache.isFullyInitialized() ||
9171 (isLeafOrEmpty && !eventSnap.getNode().equals(oldCompleteSnap)) ||
9172 !eventSnap.getNode().getPriority().equals(oldCompleteSnap.getPriority())) {
9173 accumulator.push(changeValue(viewCacheGetCompleteEventSnap(newViewCache)));
9174 }
9175 }
9176}
9177function viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, viewCache, changePath, writesCache, source, accumulator) {
9178 var oldEventSnap = viewCache.eventCache;
9179 if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
9180 // we have a shadowing write, ignore changes
9181 return viewCache;
9182 }
9183 else {
9184 var newEventCache = void 0, serverNode = void 0;
9185 if (pathIsEmpty(changePath)) {
9186 // TODO: figure out how this plays with "sliding ack windows"
9187 util.assert(viewCache.serverCache.isFullyInitialized(), 'If change path is empty, we must have complete server data');
9188 if (viewCache.serverCache.isFiltered()) {
9189 // We need to special case this, because we need to only apply writes to complete children, or
9190 // we might end up raising events for incomplete children. If the server data is filtered deep
9191 // writes cannot be guaranteed to be complete
9192 var serverCache = viewCacheGetCompleteServerSnap(viewCache);
9193 var completeChildren = serverCache instanceof ChildrenNode
9194 ? serverCache
9195 : ChildrenNode.EMPTY_NODE;
9196 var completeEventChildren = writeTreeRefCalcCompleteEventChildren(writesCache, completeChildren);
9197 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeEventChildren, accumulator);
9198 }
9199 else {
9200 var completeNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9201 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeNode, accumulator);
9202 }
9203 }
9204 else {
9205 var childKey = pathGetFront(changePath);
9206 if (childKey === '.priority') {
9207 util.assert(pathGetLength(changePath) === 1, "Can't have a priority with additional path components");
9208 var oldEventNode = oldEventSnap.getNode();
9209 serverNode = viewCache.serverCache.getNode();
9210 // we might have overwrites for this priority
9211 var updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventNode, serverNode);
9212 if (updatedPriority != null) {
9213 newEventCache = viewProcessor.filter.updatePriority(oldEventNode, updatedPriority);
9214 }
9215 else {
9216 // priority didn't change, keep old node
9217 newEventCache = oldEventSnap.getNode();
9218 }
9219 }
9220 else {
9221 var childChangePath = pathPopFront(changePath);
9222 // update child
9223 var newEventChild = void 0;
9224 if (oldEventSnap.isCompleteForChild(childKey)) {
9225 serverNode = viewCache.serverCache.getNode();
9226 var eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventSnap.getNode(), serverNode);
9227 if (eventChildUpdate != null) {
9228 newEventChild = oldEventSnap
9229 .getNode()
9230 .getImmediateChild(childKey)
9231 .updateChild(childChangePath, eventChildUpdate);
9232 }
9233 else {
9234 // Nothing changed, just keep the old child
9235 newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
9236 }
9237 }
9238 else {
9239 newEventChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9240 }
9241 if (newEventChild != null) {
9242 newEventCache = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newEventChild, childChangePath, source, accumulator);
9243 }
9244 else {
9245 // no complete child available or no change
9246 newEventCache = oldEventSnap.getNode();
9247 }
9248 }
9249 }
9250 return viewCacheUpdateEventSnap(viewCache, newEventCache, oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath), viewProcessor.filter.filtersNodes());
9251 }
9252}
9253function viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, filterServerNode, accumulator) {
9254 var oldServerSnap = oldViewCache.serverCache;
9255 var newServerCache;
9256 var serverFilter = filterServerNode
9257 ? viewProcessor.filter
9258 : viewProcessor.filter.getIndexedFilter();
9259 if (pathIsEmpty(changePath)) {
9260 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), changedSnap, null);
9261 }
9262 else if (serverFilter.filtersNodes() && !oldServerSnap.isFiltered()) {
9263 // we want to filter the server node, but we didn't filter the server node yet, so simulate a full update
9264 var newServerNode = oldServerSnap
9265 .getNode()
9266 .updateChild(changePath, changedSnap);
9267 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), newServerNode, null);
9268 }
9269 else {
9270 var childKey = pathGetFront(changePath);
9271 if (!oldServerSnap.isCompleteForPath(changePath) &&
9272 pathGetLength(changePath) > 1) {
9273 // We don't update incomplete nodes with updates intended for other listeners
9274 return oldViewCache;
9275 }
9276 var childChangePath = pathPopFront(changePath);
9277 var childNode = oldServerSnap.getNode().getImmediateChild(childKey);
9278 var newChildNode = childNode.updateChild(childChangePath, changedSnap);
9279 if (childKey === '.priority') {
9280 newServerCache = serverFilter.updatePriority(oldServerSnap.getNode(), newChildNode);
9281 }
9282 else {
9283 newServerCache = serverFilter.updateChild(oldServerSnap.getNode(), childKey, newChildNode, childChangePath, NO_COMPLETE_CHILD_SOURCE, null);
9284 }
9285 }
9286 var newViewCache = viewCacheUpdateServerSnap(oldViewCache, newServerCache, oldServerSnap.isFullyInitialized() || pathIsEmpty(changePath), serverFilter.filtersNodes());
9287 var source = new WriteTreeCompleteChildSource(writesCache, newViewCache, completeCache);
9288 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, changePath, writesCache, source, accumulator);
9289}
9290function viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, accumulator) {
9291 var oldEventSnap = oldViewCache.eventCache;
9292 var newViewCache, newEventCache;
9293 var source = new WriteTreeCompleteChildSource(writesCache, oldViewCache, completeCache);
9294 if (pathIsEmpty(changePath)) {
9295 newEventCache = viewProcessor.filter.updateFullNode(oldViewCache.eventCache.getNode(), changedSnap, accumulator);
9296 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, true, viewProcessor.filter.filtersNodes());
9297 }
9298 else {
9299 var childKey = pathGetFront(changePath);
9300 if (childKey === '.priority') {
9301 newEventCache = viewProcessor.filter.updatePriority(oldViewCache.eventCache.getNode(), changedSnap);
9302 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, oldEventSnap.isFullyInitialized(), oldEventSnap.isFiltered());
9303 }
9304 else {
9305 var childChangePath = pathPopFront(changePath);
9306 var oldChild = oldEventSnap.getNode().getImmediateChild(childKey);
9307 var newChild = void 0;
9308 if (pathIsEmpty(childChangePath)) {
9309 // Child overwrite, we can replace the child
9310 newChild = changedSnap;
9311 }
9312 else {
9313 var childNode = source.getCompleteChild(childKey);
9314 if (childNode != null) {
9315 if (pathGetBack(childChangePath) === '.priority' &&
9316 childNode.getChild(pathParent(childChangePath)).isEmpty()) {
9317 // This is a priority update on an empty node. If this node exists on the server, the
9318 // server will send down the priority in the update, so ignore for now
9319 newChild = childNode;
9320 }
9321 else {
9322 newChild = childNode.updateChild(childChangePath, changedSnap);
9323 }
9324 }
9325 else {
9326 // There is no complete child node available
9327 newChild = ChildrenNode.EMPTY_NODE;
9328 }
9329 }
9330 if (!oldChild.equals(newChild)) {
9331 var newEventSnap = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newChild, childChangePath, source, accumulator);
9332 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventSnap, oldEventSnap.isFullyInitialized(), viewProcessor.filter.filtersNodes());
9333 }
9334 else {
9335 newViewCache = oldViewCache;
9336 }
9337 }
9338 }
9339 return newViewCache;
9340}
9341function viewProcessorCacheHasChild(viewCache, childKey) {
9342 return viewCache.eventCache.isCompleteForChild(childKey);
9343}
9344function viewProcessorApplyUserMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, accumulator) {
9345 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9346 // window leaving room for new items. It's important we process these changes first, so we
9347 // iterate the changes twice, first processing any that affect items currently in view.
9348 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9349 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9350 // not the other.
9351 var curViewCache = viewCache;
9352 changedChildren.foreach(function (relativePath, childNode) {
9353 var writePath = pathChild(path, relativePath);
9354 if (viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9355 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9356 }
9357 });
9358 changedChildren.foreach(function (relativePath, childNode) {
9359 var writePath = pathChild(path, relativePath);
9360 if (!viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9361 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9362 }
9363 });
9364 return curViewCache;
9365}
9366function viewProcessorApplyMerge(viewProcessor, node, merge) {
9367 merge.foreach(function (relativePath, childNode) {
9368 node = node.updateChild(relativePath, childNode);
9369 });
9370 return node;
9371}
9372function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, filterServerNode, accumulator) {
9373 // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
9374 // wait for the complete data update coming soon.
9375 if (viewCache.serverCache.getNode().isEmpty() &&
9376 !viewCache.serverCache.isFullyInitialized()) {
9377 return viewCache;
9378 }
9379 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9380 // window leaving room for new items. It's important we process these changes first, so we
9381 // iterate the changes twice, first processing any that affect items currently in view.
9382 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9383 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9384 // not the other.
9385 var curViewCache = viewCache;
9386 var viewMergeTree;
9387 if (pathIsEmpty(path)) {
9388 viewMergeTree = changedChildren;
9389 }
9390 else {
9391 viewMergeTree = new ImmutableTree(null).setTree(path, changedChildren);
9392 }
9393 var serverNode = viewCache.serverCache.getNode();
9394 viewMergeTree.children.inorderTraversal(function (childKey, childTree) {
9395 if (serverNode.hasChild(childKey)) {
9396 var serverChild = viewCache.serverCache
9397 .getNode()
9398 .getImmediateChild(childKey);
9399 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childTree);
9400 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9401 }
9402 });
9403 viewMergeTree.children.inorderTraversal(function (childKey, childMergeTree) {
9404 var isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9405 childMergeTree.value === undefined;
9406 if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9407 var serverChild = viewCache.serverCache
9408 .getNode()
9409 .getImmediateChild(childKey);
9410 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childMergeTree);
9411 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9412 }
9413 });
9414 return curViewCache;
9415}
9416function viewProcessorAckUserWrite(viewProcessor, viewCache, ackPath, affectedTree, writesCache, completeCache, accumulator) {
9417 if (writeTreeRefShadowingWrite(writesCache, ackPath) != null) {
9418 return viewCache;
9419 }
9420 // Only filter server node if it is currently filtered
9421 var filterServerNode = viewCache.serverCache.isFiltered();
9422 // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
9423 // now that it won't be shadowed.
9424 var serverCache = viewCache.serverCache;
9425 if (affectedTree.value != null) {
9426 // This is an overwrite.
9427 if ((pathIsEmpty(ackPath) && serverCache.isFullyInitialized()) ||
9428 serverCache.isCompleteForPath(ackPath)) {
9429 return viewProcessorApplyServerOverwrite(viewProcessor, viewCache, ackPath, serverCache.getNode().getChild(ackPath), writesCache, completeCache, filterServerNode, accumulator);
9430 }
9431 else if (pathIsEmpty(ackPath)) {
9432 // This is a goofy edge case where we are acking data at this location but don't have full data. We
9433 // should just re-apply whatever we have in our cache as a merge.
9434 var changedChildren_1 = new ImmutableTree(null);
9435 serverCache.getNode().forEachChild(KEY_INDEX, function (name, node) {
9436 changedChildren_1 = changedChildren_1.set(new Path(name), node);
9437 });
9438 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_1, writesCache, completeCache, filterServerNode, accumulator);
9439 }
9440 else {
9441 return viewCache;
9442 }
9443 }
9444 else {
9445 // This is a merge.
9446 var changedChildren_2 = new ImmutableTree(null);
9447 affectedTree.foreach(function (mergePath, value) {
9448 var serverCachePath = pathChild(ackPath, mergePath);
9449 if (serverCache.isCompleteForPath(serverCachePath)) {
9450 changedChildren_2 = changedChildren_2.set(mergePath, serverCache.getNode().getChild(serverCachePath));
9451 }
9452 });
9453 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_2, writesCache, completeCache, filterServerNode, accumulator);
9454 }
9455}
9456function viewProcessorListenComplete(viewProcessor, viewCache, path, writesCache, accumulator) {
9457 var oldServerNode = viewCache.serverCache;
9458 var newViewCache = viewCacheUpdateServerSnap(viewCache, oldServerNode.getNode(), oldServerNode.isFullyInitialized() || pathIsEmpty(path), oldServerNode.isFiltered());
9459 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, path, writesCache, NO_COMPLETE_CHILD_SOURCE, accumulator);
9460}
9461function viewProcessorRevertUserWrite(viewProcessor, viewCache, path, writesCache, completeServerCache, accumulator) {
9462 var complete;
9463 if (writeTreeRefShadowingWrite(writesCache, path) != null) {
9464 return viewCache;
9465 }
9466 else {
9467 var source = new WriteTreeCompleteChildSource(writesCache, viewCache, completeServerCache);
9468 var oldEventCache = viewCache.eventCache.getNode();
9469 var newEventCache = void 0;
9470 if (pathIsEmpty(path) || pathGetFront(path) === '.priority') {
9471 var newNode = void 0;
9472 if (viewCache.serverCache.isFullyInitialized()) {
9473 newNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9474 }
9475 else {
9476 var serverChildren = viewCache.serverCache.getNode();
9477 util.assert(serverChildren instanceof ChildrenNode, 'serverChildren would be complete if leaf node');
9478 newNode = writeTreeRefCalcCompleteEventChildren(writesCache, serverChildren);
9479 }
9480 newNode = newNode;
9481 newEventCache = viewProcessor.filter.updateFullNode(oldEventCache, newNode, accumulator);
9482 }
9483 else {
9484 var childKey = pathGetFront(path);
9485 var newChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9486 if (newChild == null &&
9487 viewCache.serverCache.isCompleteForChild(childKey)) {
9488 newChild = oldEventCache.getImmediateChild(childKey);
9489 }
9490 if (newChild != null) {
9491 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, newChild, pathPopFront(path), source, accumulator);
9492 }
9493 else if (viewCache.eventCache.getNode().hasChild(childKey)) {
9494 // No complete child available, delete the existing one, if any
9495 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, ChildrenNode.EMPTY_NODE, pathPopFront(path), source, accumulator);
9496 }
9497 else {
9498 newEventCache = oldEventCache;
9499 }
9500 if (newEventCache.isEmpty() &&
9501 viewCache.serverCache.isFullyInitialized()) {
9502 // We might have reverted all child writes. Maybe the old event was a leaf node
9503 complete = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9504 if (complete.isLeafNode()) {
9505 newEventCache = viewProcessor.filter.updateFullNode(newEventCache, complete, accumulator);
9506 }
9507 }
9508 }
9509 complete =
9510 viewCache.serverCache.isFullyInitialized() ||
9511 writeTreeRefShadowingWrite(writesCache, newEmptyPath()) != null;
9512 return viewCacheUpdateEventSnap(viewCache, newEventCache, complete, viewProcessor.filter.filtersNodes());
9513 }
9514}
9515
9516/**
9517 * @license
9518 * Copyright 2017 Google LLC
9519 *
9520 * Licensed under the Apache License, Version 2.0 (the "License");
9521 * you may not use this file except in compliance with the License.
9522 * You may obtain a copy of the License at
9523 *
9524 * http://www.apache.org/licenses/LICENSE-2.0
9525 *
9526 * Unless required by applicable law or agreed to in writing, software
9527 * distributed under the License is distributed on an "AS IS" BASIS,
9528 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9529 * See the License for the specific language governing permissions and
9530 * limitations under the License.
9531 */
9532/**
9533 * A view represents a specific location and query that has 1 or more event registrations.
9534 *
9535 * It does several things:
9536 * - Maintains the list of event registrations for this location/query.
9537 * - Maintains a cache of the data visible for this location/query.
9538 * - Applies new operations (via applyOperation), updates the cache, and based on the event
9539 * registrations returns the set of events to be raised.
9540 */
9541var View = /** @class */ (function () {
9542 function View(query_, initialViewCache) {
9543 this.query_ = query_;
9544 this.eventRegistrations_ = [];
9545 var params = this.query_._queryParams;
9546 var indexFilter = new IndexedFilter(params.getIndex());
9547 var filter = queryParamsGetNodeFilter(params);
9548 this.processor_ = newViewProcessor(filter);
9549 var initialServerCache = initialViewCache.serverCache;
9550 var initialEventCache = initialViewCache.eventCache;
9551 // Don't filter server node with other filter than index, wait for tagged listen
9552 var serverSnap = indexFilter.updateFullNode(ChildrenNode.EMPTY_NODE, initialServerCache.getNode(), null);
9553 var eventSnap = filter.updateFullNode(ChildrenNode.EMPTY_NODE, initialEventCache.getNode(), null);
9554 var newServerCache = new CacheNode(serverSnap, initialServerCache.isFullyInitialized(), indexFilter.filtersNodes());
9555 var newEventCache = new CacheNode(eventSnap, initialEventCache.isFullyInitialized(), filter.filtersNodes());
9556 this.viewCache_ = newViewCache(newEventCache, newServerCache);
9557 this.eventGenerator_ = new EventGenerator(this.query_);
9558 }
9559 Object.defineProperty(View.prototype, "query", {
9560 get: function () {
9561 return this.query_;
9562 },
9563 enumerable: false,
9564 configurable: true
9565 });
9566 return View;
9567}());
9568function viewGetServerCache(view) {
9569 return view.viewCache_.serverCache.getNode();
9570}
9571function viewGetCompleteNode(view) {
9572 return viewCacheGetCompleteEventSnap(view.viewCache_);
9573}
9574function viewGetCompleteServerCache(view, path) {
9575 var cache = viewCacheGetCompleteServerSnap(view.viewCache_);
9576 if (cache) {
9577 // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
9578 // we need to see if it contains the child we're interested in.
9579 if (view.query._queryParams.loadsAllData() ||
9580 (!pathIsEmpty(path) &&
9581 !cache.getImmediateChild(pathGetFront(path)).isEmpty())) {
9582 return cache.getChild(path);
9583 }
9584 }
9585 return null;
9586}
9587function viewIsEmpty(view) {
9588 return view.eventRegistrations_.length === 0;
9589}
9590function viewAddEventRegistration(view, eventRegistration) {
9591 view.eventRegistrations_.push(eventRegistration);
9592}
9593/**
9594 * @param eventRegistration - If null, remove all callbacks.
9595 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9596 * @returns Cancel events, if cancelError was provided.
9597 */
9598function viewRemoveEventRegistration(view, eventRegistration, cancelError) {
9599 var cancelEvents = [];
9600 if (cancelError) {
9601 util.assert(eventRegistration == null, 'A cancel should cancel all event registrations.');
9602 var path_1 = view.query._path;
9603 view.eventRegistrations_.forEach(function (registration) {
9604 var maybeEvent = registration.createCancelEvent(cancelError, path_1);
9605 if (maybeEvent) {
9606 cancelEvents.push(maybeEvent);
9607 }
9608 });
9609 }
9610 if (eventRegistration) {
9611 var remaining = [];
9612 for (var i = 0; i < view.eventRegistrations_.length; ++i) {
9613 var existing = view.eventRegistrations_[i];
9614 if (!existing.matches(eventRegistration)) {
9615 remaining.push(existing);
9616 }
9617 else if (eventRegistration.hasAnyCallback()) {
9618 // We're removing just this one
9619 remaining = remaining.concat(view.eventRegistrations_.slice(i + 1));
9620 break;
9621 }
9622 }
9623 view.eventRegistrations_ = remaining;
9624 }
9625 else {
9626 view.eventRegistrations_ = [];
9627 }
9628 return cancelEvents;
9629}
9630/**
9631 * Applies the given Operation, updates our cache, and returns the appropriate events.
9632 */
9633function viewApplyOperation(view, operation, writesCache, completeServerCache) {
9634 if (operation.type === OperationType.MERGE &&
9635 operation.source.queryId !== null) {
9636 util.assert(viewCacheGetCompleteServerSnap(view.viewCache_), 'We should always have a full cache before handling merges');
9637 util.assert(viewCacheGetCompleteEventSnap(view.viewCache_), 'Missing event cache, even though we have a server cache');
9638 }
9639 var oldViewCache = view.viewCache_;
9640 var result = viewProcessorApplyOperation(view.processor_, oldViewCache, operation, writesCache, completeServerCache);
9641 viewProcessorAssertIndexed(view.processor_, result.viewCache);
9642 util.assert(result.viewCache.serverCache.isFullyInitialized() ||
9643 !oldViewCache.serverCache.isFullyInitialized(), 'Once a server snap is complete, it should never go back');
9644 view.viewCache_ = result.viewCache;
9645 return viewGenerateEventsForChanges_(view, result.changes, result.viewCache.eventCache.getNode(), null);
9646}
9647function viewGetInitialEvents(view, registration) {
9648 var eventSnap = view.viewCache_.eventCache;
9649 var initialChanges = [];
9650 if (!eventSnap.getNode().isLeafNode()) {
9651 var eventNode = eventSnap.getNode();
9652 eventNode.forEachChild(PRIORITY_INDEX, function (key, childNode) {
9653 initialChanges.push(changeChildAdded(key, childNode));
9654 });
9655 }
9656 if (eventSnap.isFullyInitialized()) {
9657 initialChanges.push(changeValue(eventSnap.getNode()));
9658 }
9659 return viewGenerateEventsForChanges_(view, initialChanges, eventSnap.getNode(), registration);
9660}
9661function viewGenerateEventsForChanges_(view, changes, eventCache, eventRegistration) {
9662 var registrations = eventRegistration
9663 ? [eventRegistration]
9664 : view.eventRegistrations_;
9665 return eventGeneratorGenerateEventsForChanges(view.eventGenerator_, changes, eventCache, registrations);
9666}
9667
9668/**
9669 * @license
9670 * Copyright 2017 Google LLC
9671 *
9672 * Licensed under the Apache License, Version 2.0 (the "License");
9673 * you may not use this file except in compliance with the License.
9674 * You may obtain a copy of the License at
9675 *
9676 * http://www.apache.org/licenses/LICENSE-2.0
9677 *
9678 * Unless required by applicable law or agreed to in writing, software
9679 * distributed under the License is distributed on an "AS IS" BASIS,
9680 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9681 * See the License for the specific language governing permissions and
9682 * limitations under the License.
9683 */
9684var referenceConstructor$1;
9685/**
9686 * SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
9687 * maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
9688 * and user writes (set, transaction, update).
9689 *
9690 * It's responsible for:
9691 * - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
9692 * - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
9693 * applyUserOverwrite, etc.)
9694 */
9695var SyncPoint = /** @class */ (function () {
9696 function SyncPoint() {
9697 /**
9698 * The Views being tracked at this location in the tree, stored as a map where the key is a
9699 * queryId and the value is the View for that query.
9700 *
9701 * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
9702 */
9703 this.views = new Map();
9704 }
9705 return SyncPoint;
9706}());
9707function syncPointSetReferenceConstructor(val) {
9708 util.assert(!referenceConstructor$1, '__referenceConstructor has already been defined');
9709 referenceConstructor$1 = val;
9710}
9711function syncPointGetReferenceConstructor() {
9712 util.assert(referenceConstructor$1, 'Reference.ts has not been loaded');
9713 return referenceConstructor$1;
9714}
9715function syncPointIsEmpty(syncPoint) {
9716 return syncPoint.views.size === 0;
9717}
9718function syncPointApplyOperation(syncPoint, operation, writesCache, optCompleteServerCache) {
9719 var e_1, _a;
9720 var queryId = operation.source.queryId;
9721 if (queryId !== null) {
9722 var view = syncPoint.views.get(queryId);
9723 util.assert(view != null, 'SyncTree gave us an op for an invalid query.');
9724 return viewApplyOperation(view, operation, writesCache, optCompleteServerCache);
9725 }
9726 else {
9727 var events = [];
9728 try {
9729 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9730 var view = _c.value;
9731 events = events.concat(viewApplyOperation(view, operation, writesCache, optCompleteServerCache));
9732 }
9733 }
9734 catch (e_1_1) { e_1 = { error: e_1_1 }; }
9735 finally {
9736 try {
9737 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9738 }
9739 finally { if (e_1) throw e_1.error; }
9740 }
9741 return events;
9742 }
9743}
9744/**
9745 * Get a view for the specified query.
9746 *
9747 * @param query - The query to return a view for
9748 * @param writesCache
9749 * @param serverCache
9750 * @param serverCacheComplete
9751 * @returns Events to raise.
9752 */
9753function syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete) {
9754 var queryId = query._queryIdentifier;
9755 var view = syncPoint.views.get(queryId);
9756 if (!view) {
9757 // TODO: make writesCache take flag for complete server node
9758 var eventCache = writeTreeRefCalcCompleteEventCache(writesCache, serverCacheComplete ? serverCache : null);
9759 var eventCacheComplete = false;
9760 if (eventCache) {
9761 eventCacheComplete = true;
9762 }
9763 else if (serverCache instanceof ChildrenNode) {
9764 eventCache = writeTreeRefCalcCompleteEventChildren(writesCache, serverCache);
9765 eventCacheComplete = false;
9766 }
9767 else {
9768 eventCache = ChildrenNode.EMPTY_NODE;
9769 eventCacheComplete = false;
9770 }
9771 var viewCache = newViewCache(new CacheNode(eventCache, eventCacheComplete, false), new CacheNode(serverCache, serverCacheComplete, false));
9772 return new View(query, viewCache);
9773 }
9774 return view;
9775}
9776/**
9777 * Add an event callback for the specified query.
9778 *
9779 * @param query
9780 * @param eventRegistration
9781 * @param writesCache
9782 * @param serverCache - Complete server cache, if we have it.
9783 * @param serverCacheComplete
9784 * @returns Events to raise.
9785 */
9786function syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete) {
9787 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9788 if (!syncPoint.views.has(query._queryIdentifier)) {
9789 syncPoint.views.set(query._queryIdentifier, view);
9790 }
9791 // This is guaranteed to exist now, we just created anything that was missing
9792 viewAddEventRegistration(view, eventRegistration);
9793 return viewGetInitialEvents(view, eventRegistration);
9794}
9795/**
9796 * Remove event callback(s). Return cancelEvents if a cancelError is specified.
9797 *
9798 * If query is the default query, we'll check all views for the specified eventRegistration.
9799 * If eventRegistration is null, we'll remove all callbacks for the specified view(s).
9800 *
9801 * @param eventRegistration - If null, remove all callbacks.
9802 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9803 * @returns removed queries and any cancel events
9804 */
9805function syncPointRemoveEventRegistration(syncPoint, query, eventRegistration, cancelError) {
9806 var e_2, _a;
9807 var queryId = query._queryIdentifier;
9808 var removed = [];
9809 var cancelEvents = [];
9810 var hadCompleteView = syncPointHasCompleteView(syncPoint);
9811 if (queryId === 'default') {
9812 try {
9813 // When you do ref.off(...), we search all views for the registration to remove.
9814 for (var _b = tslib.__values(syncPoint.views.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
9815 var _d = tslib.__read(_c.value, 2), viewQueryId = _d[0], view = _d[1];
9816 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9817 if (viewIsEmpty(view)) {
9818 syncPoint.views.delete(viewQueryId);
9819 // We'll deal with complete views later.
9820 if (!view.query._queryParams.loadsAllData()) {
9821 removed.push(view.query);
9822 }
9823 }
9824 }
9825 }
9826 catch (e_2_1) { e_2 = { error: e_2_1 }; }
9827 finally {
9828 try {
9829 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9830 }
9831 finally { if (e_2) throw e_2.error; }
9832 }
9833 }
9834 else {
9835 // remove the callback from the specific view.
9836 var view = syncPoint.views.get(queryId);
9837 if (view) {
9838 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9839 if (viewIsEmpty(view)) {
9840 syncPoint.views.delete(queryId);
9841 // We'll deal with complete views later.
9842 if (!view.query._queryParams.loadsAllData()) {
9843 removed.push(view.query);
9844 }
9845 }
9846 }
9847 }
9848 if (hadCompleteView && !syncPointHasCompleteView(syncPoint)) {
9849 // We removed our last complete view.
9850 removed.push(new (syncPointGetReferenceConstructor())(query._repo, query._path));
9851 }
9852 return { removed: removed, events: cancelEvents };
9853}
9854function syncPointGetQueryViews(syncPoint) {
9855 var e_3, _a;
9856 var result = [];
9857 try {
9858 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9859 var view = _c.value;
9860 if (!view.query._queryParams.loadsAllData()) {
9861 result.push(view);
9862 }
9863 }
9864 }
9865 catch (e_3_1) { e_3 = { error: e_3_1 }; }
9866 finally {
9867 try {
9868 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9869 }
9870 finally { if (e_3) throw e_3.error; }
9871 }
9872 return result;
9873}
9874/**
9875 * @param path - The path to the desired complete snapshot
9876 * @returns A complete cache, if it exists
9877 */
9878function syncPointGetCompleteServerCache(syncPoint, path) {
9879 var e_4, _a;
9880 var serverCache = null;
9881 try {
9882 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9883 var view = _c.value;
9884 serverCache = serverCache || viewGetCompleteServerCache(view, path);
9885 }
9886 }
9887 catch (e_4_1) { e_4 = { error: e_4_1 }; }
9888 finally {
9889 try {
9890 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9891 }
9892 finally { if (e_4) throw e_4.error; }
9893 }
9894 return serverCache;
9895}
9896function syncPointViewForQuery(syncPoint, query) {
9897 var params = query._queryParams;
9898 if (params.loadsAllData()) {
9899 return syncPointGetCompleteView(syncPoint);
9900 }
9901 else {
9902 var queryId = query._queryIdentifier;
9903 return syncPoint.views.get(queryId);
9904 }
9905}
9906function syncPointViewExistsForQuery(syncPoint, query) {
9907 return syncPointViewForQuery(syncPoint, query) != null;
9908}
9909function syncPointHasCompleteView(syncPoint) {
9910 return syncPointGetCompleteView(syncPoint) != null;
9911}
9912function syncPointGetCompleteView(syncPoint) {
9913 var e_5, _a;
9914 try {
9915 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9916 var view = _c.value;
9917 if (view.query._queryParams.loadsAllData()) {
9918 return view;
9919 }
9920 }
9921 }
9922 catch (e_5_1) { e_5 = { error: e_5_1 }; }
9923 finally {
9924 try {
9925 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9926 }
9927 finally { if (e_5) throw e_5.error; }
9928 }
9929 return null;
9930}
9931
9932/**
9933 * @license
9934 * Copyright 2017 Google LLC
9935 *
9936 * Licensed under the Apache License, Version 2.0 (the "License");
9937 * you may not use this file except in compliance with the License.
9938 * You may obtain a copy of the License at
9939 *
9940 * http://www.apache.org/licenses/LICENSE-2.0
9941 *
9942 * Unless required by applicable law or agreed to in writing, software
9943 * distributed under the License is distributed on an "AS IS" BASIS,
9944 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9945 * See the License for the specific language governing permissions and
9946 * limitations under the License.
9947 */
9948var referenceConstructor;
9949function syncTreeSetReferenceConstructor(val) {
9950 util.assert(!referenceConstructor, '__referenceConstructor has already been defined');
9951 referenceConstructor = val;
9952}
9953function syncTreeGetReferenceConstructor() {
9954 util.assert(referenceConstructor, 'Reference.ts has not been loaded');
9955 return referenceConstructor;
9956}
9957/**
9958 * Static tracker for next query tag.
9959 */
9960var syncTreeNextQueryTag_ = 1;
9961/**
9962 * SyncTree is the central class for managing event callback registration, data caching, views
9963 * (query processing), and event generation. There are typically two SyncTree instances for
9964 * each Repo, one for the normal Firebase data, and one for the .info data.
9965 *
9966 * It has a number of responsibilities, including:
9967 * - Tracking all user event callbacks (registered via addEventRegistration() and removeEventRegistration()).
9968 * - Applying and caching data changes for user set(), transaction(), and update() calls
9969 * (applyUserOverwrite(), applyUserMerge()).
9970 * - Applying and caching data changes for server data changes (applyServerOverwrite(),
9971 * applyServerMerge()).
9972 * - Generating user-facing events for server and user changes (all of the apply* methods
9973 * return the set of events that need to be raised as a result).
9974 * - Maintaining the appropriate set of server listens to ensure we are always subscribed
9975 * to the correct set of paths and queries to satisfy the current set of user event
9976 * callbacks (listens are started/stopped using the provided listenProvider).
9977 *
9978 * NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
9979 * events are returned to the caller rather than raised synchronously.
9980 *
9981 */
9982var SyncTree = /** @class */ (function () {
9983 /**
9984 * @param listenProvider_ - Used by SyncTree to start / stop listening
9985 * to server data.
9986 */
9987 function SyncTree(listenProvider_) {
9988 this.listenProvider_ = listenProvider_;
9989 /**
9990 * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
9991 */
9992 this.syncPointTree_ = new ImmutableTree(null);
9993 /**
9994 * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
9995 */
9996 this.pendingWriteTree_ = newWriteTree();
9997 this.tagToQueryMap = new Map();
9998 this.queryToTagMap = new Map();
9999 }
10000 return SyncTree;
10001}());
10002/**
10003 * Apply the data changes for a user-generated set() or transaction() call.
10004 *
10005 * @returns Events to raise.
10006 */
10007function syncTreeApplyUserOverwrite(syncTree, path, newData, writeId, visible) {
10008 // Record pending write.
10009 writeTreeAddOverwrite(syncTree.pendingWriteTree_, path, newData, writeId, visible);
10010 if (!visible) {
10011 return [];
10012 }
10013 else {
10014 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceUser(), path, newData));
10015 }
10016}
10017/**
10018 * Apply the data from a user-generated update() call
10019 *
10020 * @returns Events to raise.
10021 */
10022function syncTreeApplyUserMerge(syncTree, path, changedChildren, writeId) {
10023 // Record pending merge.
10024 writeTreeAddMerge(syncTree.pendingWriteTree_, path, changedChildren, writeId);
10025 var changeTree = ImmutableTree.fromObject(changedChildren);
10026 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceUser(), path, changeTree));
10027}
10028/**
10029 * Acknowledge a pending user write that was previously registered with applyUserOverwrite() or applyUserMerge().
10030 *
10031 * @param revert - True if the given write failed and needs to be reverted
10032 * @returns Events to raise.
10033 */
10034function syncTreeAckUserWrite(syncTree, writeId, revert) {
10035 if (revert === void 0) { revert = false; }
10036 var write = writeTreeGetWrite(syncTree.pendingWriteTree_, writeId);
10037 var needToReevaluate = writeTreeRemoveWrite(syncTree.pendingWriteTree_, writeId);
10038 if (!needToReevaluate) {
10039 return [];
10040 }
10041 else {
10042 var affectedTree_1 = new ImmutableTree(null);
10043 if (write.snap != null) {
10044 // overwrite
10045 affectedTree_1 = affectedTree_1.set(newEmptyPath(), true);
10046 }
10047 else {
10048 each(write.children, function (pathString) {
10049 affectedTree_1 = affectedTree_1.set(new Path(pathString), true);
10050 });
10051 }
10052 return syncTreeApplyOperationToSyncPoints_(syncTree, new AckUserWrite(write.path, affectedTree_1, revert));
10053 }
10054}
10055/**
10056 * Apply new server data for the specified path..
10057 *
10058 * @returns Events to raise.
10059 */
10060function syncTreeApplyServerOverwrite(syncTree, path, newData) {
10061 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceServer(), path, newData));
10062}
10063/**
10064 * Apply new server data to be merged in at the specified path.
10065 *
10066 * @returns Events to raise.
10067 */
10068function syncTreeApplyServerMerge(syncTree, path, changedChildren) {
10069 var changeTree = ImmutableTree.fromObject(changedChildren);
10070 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceServer(), path, changeTree));
10071}
10072/**
10073 * Apply a listen complete for a query
10074 *
10075 * @returns Events to raise.
10076 */
10077function syncTreeApplyListenComplete(syncTree, path) {
10078 return syncTreeApplyOperationToSyncPoints_(syncTree, new ListenComplete(newOperationSourceServer(), path));
10079}
10080/**
10081 * Apply a listen complete for a tagged query
10082 *
10083 * @returns Events to raise.
10084 */
10085function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
10086 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10087 if (queryKey) {
10088 var r = syncTreeParseQueryKey_(queryKey);
10089 var queryPath = r.path, queryId = r.queryId;
10090 var relativePath = newRelativePath(queryPath, path);
10091 var op = new ListenComplete(newOperationSourceServerTaggedQuery(queryId), relativePath);
10092 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10093 }
10094 else {
10095 // We've already removed the query. No big deal, ignore the update
10096 return [];
10097 }
10098}
10099/**
10100 * Remove event callback(s).
10101 *
10102 * If query is the default query, we'll check all queries for the specified eventRegistration.
10103 * If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
10104 *
10105 * @param eventRegistration - If null, all callbacks are removed.
10106 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
10107 * @returns Cancel events, if cancelError was provided.
10108 */
10109function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError) {
10110 // Find the syncPoint first. Then deal with whether or not it has matching listeners
10111 var path = query._path;
10112 var maybeSyncPoint = syncTree.syncPointTree_.get(path);
10113 var cancelEvents = [];
10114 // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
10115 // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
10116 // not loadsAllData().
10117 if (maybeSyncPoint &&
10118 (query._queryIdentifier === 'default' ||
10119 syncPointViewExistsForQuery(maybeSyncPoint, query))) {
10120 var removedAndEvents = syncPointRemoveEventRegistration(maybeSyncPoint, query, eventRegistration, cancelError);
10121 if (syncPointIsEmpty(maybeSyncPoint)) {
10122 syncTree.syncPointTree_ = syncTree.syncPointTree_.remove(path);
10123 }
10124 var removed = removedAndEvents.removed;
10125 cancelEvents = removedAndEvents.events;
10126 // We may have just removed one of many listeners and can short-circuit this whole process
10127 // We may also not have removed a default listener, in which case all of the descendant listeners should already be
10128 // properly set up.
10129 //
10130 // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
10131 // queryId === 'default'
10132 var removingDefault = -1 !==
10133 removed.findIndex(function (query) {
10134 return query._queryParams.loadsAllData();
10135 });
10136 var covered = syncTree.syncPointTree_.findOnPath(path, function (relativePath, parentSyncPoint) {
10137 return syncPointHasCompleteView(parentSyncPoint);
10138 });
10139 if (removingDefault && !covered) {
10140 var subtree = syncTree.syncPointTree_.subtree(path);
10141 // There are potentially child listeners. Determine what if any listens we need to send before executing the
10142 // removal
10143 if (!subtree.isEmpty()) {
10144 // We need to fold over our subtree and collect the listeners to send
10145 var newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
10146 // Ok, we've collected all the listens we need. Set them up.
10147 for (var i = 0; i < newViews.length; ++i) {
10148 var view = newViews[i], newQuery = view.query;
10149 var listener = syncTreeCreateListenerForView_(syncTree, view);
10150 syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery_(syncTree, newQuery), listener.hashFn, listener.onComplete);
10151 }
10152 }
10153 }
10154 // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
10155 // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
10156 // Also, note that if we have a cancelError, it's already been removed at the provider level.
10157 if (!covered && removed.length > 0 && !cancelError) {
10158 // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
10159 // default. Otherwise, we need to iterate through and cancel each individual query
10160 if (removingDefault) {
10161 // We don't tag default listeners
10162 var defaultTag = null;
10163 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
10164 }
10165 else {
10166 removed.forEach(function (queryToRemove) {
10167 var tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
10168 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
10169 });
10170 }
10171 }
10172 // Now, clear all of the tags we're tracking for the removed listens
10173 syncTreeRemoveTags_(syncTree, removed);
10174 }
10175 return cancelEvents;
10176}
10177/**
10178 * Apply new server data for the specified tagged query.
10179 *
10180 * @returns Events to raise.
10181 */
10182function syncTreeApplyTaggedQueryOverwrite(syncTree, path, snap, tag) {
10183 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10184 if (queryKey != null) {
10185 var r = syncTreeParseQueryKey_(queryKey);
10186 var queryPath = r.path, queryId = r.queryId;
10187 var relativePath = newRelativePath(queryPath, path);
10188 var op = new Overwrite(newOperationSourceServerTaggedQuery(queryId), relativePath, snap);
10189 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10190 }
10191 else {
10192 // Query must have been removed already
10193 return [];
10194 }
10195}
10196/**
10197 * Apply server data to be merged in for the specified tagged query.
10198 *
10199 * @returns Events to raise.
10200 */
10201function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
10202 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10203 if (queryKey) {
10204 var r = syncTreeParseQueryKey_(queryKey);
10205 var queryPath = r.path, queryId = r.queryId;
10206 var relativePath = newRelativePath(queryPath, path);
10207 var changeTree = ImmutableTree.fromObject(changedChildren);
10208 var op = new Merge(newOperationSourceServerTaggedQuery(queryId), relativePath, changeTree);
10209 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10210 }
10211 else {
10212 // We've already removed the query. No big deal, ignore the update
10213 return [];
10214 }
10215}
10216/**
10217 * Add an event callback for the specified query.
10218 *
10219 * @returns Events to raise.
10220 */
10221function syncTreeAddEventRegistration(syncTree, query, eventRegistration) {
10222 var path = query._path;
10223 var serverCache = null;
10224 var foundAncestorDefaultView = false;
10225 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10226 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10227 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10228 var relativePath = newRelativePath(pathToSyncPoint, path);
10229 serverCache =
10230 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10231 foundAncestorDefaultView =
10232 foundAncestorDefaultView || syncPointHasCompleteView(sp);
10233 });
10234 var syncPoint = syncTree.syncPointTree_.get(path);
10235 if (!syncPoint) {
10236 syncPoint = new SyncPoint();
10237 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10238 }
10239 else {
10240 foundAncestorDefaultView =
10241 foundAncestorDefaultView || syncPointHasCompleteView(syncPoint);
10242 serverCache =
10243 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10244 }
10245 var serverCacheComplete;
10246 if (serverCache != null) {
10247 serverCacheComplete = true;
10248 }
10249 else {
10250 serverCacheComplete = false;
10251 serverCache = ChildrenNode.EMPTY_NODE;
10252 var subtree = syncTree.syncPointTree_.subtree(path);
10253 subtree.foreachChild(function (childName, childSyncPoint) {
10254 var completeCache = syncPointGetCompleteServerCache(childSyncPoint, newEmptyPath());
10255 if (completeCache) {
10256 serverCache = serverCache.updateImmediateChild(childName, completeCache);
10257 }
10258 });
10259 }
10260 var viewAlreadyExists = syncPointViewExistsForQuery(syncPoint, query);
10261 if (!viewAlreadyExists && !query._queryParams.loadsAllData()) {
10262 // We need to track a tag for this query
10263 var queryKey = syncTreeMakeQueryKey_(query);
10264 util.assert(!syncTree.queryToTagMap.has(queryKey), 'View does not exist, but we have a tag');
10265 var tag = syncTreeGetNextQueryTag_();
10266 syncTree.queryToTagMap.set(queryKey, tag);
10267 syncTree.tagToQueryMap.set(tag, queryKey);
10268 }
10269 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
10270 var events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
10271 if (!viewAlreadyExists && !foundAncestorDefaultView) {
10272 var view = syncPointViewForQuery(syncPoint, query);
10273 events = events.concat(syncTreeSetupListener_(syncTree, query, view));
10274 }
10275 return events;
10276}
10277/**
10278 * Returns a complete cache, if we have one, of the data at a particular path. If the location does not have a
10279 * listener above it, we will get a false "null". This shouldn't be a problem because transactions will always
10280 * have a listener above, and atomic operations would correctly show a jitter of <increment value> ->
10281 * <incremented total> as the write is applied locally and then acknowledged at the server.
10282 *
10283 * Note: this method will *include* hidden writes from transaction with applyLocally set to false.
10284 *
10285 * @param path - The path to the data we want
10286 * @param writeIdsToExclude - A specific set to be excluded
10287 */
10288function syncTreeCalcCompleteEventCache(syncTree, path, writeIdsToExclude) {
10289 var includeHiddenSets = true;
10290 var writeTree = syncTree.pendingWriteTree_;
10291 var serverCache = syncTree.syncPointTree_.findOnPath(path, function (pathSoFar, syncPoint) {
10292 var relativePath = newRelativePath(pathSoFar, path);
10293 var serverCache = syncPointGetCompleteServerCache(syncPoint, relativePath);
10294 if (serverCache) {
10295 return serverCache;
10296 }
10297 });
10298 return writeTreeCalcCompleteEventCache(writeTree, path, serverCache, writeIdsToExclude, includeHiddenSets);
10299}
10300function syncTreeGetServerValue(syncTree, query) {
10301 var path = query._path;
10302 var serverCache = null;
10303 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10304 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10305 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10306 var relativePath = newRelativePath(pathToSyncPoint, path);
10307 serverCache =
10308 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10309 });
10310 var syncPoint = syncTree.syncPointTree_.get(path);
10311 if (!syncPoint) {
10312 syncPoint = new SyncPoint();
10313 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10314 }
10315 else {
10316 serverCache =
10317 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10318 }
10319 var serverCacheComplete = serverCache != null;
10320 var serverCacheNode = serverCacheComplete
10321 ? new CacheNode(serverCache, true, false)
10322 : null;
10323 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, query._path);
10324 var view = syncPointGetView(syncPoint, query, writesCache, serverCacheComplete ? serverCacheNode.getNode() : ChildrenNode.EMPTY_NODE, serverCacheComplete);
10325 return viewGetCompleteNode(view);
10326}
10327/**
10328 * A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
10329 *
10330 * NOTES:
10331 * - Descendant SyncPoints will be visited first (since we raise events depth-first).
10332 *
10333 * - We call applyOperation() on each SyncPoint passing three things:
10334 * 1. A version of the Operation that has been made relative to the SyncPoint location.
10335 * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
10336 * 3. A snapshot Node with cached server data, if we have it.
10337 *
10338 * - We concatenate all of the events returned by each SyncPoint and return the result.
10339 */
10340function syncTreeApplyOperationToSyncPoints_(syncTree, operation) {
10341 return syncTreeApplyOperationHelper_(operation, syncTree.syncPointTree_,
10342 /*serverCache=*/ null, writeTreeChildWrites(syncTree.pendingWriteTree_, newEmptyPath()));
10343}
10344/**
10345 * Recursive helper for applyOperationToSyncPoints_
10346 */
10347function syncTreeApplyOperationHelper_(operation, syncPointTree, serverCache, writesCache) {
10348 if (pathIsEmpty(operation.path)) {
10349 return syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache);
10350 }
10351 else {
10352 var syncPoint = syncPointTree.get(newEmptyPath());
10353 // If we don't have cached server data, see if we can get it from this SyncPoint.
10354 if (serverCache == null && syncPoint != null) {
10355 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10356 }
10357 var events = [];
10358 var childName = pathGetFront(operation.path);
10359 var childOperation = operation.operationForChild(childName);
10360 var childTree = syncPointTree.children.get(childName);
10361 if (childTree && childOperation) {
10362 var childServerCache = serverCache
10363 ? serverCache.getImmediateChild(childName)
10364 : null;
10365 var childWritesCache = writeTreeRefChild(writesCache, childName);
10366 events = events.concat(syncTreeApplyOperationHelper_(childOperation, childTree, childServerCache, childWritesCache));
10367 }
10368 if (syncPoint) {
10369 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10370 }
10371 return events;
10372 }
10373}
10374/**
10375 * Recursive helper for applyOperationToSyncPoints_
10376 */
10377function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache) {
10378 var syncPoint = syncPointTree.get(newEmptyPath());
10379 // If we don't have cached server data, see if we can get it from this SyncPoint.
10380 if (serverCache == null && syncPoint != null) {
10381 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10382 }
10383 var events = [];
10384 syncPointTree.children.inorderTraversal(function (childName, childTree) {
10385 var childServerCache = serverCache
10386 ? serverCache.getImmediateChild(childName)
10387 : null;
10388 var childWritesCache = writeTreeRefChild(writesCache, childName);
10389 var childOperation = operation.operationForChild(childName);
10390 if (childOperation) {
10391 events = events.concat(syncTreeApplyOperationDescendantsHelper_(childOperation, childTree, childServerCache, childWritesCache));
10392 }
10393 });
10394 if (syncPoint) {
10395 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10396 }
10397 return events;
10398}
10399function syncTreeCreateListenerForView_(syncTree, view) {
10400 var query = view.query;
10401 var tag = syncTreeTagForQuery_(syncTree, query);
10402 return {
10403 hashFn: function () {
10404 var cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
10405 return cache.hash();
10406 },
10407 onComplete: function (status) {
10408 if (status === 'ok') {
10409 if (tag) {
10410 return syncTreeApplyTaggedListenComplete(syncTree, query._path, tag);
10411 }
10412 else {
10413 return syncTreeApplyListenComplete(syncTree, query._path);
10414 }
10415 }
10416 else {
10417 // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
10418 // Note that this may need to be scoped to just this listener if we change permissions on filtered children
10419 var error = errorForServerCode(status, query);
10420 return syncTreeRemoveEventRegistration(syncTree, query,
10421 /*eventRegistration*/ null, error);
10422 }
10423 }
10424 };
10425}
10426/**
10427 * Return the tag associated with the given query.
10428 */
10429function syncTreeTagForQuery_(syncTree, query) {
10430 var queryKey = syncTreeMakeQueryKey_(query);
10431 return syncTree.queryToTagMap.get(queryKey);
10432}
10433/**
10434 * Given a query, computes a "queryKey" suitable for use in our queryToTagMap_.
10435 */
10436function syncTreeMakeQueryKey_(query) {
10437 return query._path.toString() + '$' + query._queryIdentifier;
10438}
10439/**
10440 * Return the query associated with the given tag, if we have one
10441 */
10442function syncTreeQueryKeyForTag_(syncTree, tag) {
10443 return syncTree.tagToQueryMap.get(tag);
10444}
10445/**
10446 * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId.
10447 */
10448function syncTreeParseQueryKey_(queryKey) {
10449 var splitIndex = queryKey.indexOf('$');
10450 util.assert(splitIndex !== -1 && splitIndex < queryKey.length - 1, 'Bad queryKey.');
10451 return {
10452 queryId: queryKey.substr(splitIndex + 1),
10453 path: new Path(queryKey.substr(0, splitIndex))
10454 };
10455}
10456/**
10457 * A helper method to apply tagged operations
10458 */
10459function syncTreeApplyTaggedOperation_(syncTree, queryPath, operation) {
10460 var syncPoint = syncTree.syncPointTree_.get(queryPath);
10461 util.assert(syncPoint, "Missing sync point for query tag that we're tracking");
10462 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, queryPath);
10463 return syncPointApplyOperation(syncPoint, operation, writesCache, null);
10464}
10465/**
10466 * This collapses multiple unfiltered views into a single view, since we only need a single
10467 * listener for them.
10468 */
10469function syncTreeCollectDistinctViewsForSubTree_(subtree) {
10470 return subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10471 if (maybeChildSyncPoint && syncPointHasCompleteView(maybeChildSyncPoint)) {
10472 var completeView = syncPointGetCompleteView(maybeChildSyncPoint);
10473 return [completeView];
10474 }
10475 else {
10476 // No complete view here, flatten any deeper listens into an array
10477 var views_1 = [];
10478 if (maybeChildSyncPoint) {
10479 views_1 = syncPointGetQueryViews(maybeChildSyncPoint);
10480 }
10481 each(childMap, function (_key, childViews) {
10482 views_1 = views_1.concat(childViews);
10483 });
10484 return views_1;
10485 }
10486 });
10487}
10488/**
10489 * Normalizes a query to a query we send the server for listening
10490 *
10491 * @returns The normalized query
10492 */
10493function syncTreeQueryForListening_(query) {
10494 if (query._queryParams.loadsAllData() && !query._queryParams.isDefault()) {
10495 // We treat queries that load all data as default queries
10496 // Cast is necessary because ref() technically returns Firebase which is actually fb.api.Firebase which inherits
10497 // from Query
10498 return new (syncTreeGetReferenceConstructor())(query._repo, query._path);
10499 }
10500 else {
10501 return query;
10502 }
10503}
10504function syncTreeRemoveTags_(syncTree, queries) {
10505 for (var j = 0; j < queries.length; ++j) {
10506 var removedQuery = queries[j];
10507 if (!removedQuery._queryParams.loadsAllData()) {
10508 // We should have a tag for this
10509 var removedQueryKey = syncTreeMakeQueryKey_(removedQuery);
10510 var removedQueryTag = syncTree.queryToTagMap.get(removedQueryKey);
10511 syncTree.queryToTagMap.delete(removedQueryKey);
10512 syncTree.tagToQueryMap.delete(removedQueryTag);
10513 }
10514 }
10515}
10516/**
10517 * Static accessor for query tags.
10518 */
10519function syncTreeGetNextQueryTag_() {
10520 return syncTreeNextQueryTag_++;
10521}
10522/**
10523 * For a given new listen, manage the de-duplication of outstanding subscriptions.
10524 *
10525 * @returns This method can return events to support synchronous data sources
10526 */
10527function syncTreeSetupListener_(syncTree, query, view) {
10528 var path = query._path;
10529 var tag = syncTreeTagForQuery_(syncTree, query);
10530 var listener = syncTreeCreateListenerForView_(syncTree, view);
10531 var events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10532 var subtree = syncTree.syncPointTree_.subtree(path);
10533 // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
10534 // may need to shadow other listens as well.
10535 if (tag) {
10536 util.assert(!syncPointHasCompleteView(subtree.value), "If we're adding a query, it shouldn't be shadowed");
10537 }
10538 else {
10539 // Shadow everything at or below this location, this is a default listener.
10540 var queriesToStop = subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10541 if (!pathIsEmpty(relativePath) &&
10542 maybeChildSyncPoint &&
10543 syncPointHasCompleteView(maybeChildSyncPoint)) {
10544 return [syncPointGetCompleteView(maybeChildSyncPoint).query];
10545 }
10546 else {
10547 // No default listener here, flatten any deeper queries into an array
10548 var queries_1 = [];
10549 if (maybeChildSyncPoint) {
10550 queries_1 = queries_1.concat(syncPointGetQueryViews(maybeChildSyncPoint).map(function (view) { return view.query; }));
10551 }
10552 each(childMap, function (_key, childQueries) {
10553 queries_1 = queries_1.concat(childQueries);
10554 });
10555 return queries_1;
10556 }
10557 });
10558 for (var i = 0; i < queriesToStop.length; ++i) {
10559 var queryToStop = queriesToStop[i];
10560 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery_(syncTree, queryToStop));
10561 }
10562 }
10563 return events;
10564}
10565
10566/**
10567 * @license
10568 * Copyright 2017 Google LLC
10569 *
10570 * Licensed under the Apache License, Version 2.0 (the "License");
10571 * you may not use this file except in compliance with the License.
10572 * You may obtain a copy of the License at
10573 *
10574 * http://www.apache.org/licenses/LICENSE-2.0
10575 *
10576 * Unless required by applicable law or agreed to in writing, software
10577 * distributed under the License is distributed on an "AS IS" BASIS,
10578 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10579 * See the License for the specific language governing permissions and
10580 * limitations under the License.
10581 */
10582var ExistingValueProvider = /** @class */ (function () {
10583 function ExistingValueProvider(node_) {
10584 this.node_ = node_;
10585 }
10586 ExistingValueProvider.prototype.getImmediateChild = function (childName) {
10587 var child = this.node_.getImmediateChild(childName);
10588 return new ExistingValueProvider(child);
10589 };
10590 ExistingValueProvider.prototype.node = function () {
10591 return this.node_;
10592 };
10593 return ExistingValueProvider;
10594}());
10595var DeferredValueProvider = /** @class */ (function () {
10596 function DeferredValueProvider(syncTree, path) {
10597 this.syncTree_ = syncTree;
10598 this.path_ = path;
10599 }
10600 DeferredValueProvider.prototype.getImmediateChild = function (childName) {
10601 var childPath = pathChild(this.path_, childName);
10602 return new DeferredValueProvider(this.syncTree_, childPath);
10603 };
10604 DeferredValueProvider.prototype.node = function () {
10605 return syncTreeCalcCompleteEventCache(this.syncTree_, this.path_);
10606 };
10607 return DeferredValueProvider;
10608}());
10609/**
10610 * Generate placeholders for deferred values.
10611 */
10612var generateWithValues = function (values) {
10613 values = values || {};
10614 values['timestamp'] = values['timestamp'] || new Date().getTime();
10615 return values;
10616};
10617/**
10618 * Value to use when firing local events. When writing server values, fire
10619 * local events with an approximate value, otherwise return value as-is.
10620 */
10621var resolveDeferredLeafValue = function (value, existingVal, serverValues) {
10622 if (!value || typeof value !== 'object') {
10623 return value;
10624 }
10625 util.assert('.sv' in value, 'Unexpected leaf node or priority contents');
10626 if (typeof value['.sv'] === 'string') {
10627 return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
10628 }
10629 else if (typeof value['.sv'] === 'object') {
10630 return resolveComplexDeferredValue(value['.sv'], existingVal);
10631 }
10632 else {
10633 util.assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
10634 }
10635};
10636var resolveScalarDeferredValue = function (op, existing, serverValues) {
10637 switch (op) {
10638 case 'timestamp':
10639 return serverValues['timestamp'];
10640 default:
10641 util.assert(false, 'Unexpected server value: ' + op);
10642 }
10643};
10644var resolveComplexDeferredValue = function (op, existing, unused) {
10645 if (!op.hasOwnProperty('increment')) {
10646 util.assert(false, 'Unexpected server value: ' + JSON.stringify(op, null, 2));
10647 }
10648 var delta = op['increment'];
10649 if (typeof delta !== 'number') {
10650 util.assert(false, 'Unexpected increment value: ' + delta);
10651 }
10652 var existingNode = existing.node();
10653 util.assert(existingNode !== null && typeof existingNode !== 'undefined', 'Expected ChildrenNode.EMPTY_NODE for nulls');
10654 // Incrementing a non-number sets the value to the incremented amount
10655 if (!existingNode.isLeafNode()) {
10656 return delta;
10657 }
10658 var leaf = existingNode;
10659 var existingVal = leaf.getValue();
10660 if (typeof existingVal !== 'number') {
10661 return delta;
10662 }
10663 // No need to do over/underflow arithmetic here because JS only handles floats under the covers
10664 return existingVal + delta;
10665};
10666/**
10667 * Recursively replace all deferred values and priorities in the tree with the
10668 * specified generated replacement values.
10669 * @param path - path to which write is relative
10670 * @param node - new data written at path
10671 * @param syncTree - current data
10672 */
10673var resolveDeferredValueTree = function (path, node, syncTree, serverValues) {
10674 return resolveDeferredValue(node, new DeferredValueProvider(syncTree, path), serverValues);
10675};
10676/**
10677 * Recursively replace all deferred values and priorities in the node with the
10678 * specified generated replacement values. If there are no server values in the node,
10679 * it'll be returned as-is.
10680 */
10681var resolveDeferredValueSnapshot = function (node, existing, serverValues) {
10682 return resolveDeferredValue(node, new ExistingValueProvider(existing), serverValues);
10683};
10684function resolveDeferredValue(node, existingVal, serverValues) {
10685 var rawPri = node.getPriority().val();
10686 var priority = resolveDeferredLeafValue(rawPri, existingVal.getImmediateChild('.priority'), serverValues);
10687 var newNode;
10688 if (node.isLeafNode()) {
10689 var leafNode = node;
10690 var value = resolveDeferredLeafValue(leafNode.getValue(), existingVal, serverValues);
10691 if (value !== leafNode.getValue() ||
10692 priority !== leafNode.getPriority().val()) {
10693 return new LeafNode(value, nodeFromJSON(priority));
10694 }
10695 else {
10696 return node;
10697 }
10698 }
10699 else {
10700 var childrenNode = node;
10701 newNode = childrenNode;
10702 if (priority !== childrenNode.getPriority().val()) {
10703 newNode = newNode.updatePriority(new LeafNode(priority));
10704 }
10705 childrenNode.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
10706 var newChildNode = resolveDeferredValue(childNode, existingVal.getImmediateChild(childName), serverValues);
10707 if (newChildNode !== childNode) {
10708 newNode = newNode.updateImmediateChild(childName, newChildNode);
10709 }
10710 });
10711 return newNode;
10712 }
10713}
10714
10715/**
10716 * @license
10717 * Copyright 2017 Google LLC
10718 *
10719 * Licensed under the Apache License, Version 2.0 (the "License");
10720 * you may not use this file except in compliance with the License.
10721 * You may obtain a copy of the License at
10722 *
10723 * http://www.apache.org/licenses/LICENSE-2.0
10724 *
10725 * Unless required by applicable law or agreed to in writing, software
10726 * distributed under the License is distributed on an "AS IS" BASIS,
10727 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10728 * See the License for the specific language governing permissions and
10729 * limitations under the License.
10730 */
10731/**
10732 * A light-weight tree, traversable by path. Nodes can have both values and children.
10733 * Nodes are not enumerated (by forEachChild) unless they have a value or non-empty
10734 * children.
10735 */
10736var Tree = /** @class */ (function () {
10737 /**
10738 * @param name - Optional name of the node.
10739 * @param parent - Optional parent node.
10740 * @param node - Optional node to wrap.
10741 */
10742 function Tree(name, parent, node) {
10743 if (name === void 0) { name = ''; }
10744 if (parent === void 0) { parent = null; }
10745 if (node === void 0) { node = { children: {}, childCount: 0 }; }
10746 this.name = name;
10747 this.parent = parent;
10748 this.node = node;
10749 }
10750 return Tree;
10751}());
10752/**
10753 * Returns a sub-Tree for the given path.
10754 *
10755 * @param pathObj - Path to look up.
10756 * @returns Tree for path.
10757 */
10758function treeSubTree(tree, pathObj) {
10759 // TODO: Require pathObj to be Path?
10760 var path = pathObj instanceof Path ? pathObj : new Path(pathObj);
10761 var child = tree, next = pathGetFront(path);
10762 while (next !== null) {
10763 var childNode = util.safeGet(child.node.children, next) || {
10764 children: {},
10765 childCount: 0
10766 };
10767 child = new Tree(next, child, childNode);
10768 path = pathPopFront(path);
10769 next = pathGetFront(path);
10770 }
10771 return child;
10772}
10773/**
10774 * Returns the data associated with this tree node.
10775 *
10776 * @returns The data or null if no data exists.
10777 */
10778function treeGetValue(tree) {
10779 return tree.node.value;
10780}
10781/**
10782 * Sets data to this tree node.
10783 *
10784 * @param value - Value to set.
10785 */
10786function treeSetValue(tree, value) {
10787 tree.node.value = value;
10788 treeUpdateParents(tree);
10789}
10790/**
10791 * @returns Whether the tree has any children.
10792 */
10793function treeHasChildren(tree) {
10794 return tree.node.childCount > 0;
10795}
10796/**
10797 * @returns Whethe rthe tree is empty (no value or children).
10798 */
10799function treeIsEmpty(tree) {
10800 return treeGetValue(tree) === undefined && !treeHasChildren(tree);
10801}
10802/**
10803 * Calls action for each child of this tree node.
10804 *
10805 * @param action - Action to be called for each child.
10806 */
10807function treeForEachChild(tree, action) {
10808 each(tree.node.children, function (child, childTree) {
10809 action(new Tree(child, tree, childTree));
10810 });
10811}
10812/**
10813 * Does a depth-first traversal of this node's descendants, calling action for each one.
10814 *
10815 * @param action - Action to be called for each child.
10816 * @param includeSelf - Whether to call action on this node as well. Defaults to
10817 * false.
10818 * @param childrenFirst - Whether to call action on children before calling it on
10819 * parent.
10820 */
10821function treeForEachDescendant(tree, action, includeSelf, childrenFirst) {
10822 if (includeSelf && !childrenFirst) {
10823 action(tree);
10824 }
10825 treeForEachChild(tree, function (child) {
10826 treeForEachDescendant(child, action, true, childrenFirst);
10827 });
10828 if (includeSelf && childrenFirst) {
10829 action(tree);
10830 }
10831}
10832/**
10833 * Calls action on each ancestor node.
10834 *
10835 * @param action - Action to be called on each parent; return
10836 * true to abort.
10837 * @param includeSelf - Whether to call action on this node as well.
10838 * @returns true if the action callback returned true.
10839 */
10840function treeForEachAncestor(tree, action, includeSelf) {
10841 var node = includeSelf ? tree : tree.parent;
10842 while (node !== null) {
10843 if (action(node)) {
10844 return true;
10845 }
10846 node = node.parent;
10847 }
10848 return false;
10849}
10850/**
10851 * @returns The path of this tree node, as a Path.
10852 */
10853function treeGetPath(tree) {
10854 return new Path(tree.parent === null
10855 ? tree.name
10856 : treeGetPath(tree.parent) + '/' + tree.name);
10857}
10858/**
10859 * Adds or removes this child from its parent based on whether it's empty or not.
10860 */
10861function treeUpdateParents(tree) {
10862 if (tree.parent !== null) {
10863 treeUpdateChild(tree.parent, tree.name, tree);
10864 }
10865}
10866/**
10867 * Adds or removes the passed child to this tree node, depending on whether it's empty.
10868 *
10869 * @param childName - The name of the child to update.
10870 * @param child - The child to update.
10871 */
10872function treeUpdateChild(tree, childName, child) {
10873 var childEmpty = treeIsEmpty(child);
10874 var childExists = util.contains(tree.node.children, childName);
10875 if (childEmpty && childExists) {
10876 delete tree.node.children[childName];
10877 tree.node.childCount--;
10878 treeUpdateParents(tree);
10879 }
10880 else if (!childEmpty && !childExists) {
10881 tree.node.children[childName] = child.node;
10882 tree.node.childCount++;
10883 treeUpdateParents(tree);
10884 }
10885}
10886
10887/**
10888 * @license
10889 * Copyright 2017 Google LLC
10890 *
10891 * Licensed under the Apache License, Version 2.0 (the "License");
10892 * you may not use this file except in compliance with the License.
10893 * You may obtain a copy of the License at
10894 *
10895 * http://www.apache.org/licenses/LICENSE-2.0
10896 *
10897 * Unless required by applicable law or agreed to in writing, software
10898 * distributed under the License is distributed on an "AS IS" BASIS,
10899 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10900 * See the License for the specific language governing permissions and
10901 * limitations under the License.
10902 */
10903/**
10904 * True for invalid Firebase keys
10905 */
10906var INVALID_KEY_REGEX_ = /[\[\].#$\/\u0000-\u001F\u007F]/;
10907/**
10908 * True for invalid Firebase paths.
10909 * Allows '/' in paths.
10910 */
10911var INVALID_PATH_REGEX_ = /[\[\].#$\u0000-\u001F\u007F]/;
10912/**
10913 * Maximum number of characters to allow in leaf value
10914 */
10915var MAX_LEAF_SIZE_ = 10 * 1024 * 1024;
10916var isValidKey = function (key) {
10917 return (typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX_.test(key));
10918};
10919var isValidPathString = function (pathString) {
10920 return (typeof pathString === 'string' &&
10921 pathString.length !== 0 &&
10922 !INVALID_PATH_REGEX_.test(pathString));
10923};
10924var isValidRootPathString = function (pathString) {
10925 if (pathString) {
10926 // Allow '/.info/' at the beginning.
10927 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10928 }
10929 return isValidPathString(pathString);
10930};
10931var isValidPriority = function (priority) {
10932 return (priority === null ||
10933 typeof priority === 'string' ||
10934 (typeof priority === 'number' && !isInvalidJSONNumber(priority)) ||
10935 (priority &&
10936 typeof priority === 'object' &&
10937 // eslint-disable-next-line @typescript-eslint/no-explicit-any
10938 util.contains(priority, '.sv')));
10939};
10940/**
10941 * Pre-validate a datum passed as an argument to Firebase function.
10942 */
10943var validateFirebaseDataArg = function (fnName, value, path, optional) {
10944 if (optional && value === undefined) {
10945 return;
10946 }
10947 validateFirebaseData(util.errorPrefix(fnName, 'value'), value, path);
10948};
10949/**
10950 * Validate a data object client-side before sending to server.
10951 */
10952var validateFirebaseData = function (errorPrefix, data, path_) {
10953 var path = path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;
10954 if (data === undefined) {
10955 throw new Error(errorPrefix + 'contains undefined ' + validationPathToErrorString(path));
10956 }
10957 if (typeof data === 'function') {
10958 throw new Error(errorPrefix +
10959 'contains a function ' +
10960 validationPathToErrorString(path) +
10961 ' with contents = ' +
10962 data.toString());
10963 }
10964 if (isInvalidJSONNumber(data)) {
10965 throw new Error(errorPrefix +
10966 'contains ' +
10967 data.toString() +
10968 ' ' +
10969 validationPathToErrorString(path));
10970 }
10971 // Check max leaf size, but try to avoid the utf8 conversion if we can.
10972 if (typeof data === 'string' &&
10973 data.length > MAX_LEAF_SIZE_ / 3 &&
10974 util.stringLength(data) > MAX_LEAF_SIZE_) {
10975 throw new Error(errorPrefix +
10976 'contains a string greater than ' +
10977 MAX_LEAF_SIZE_ +
10978 ' utf8 bytes ' +
10979 validationPathToErrorString(path) +
10980 " ('" +
10981 data.substring(0, 50) +
10982 "...')");
10983 }
10984 // TODO = Perf = Consider combining the recursive validation of keys into NodeFromJSON
10985 // to save extra walking of large objects.
10986 if (data && typeof data === 'object') {
10987 var hasDotValue_1 = false;
10988 var hasActualChild_1 = false;
10989 each(data, function (key, value) {
10990 if (key === '.value') {
10991 hasDotValue_1 = true;
10992 }
10993 else if (key !== '.priority' && key !== '.sv') {
10994 hasActualChild_1 = true;
10995 if (!isValidKey(key)) {
10996 throw new Error(errorPrefix +
10997 ' contains an invalid key (' +
10998 key +
10999 ') ' +
11000 validationPathToErrorString(path) +
11001 '. Keys must be non-empty strings ' +
11002 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11003 }
11004 }
11005 validationPathPush(path, key);
11006 validateFirebaseData(errorPrefix, value, path);
11007 validationPathPop(path);
11008 });
11009 if (hasDotValue_1 && hasActualChild_1) {
11010 throw new Error(errorPrefix +
11011 ' contains ".value" child ' +
11012 validationPathToErrorString(path) +
11013 ' in addition to actual children.');
11014 }
11015 }
11016};
11017/**
11018 * Pre-validate paths passed in the firebase function.
11019 */
11020var validateFirebaseMergePaths = function (errorPrefix, mergePaths) {
11021 var i, curPath;
11022 for (i = 0; i < mergePaths.length; i++) {
11023 curPath = mergePaths[i];
11024 var keys = pathSlice(curPath);
11025 for (var j = 0; j < keys.length; j++) {
11026 if (keys[j] === '.priority' && j === keys.length - 1) ;
11027 else if (!isValidKey(keys[j])) {
11028 throw new Error(errorPrefix +
11029 'contains an invalid key (' +
11030 keys[j] +
11031 ') in path ' +
11032 curPath.toString() +
11033 '. Keys must be non-empty strings ' +
11034 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11035 }
11036 }
11037 }
11038 // Check that update keys are not descendants of each other.
11039 // We rely on the property that sorting guarantees that ancestors come
11040 // right before descendants.
11041 mergePaths.sort(pathCompare);
11042 var prevPath = null;
11043 for (i = 0; i < mergePaths.length; i++) {
11044 curPath = mergePaths[i];
11045 if (prevPath !== null && pathContains(prevPath, curPath)) {
11046 throw new Error(errorPrefix +
11047 'contains a path ' +
11048 prevPath.toString() +
11049 ' that is ancestor of another path ' +
11050 curPath.toString());
11051 }
11052 prevPath = curPath;
11053 }
11054};
11055/**
11056 * pre-validate an object passed as an argument to firebase function (
11057 * must be an object - e.g. for firebase.update()).
11058 */
11059var validateFirebaseMergeDataArg = function (fnName, data, path, optional) {
11060 if (optional && data === undefined) {
11061 return;
11062 }
11063 var errorPrefix = util.errorPrefix(fnName, 'values');
11064 if (!(data && typeof data === 'object') || Array.isArray(data)) {
11065 throw new Error(errorPrefix + ' must be an object containing the children to replace.');
11066 }
11067 var mergePaths = [];
11068 each(data, function (key, value) {
11069 var curPath = new Path(key);
11070 validateFirebaseData(errorPrefix, value, pathChild(path, curPath));
11071 if (pathGetBack(curPath) === '.priority') {
11072 if (!isValidPriority(value)) {
11073 throw new Error(errorPrefix +
11074 "contains an invalid value for '" +
11075 curPath.toString() +
11076 "', which must be a valid " +
11077 'Firebase priority (a string, finite number, server value, or null).');
11078 }
11079 }
11080 mergePaths.push(curPath);
11081 });
11082 validateFirebaseMergePaths(errorPrefix, mergePaths);
11083};
11084var validatePriority = function (fnName, priority, optional) {
11085 if (optional && priority === undefined) {
11086 return;
11087 }
11088 if (isInvalidJSONNumber(priority)) {
11089 throw new Error(util.errorPrefix(fnName, 'priority') +
11090 'is ' +
11091 priority.toString() +
11092 ', but must be a valid Firebase priority (a string, finite number, ' +
11093 'server value, or null).');
11094 }
11095 // Special case to allow importing data with a .sv.
11096 if (!isValidPriority(priority)) {
11097 throw new Error(util.errorPrefix(fnName, 'priority') +
11098 'must be a valid Firebase priority ' +
11099 '(a string, finite number, server value, or null).');
11100 }
11101};
11102var validateKey = function (fnName, argumentName, key, optional) {
11103 if (optional && key === undefined) {
11104 return;
11105 }
11106 if (!isValidKey(key)) {
11107 throw new Error(util.errorPrefix(fnName, argumentName) +
11108 'was an invalid key = "' +
11109 key +
11110 '". Firebase keys must be non-empty strings and ' +
11111 'can\'t contain ".", "#", "$", "/", "[", or "]").');
11112 }
11113};
11114/**
11115 * @internal
11116 */
11117var validatePathString = function (fnName, argumentName, pathString, optional) {
11118 if (optional && pathString === undefined) {
11119 return;
11120 }
11121 if (!isValidPathString(pathString)) {
11122 throw new Error(util.errorPrefix(fnName, argumentName) +
11123 'was an invalid path = "' +
11124 pathString +
11125 '". Paths must be non-empty strings and ' +
11126 'can\'t contain ".", "#", "$", "[", or "]"');
11127 }
11128};
11129var validateRootPathString = function (fnName, argumentName, pathString, optional) {
11130 if (pathString) {
11131 // Allow '/.info/' at the beginning.
11132 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
11133 }
11134 validatePathString(fnName, argumentName, pathString, optional);
11135};
11136/**
11137 * @internal
11138 */
11139var validateWritablePath = function (fnName, path) {
11140 if (pathGetFront(path) === '.info') {
11141 throw new Error(fnName + " failed = Can't modify data under /.info/");
11142 }
11143};
11144var validateUrl = function (fnName, parsedUrl) {
11145 // TODO = Validate server better.
11146 var pathString = parsedUrl.path.toString();
11147 if (!(typeof parsedUrl.repoInfo.host === 'string') ||
11148 parsedUrl.repoInfo.host.length === 0 ||
11149 (!isValidKey(parsedUrl.repoInfo.namespace) &&
11150 parsedUrl.repoInfo.host.split(':')[0] !== 'localhost') ||
11151 (pathString.length !== 0 && !isValidRootPathString(pathString))) {
11152 throw new Error(util.errorPrefix(fnName, 'url') +
11153 'must be a valid firebase URL and ' +
11154 'the path can\'t contain ".", "#", "$", "[", or "]".');
11155 }
11156};
11157
11158/**
11159 * @license
11160 * Copyright 2017 Google LLC
11161 *
11162 * Licensed under the Apache License, Version 2.0 (the "License");
11163 * you may not use this file except in compliance with the License.
11164 * You may obtain a copy of the License at
11165 *
11166 * http://www.apache.org/licenses/LICENSE-2.0
11167 *
11168 * Unless required by applicable law or agreed to in writing, software
11169 * distributed under the License is distributed on an "AS IS" BASIS,
11170 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11171 * See the License for the specific language governing permissions and
11172 * limitations under the License.
11173 */
11174/**
11175 * The event queue serves a few purposes:
11176 * 1. It ensures we maintain event order in the face of event callbacks doing operations that result in more
11177 * events being queued.
11178 * 2. raiseQueuedEvents() handles being called reentrantly nicely. That is, if in the course of raising events,
11179 * raiseQueuedEvents() is called again, the "inner" call will pick up raising events where the "outer" call
11180 * left off, ensuring that the events are still raised synchronously and in order.
11181 * 3. You can use raiseEventsAtPath and raiseEventsForChangedPath to ensure only relevant previously-queued
11182 * events are raised synchronously.
11183 *
11184 * NOTE: This can all go away if/when we move to async events.
11185 *
11186 */
11187var EventQueue = /** @class */ (function () {
11188 function EventQueue() {
11189 this.eventLists_ = [];
11190 /**
11191 * Tracks recursion depth of raiseQueuedEvents_, for debugging purposes.
11192 */
11193 this.recursionDepth_ = 0;
11194 }
11195 return EventQueue;
11196}());
11197/**
11198 * @param eventDataList - The new events to queue.
11199 */
11200function eventQueueQueueEvents(eventQueue, eventDataList) {
11201 // We group events by path, storing them in a single EventList, to make it easier to skip over them quickly.
11202 var currList = null;
11203 for (var i = 0; i < eventDataList.length; i++) {
11204 var data = eventDataList[i];
11205 var path = data.getPath();
11206 if (currList !== null && !pathEquals(path, currList.path)) {
11207 eventQueue.eventLists_.push(currList);
11208 currList = null;
11209 }
11210 if (currList === null) {
11211 currList = { events: [], path: path };
11212 }
11213 currList.events.push(data);
11214 }
11215 if (currList) {
11216 eventQueue.eventLists_.push(currList);
11217 }
11218}
11219/**
11220 * Queues the specified events and synchronously raises all events (including previously queued ones)
11221 * for the specified path.
11222 *
11223 * It is assumed that the new events are all for the specified path.
11224 *
11225 * @param path - The path to raise events for.
11226 * @param eventDataList - The new events to raise.
11227 */
11228function eventQueueRaiseEventsAtPath(eventQueue, path, eventDataList) {
11229 eventQueueQueueEvents(eventQueue, eventDataList);
11230 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11231 return pathEquals(eventPath, path);
11232 });
11233}
11234/**
11235 * Queues the specified events and synchronously raises all events (including previously queued ones) for
11236 * locations related to the specified change path (i.e. all ancestors and descendants).
11237 *
11238 * It is assumed that the new events are all related (ancestor or descendant) to the specified path.
11239 *
11240 * @param changedPath - The path to raise events for.
11241 * @param eventDataList - The events to raise
11242 */
11243function eventQueueRaiseEventsForChangedPath(eventQueue, changedPath, eventDataList) {
11244 eventQueueQueueEvents(eventQueue, eventDataList);
11245 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11246 return pathContains(eventPath, changedPath) ||
11247 pathContains(changedPath, eventPath);
11248 });
11249}
11250function eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, predicate) {
11251 eventQueue.recursionDepth_++;
11252 var sentAll = true;
11253 for (var i = 0; i < eventQueue.eventLists_.length; i++) {
11254 var eventList = eventQueue.eventLists_[i];
11255 if (eventList) {
11256 var eventPath = eventList.path;
11257 if (predicate(eventPath)) {
11258 eventListRaise(eventQueue.eventLists_[i]);
11259 eventQueue.eventLists_[i] = null;
11260 }
11261 else {
11262 sentAll = false;
11263 }
11264 }
11265 }
11266 if (sentAll) {
11267 eventQueue.eventLists_ = [];
11268 }
11269 eventQueue.recursionDepth_--;
11270}
11271/**
11272 * Iterates through the list and raises each event
11273 */
11274function eventListRaise(eventList) {
11275 for (var i = 0; i < eventList.events.length; i++) {
11276 var eventData = eventList.events[i];
11277 if (eventData !== null) {
11278 eventList.events[i] = null;
11279 var eventFn = eventData.getEventRunner();
11280 if (logger) {
11281 log('event: ' + eventData.toString());
11282 }
11283 exceptionGuard(eventFn);
11284 }
11285 }
11286}
11287
11288/**
11289 * @license
11290 * Copyright 2017 Google LLC
11291 *
11292 * Licensed under the Apache License, Version 2.0 (the "License");
11293 * you may not use this file except in compliance with the License.
11294 * You may obtain a copy of the License at
11295 *
11296 * http://www.apache.org/licenses/LICENSE-2.0
11297 *
11298 * Unless required by applicable law or agreed to in writing, software
11299 * distributed under the License is distributed on an "AS IS" BASIS,
11300 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11301 * See the License for the specific language governing permissions and
11302 * limitations under the License.
11303 */
11304var INTERRUPT_REASON = 'repo_interrupt';
11305/**
11306 * If a transaction does not succeed after 25 retries, we abort it. Among other
11307 * things this ensure that if there's ever a bug causing a mismatch between
11308 * client / server hashes for some data, we won't retry indefinitely.
11309 */
11310var MAX_TRANSACTION_RETRIES = 25;
11311/**
11312 * A connection to a single data repository.
11313 */
11314var Repo = /** @class */ (function () {
11315 function Repo(repoInfo_, forceRestClient_, authTokenProvider_, appCheckProvider_) {
11316 this.repoInfo_ = repoInfo_;
11317 this.forceRestClient_ = forceRestClient_;
11318 this.authTokenProvider_ = authTokenProvider_;
11319 this.appCheckProvider_ = appCheckProvider_;
11320 this.dataUpdateCount = 0;
11321 this.statsListener_ = null;
11322 this.eventQueue_ = new EventQueue();
11323 this.nextWriteId_ = 1;
11324 this.interceptServerDataCallback_ = null;
11325 /** A list of data pieces and paths to be set when this client disconnects. */
11326 this.onDisconnect_ = newSparseSnapshotTree();
11327 /** Stores queues of outstanding transactions for Firebase locations. */
11328 this.transactionQueueTree_ = new Tree();
11329 // TODO: This should be @private but it's used by test_access.js and internal.js
11330 this.persistentConnection_ = null;
11331 // This key is intentionally not updated if RepoInfo is later changed or replaced
11332 this.key = this.repoInfo_.toURLString();
11333 }
11334 /**
11335 * @returns The URL corresponding to the root of this Firebase.
11336 */
11337 Repo.prototype.toString = function () {
11338 return ((this.repoInfo_.secure ? 'https://' : 'http://') + this.repoInfo_.host);
11339 };
11340 return Repo;
11341}());
11342function repoStart(repo, appId, authOverride) {
11343 repo.stats_ = statsManagerGetCollection(repo.repoInfo_);
11344 if (repo.forceRestClient_ || beingCrawled()) {
11345 repo.server_ = new ReadonlyRestClient(repo.repoInfo_, function (pathString, data, isMerge, tag) {
11346 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11347 }, repo.authTokenProvider_, repo.appCheckProvider_);
11348 // Minor hack: Fire onConnect immediately, since there's no actual connection.
11349 setTimeout(function () { return repoOnConnectStatus(repo, /* connectStatus= */ true); }, 0);
11350 }
11351 else {
11352 // Validate authOverride
11353 if (typeof authOverride !== 'undefined' && authOverride !== null) {
11354 if (typeof authOverride !== 'object') {
11355 throw new Error('Only objects are supported for option databaseAuthVariableOverride');
11356 }
11357 try {
11358 util.stringify(authOverride);
11359 }
11360 catch (e) {
11361 throw new Error('Invalid authOverride provided: ' + e);
11362 }
11363 }
11364 repo.persistentConnection_ = new PersistentConnection(repo.repoInfo_, appId, function (pathString, data, isMerge, tag) {
11365 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11366 }, function (connectStatus) {
11367 repoOnConnectStatus(repo, connectStatus);
11368 }, function (updates) {
11369 repoOnServerInfoUpdate(repo, updates);
11370 }, repo.authTokenProvider_, repo.appCheckProvider_, authOverride);
11371 repo.server_ = repo.persistentConnection_;
11372 }
11373 repo.authTokenProvider_.addTokenChangeListener(function (token) {
11374 repo.server_.refreshAuthToken(token);
11375 });
11376 repo.appCheckProvider_.addTokenChangeListener(function (result) {
11377 repo.server_.refreshAppCheckToken(result.token);
11378 });
11379 // In the case of multiple Repos for the same repoInfo (i.e. there are multiple Firebase.Contexts being used),
11380 // we only want to create one StatsReporter. As such, we'll report stats over the first Repo created.
11381 repo.statsReporter_ = statsManagerGetOrCreateReporter(repo.repoInfo_, function () { return new StatsReporter(repo.stats_, repo.server_); });
11382 // Used for .info.
11383 repo.infoData_ = new SnapshotHolder();
11384 repo.infoSyncTree_ = new SyncTree({
11385 startListening: function (query, tag, currentHashFn, onComplete) {
11386 var infoEvents = [];
11387 var node = repo.infoData_.getNode(query._path);
11388 // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
11389 // on initial data...
11390 if (!node.isEmpty()) {
11391 infoEvents = syncTreeApplyServerOverwrite(repo.infoSyncTree_, query._path, node);
11392 setTimeout(function () {
11393 onComplete('ok');
11394 }, 0);
11395 }
11396 return infoEvents;
11397 },
11398 stopListening: function () { }
11399 });
11400 repoUpdateInfo(repo, 'connected', false);
11401 repo.serverSyncTree_ = new SyncTree({
11402 startListening: function (query, tag, currentHashFn, onComplete) {
11403 repo.server_.listen(query, currentHashFn, tag, function (status, data) {
11404 var events = onComplete(status, data);
11405 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11406 });
11407 // No synchronous events for network-backed sync trees
11408 return [];
11409 },
11410 stopListening: function (query, tag) {
11411 repo.server_.unlisten(query, tag);
11412 }
11413 });
11414}
11415/**
11416 * @returns The time in milliseconds, taking the server offset into account if we have one.
11417 */
11418function repoServerTime(repo) {
11419 var offsetNode = repo.infoData_.getNode(new Path('.info/serverTimeOffset'));
11420 var offset = offsetNode.val() || 0;
11421 return new Date().getTime() + offset;
11422}
11423/**
11424 * Generate ServerValues using some variables from the repo object.
11425 */
11426function repoGenerateServerValues(repo) {
11427 return generateWithValues({
11428 timestamp: repoServerTime(repo)
11429 });
11430}
11431/**
11432 * Called by realtime when we get new messages from the server.
11433 */
11434function repoOnDataUpdate(repo, pathString, data, isMerge, tag) {
11435 // For testing.
11436 repo.dataUpdateCount++;
11437 var path = new Path(pathString);
11438 data = repo.interceptServerDataCallback_
11439 ? repo.interceptServerDataCallback_(pathString, data)
11440 : data;
11441 var events = [];
11442 if (tag) {
11443 if (isMerge) {
11444 var taggedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11445 events = syncTreeApplyTaggedQueryMerge(repo.serverSyncTree_, path, taggedChildren, tag);
11446 }
11447 else {
11448 var taggedSnap = nodeFromJSON(data);
11449 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, path, taggedSnap, tag);
11450 }
11451 }
11452 else if (isMerge) {
11453 var changedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11454 events = syncTreeApplyServerMerge(repo.serverSyncTree_, path, changedChildren);
11455 }
11456 else {
11457 var snap = nodeFromJSON(data);
11458 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap);
11459 }
11460 var affectedPath = path;
11461 if (events.length > 0) {
11462 // Since we have a listener outstanding for each transaction, receiving any events
11463 // is a proxy for some change having occurred.
11464 affectedPath = repoRerunTransactions(repo, path);
11465 }
11466 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, events);
11467}
11468function repoOnConnectStatus(repo, connectStatus) {
11469 repoUpdateInfo(repo, 'connected', connectStatus);
11470 if (connectStatus === false) {
11471 repoRunOnDisconnectEvents(repo);
11472 }
11473}
11474function repoOnServerInfoUpdate(repo, updates) {
11475 each(updates, function (key, value) {
11476 repoUpdateInfo(repo, key, value);
11477 });
11478}
11479function repoUpdateInfo(repo, pathString, value) {
11480 var path = new Path('/.info/' + pathString);
11481 var newNode = nodeFromJSON(value);
11482 repo.infoData_.updateSnapshot(path, newNode);
11483 var events = syncTreeApplyServerOverwrite(repo.infoSyncTree_, path, newNode);
11484 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11485}
11486function repoGetNextWriteId(repo) {
11487 return repo.nextWriteId_++;
11488}
11489/**
11490 * The purpose of `getValue` is to return the latest known value
11491 * satisfying `query`.
11492 *
11493 * This method will first check for in-memory cached values
11494 * belonging to active listeners. If they are found, such values
11495 * are considered to be the most up-to-date.
11496 *
11497 * If the client is not connected, this method will try to
11498 * establish a connection and request the value for `query`. If
11499 * the client is not able to retrieve the query result, it reports
11500 * an error.
11501 *
11502 * @param query - The query to surface a value for.
11503 */
11504function repoGetValue(repo, query) {
11505 // Only active queries are cached. There is no persisted cache.
11506 var cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11507 if (cached != null) {
11508 return Promise.resolve(cached);
11509 }
11510 return repo.server_.get(query).then(function (payload) {
11511 var node = nodeFromJSON(payload).withIndex(query._queryParams.getIndex());
11512 var events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11513 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11514 return Promise.resolve(node);
11515 }, function (err) {
11516 repoLog(repo, 'get for query ' + util.stringify(query) + ' failed: ' + err);
11517 return Promise.reject(new Error(err));
11518 });
11519}
11520function repoSetWithPriority(repo, path, newVal, newPriority, onComplete) {
11521 repoLog(repo, 'set', {
11522 path: path.toString(),
11523 value: newVal,
11524 priority: newPriority
11525 });
11526 // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
11527 // (b) store unresolved paths on JSON parse
11528 var serverValues = repoGenerateServerValues(repo);
11529 var newNodeUnresolved = nodeFromJSON(newVal, newPriority);
11530 var existing = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path);
11531 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, existing, serverValues);
11532 var writeId = repoGetNextWriteId(repo);
11533 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, writeId, true);
11534 eventQueueQueueEvents(repo.eventQueue_, events);
11535 repo.server_.put(path.toString(), newNodeUnresolved.val(/*export=*/ true), function (status, errorReason) {
11536 var success = status === 'ok';
11537 if (!success) {
11538 warn('set at ' + path + ' failed: ' + status);
11539 }
11540 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId, !success);
11541 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, clearEvents);
11542 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11543 });
11544 var affectedPath = repoAbortTransactions(repo, path);
11545 repoRerunTransactions(repo, affectedPath);
11546 // We queued the events above, so just flush the queue here
11547 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, []);
11548}
11549function repoUpdate(repo, path, childrenToMerge, onComplete) {
11550 repoLog(repo, 'update', { path: path.toString(), value: childrenToMerge });
11551 // Start with our existing data and merge each child into it.
11552 var empty = true;
11553 var serverValues = repoGenerateServerValues(repo);
11554 var changedChildren = {};
11555 each(childrenToMerge, function (changedKey, changedValue) {
11556 empty = false;
11557 changedChildren[changedKey] = resolveDeferredValueTree(pathChild(path, changedKey), nodeFromJSON(changedValue), repo.serverSyncTree_, serverValues);
11558 });
11559 if (!empty) {
11560 var writeId_1 = repoGetNextWriteId(repo);
11561 var events = syncTreeApplyUserMerge(repo.serverSyncTree_, path, changedChildren, writeId_1);
11562 eventQueueQueueEvents(repo.eventQueue_, events);
11563 repo.server_.merge(path.toString(), childrenToMerge, function (status, errorReason) {
11564 var success = status === 'ok';
11565 if (!success) {
11566 warn('update at ' + path + ' failed: ' + status);
11567 }
11568 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId_1, !success);
11569 var affectedPath = clearEvents.length > 0 ? repoRerunTransactions(repo, path) : path;
11570 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, clearEvents);
11571 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11572 });
11573 each(childrenToMerge, function (changedPath) {
11574 var affectedPath = repoAbortTransactions(repo, pathChild(path, changedPath));
11575 repoRerunTransactions(repo, affectedPath);
11576 });
11577 // We queued the events above, so just flush the queue here
11578 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, []);
11579 }
11580 else {
11581 log("update() called with empty data. Don't do anything.");
11582 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11583 }
11584}
11585/**
11586 * Applies all of the changes stored up in the onDisconnect_ tree.
11587 */
11588function repoRunOnDisconnectEvents(repo) {
11589 repoLog(repo, 'onDisconnectEvents');
11590 var serverValues = repoGenerateServerValues(repo);
11591 var resolvedOnDisconnectTree = newSparseSnapshotTree();
11592 sparseSnapshotTreeForEachTree(repo.onDisconnect_, newEmptyPath(), function (path, node) {
11593 var resolved = resolveDeferredValueTree(path, node, repo.serverSyncTree_, serverValues);
11594 sparseSnapshotTreeRemember(resolvedOnDisconnectTree, path, resolved);
11595 });
11596 var events = [];
11597 sparseSnapshotTreeForEachTree(resolvedOnDisconnectTree, newEmptyPath(), function (path, snap) {
11598 events = events.concat(syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap));
11599 var affectedPath = repoAbortTransactions(repo, path);
11600 repoRerunTransactions(repo, affectedPath);
11601 });
11602 repo.onDisconnect_ = newSparseSnapshotTree();
11603 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, newEmptyPath(), events);
11604}
11605function repoOnDisconnectCancel(repo, path, onComplete) {
11606 repo.server_.onDisconnectCancel(path.toString(), function (status, errorReason) {
11607 if (status === 'ok') {
11608 sparseSnapshotTreeForget(repo.onDisconnect_, path);
11609 }
11610 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11611 });
11612}
11613function repoOnDisconnectSet(repo, path, value, onComplete) {
11614 var newNode = nodeFromJSON(value);
11615 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11616 if (status === 'ok') {
11617 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11618 }
11619 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11620 });
11621}
11622function repoOnDisconnectSetWithPriority(repo, path, value, priority, onComplete) {
11623 var newNode = nodeFromJSON(value, priority);
11624 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11625 if (status === 'ok') {
11626 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11627 }
11628 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11629 });
11630}
11631function repoOnDisconnectUpdate(repo, path, childrenToMerge, onComplete) {
11632 if (util.isEmpty(childrenToMerge)) {
11633 log("onDisconnect().update() called with empty data. Don't do anything.");
11634 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11635 return;
11636 }
11637 repo.server_.onDisconnectMerge(path.toString(), childrenToMerge, function (status, errorReason) {
11638 if (status === 'ok') {
11639 each(childrenToMerge, function (childName, childNode) {
11640 var newChildNode = nodeFromJSON(childNode);
11641 sparseSnapshotTreeRemember(repo.onDisconnect_, pathChild(path, childName), newChildNode);
11642 });
11643 }
11644 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11645 });
11646}
11647function repoAddEventCallbackForQuery(repo, query, eventRegistration) {
11648 var events;
11649 if (pathGetFront(query._path) === '.info') {
11650 events = syncTreeAddEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11651 }
11652 else {
11653 events = syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11654 }
11655 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11656}
11657function repoRemoveEventCallbackForQuery(repo, query, eventRegistration) {
11658 // These are guaranteed not to raise events, since we're not passing in a cancelError. However, we can future-proof
11659 // a little bit by handling the return values anyways.
11660 var events;
11661 if (pathGetFront(query._path) === '.info') {
11662 events = syncTreeRemoveEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11663 }
11664 else {
11665 events = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11666 }
11667 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11668}
11669function repoInterrupt(repo) {
11670 if (repo.persistentConnection_) {
11671 repo.persistentConnection_.interrupt(INTERRUPT_REASON);
11672 }
11673}
11674function repoResume(repo) {
11675 if (repo.persistentConnection_) {
11676 repo.persistentConnection_.resume(INTERRUPT_REASON);
11677 }
11678}
11679function repoLog(repo) {
11680 var varArgs = [];
11681 for (var _i = 1; _i < arguments.length; _i++) {
11682 varArgs[_i - 1] = arguments[_i];
11683 }
11684 var prefix = '';
11685 if (repo.persistentConnection_) {
11686 prefix = repo.persistentConnection_.id + ':';
11687 }
11688 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
11689}
11690function repoCallOnCompleteCallback(repo, callback, status, errorReason) {
11691 if (callback) {
11692 exceptionGuard(function () {
11693 if (status === 'ok') {
11694 callback(null);
11695 }
11696 else {
11697 var code = (status || 'error').toUpperCase();
11698 var message = code;
11699 if (errorReason) {
11700 message += ': ' + errorReason;
11701 }
11702 var error = new Error(message);
11703 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11704 error.code = code;
11705 callback(error);
11706 }
11707 });
11708 }
11709}
11710/**
11711 * Creates a new transaction, adds it to the transactions we're tracking, and
11712 * sends it to the server if possible.
11713 *
11714 * @param path - Path at which to do transaction.
11715 * @param transactionUpdate - Update callback.
11716 * @param onComplete - Completion callback.
11717 * @param unwatcher - Function that will be called when the transaction no longer
11718 * need data updates for `path`.
11719 * @param applyLocally - Whether or not to make intermediate results visible
11720 */
11721function repoStartTransaction(repo, path, transactionUpdate, onComplete, unwatcher, applyLocally) {
11722 repoLog(repo, 'transaction on ' + path);
11723 // Initialize transaction.
11724 var transaction = {
11725 path: path,
11726 update: transactionUpdate,
11727 onComplete: onComplete,
11728 // One of TransactionStatus enums.
11729 status: null,
11730 // Used when combining transactions at different locations to figure out
11731 // which one goes first.
11732 order: LUIDGenerator(),
11733 // Whether to raise local events for this transaction.
11734 applyLocally: applyLocally,
11735 // Count of how many times we've retried the transaction.
11736 retryCount: 0,
11737 // Function to call to clean up our .on() listener.
11738 unwatcher: unwatcher,
11739 // Stores why a transaction was aborted.
11740 abortReason: null,
11741 currentWriteId: null,
11742 currentInputSnapshot: null,
11743 currentOutputSnapshotRaw: null,
11744 currentOutputSnapshotResolved: null
11745 };
11746 // Run transaction initially.
11747 var currentState = repoGetLatestState(repo, path, undefined);
11748 transaction.currentInputSnapshot = currentState;
11749 var newVal = transaction.update(currentState.val());
11750 if (newVal === undefined) {
11751 // Abort transaction.
11752 transaction.unwatcher();
11753 transaction.currentOutputSnapshotRaw = null;
11754 transaction.currentOutputSnapshotResolved = null;
11755 if (transaction.onComplete) {
11756 transaction.onComplete(null, false, transaction.currentInputSnapshot);
11757 }
11758 }
11759 else {
11760 validateFirebaseData('transaction failed: Data returned ', newVal, transaction.path);
11761 // Mark as run and add to our queue.
11762 transaction.status = 0 /* RUN */;
11763 var queueNode = treeSubTree(repo.transactionQueueTree_, path);
11764 var nodeQueue = treeGetValue(queueNode) || [];
11765 nodeQueue.push(transaction);
11766 treeSetValue(queueNode, nodeQueue);
11767 // Update visibleData and raise events
11768 // Note: We intentionally raise events after updating all of our
11769 // transaction state, since the user could start new transactions from the
11770 // event callbacks.
11771 var priorityForNode = void 0;
11772 if (typeof newVal === 'object' &&
11773 newVal !== null &&
11774 util.contains(newVal, '.priority')) {
11775 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11776 priorityForNode = util.safeGet(newVal, '.priority');
11777 util.assert(isValidPriority(priorityForNode), 'Invalid priority returned by transaction. ' +
11778 'Priority must be a valid string, finite number, server value, or null.');
11779 }
11780 else {
11781 var currentNode = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path) ||
11782 ChildrenNode.EMPTY_NODE;
11783 priorityForNode = currentNode.getPriority().val();
11784 }
11785 var serverValues = repoGenerateServerValues(repo);
11786 var newNodeUnresolved = nodeFromJSON(newVal, priorityForNode);
11787 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, currentState, serverValues);
11788 transaction.currentOutputSnapshotRaw = newNodeUnresolved;
11789 transaction.currentOutputSnapshotResolved = newNode;
11790 transaction.currentWriteId = repoGetNextWriteId(repo);
11791 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, transaction.currentWriteId, transaction.applyLocally);
11792 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11793 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11794 }
11795}
11796/**
11797 * @param excludeSets - A specific set to exclude
11798 */
11799function repoGetLatestState(repo, path, excludeSets) {
11800 return (syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path, excludeSets) ||
11801 ChildrenNode.EMPTY_NODE);
11802}
11803/**
11804 * Sends any already-run transactions that aren't waiting for outstanding
11805 * transactions to complete.
11806 *
11807 * Externally it's called with no arguments, but it calls itself recursively
11808 * with a particular transactionQueueTree node to recurse through the tree.
11809 *
11810 * @param node - transactionQueueTree node to start at.
11811 */
11812function repoSendReadyTransactions(repo, node) {
11813 if (node === void 0) { node = repo.transactionQueueTree_; }
11814 // Before recursing, make sure any completed transactions are removed.
11815 if (!node) {
11816 repoPruneCompletedTransactionsBelowNode(repo, node);
11817 }
11818 if (treeGetValue(node)) {
11819 var queue = repoBuildTransactionQueue(repo, node);
11820 util.assert(queue.length > 0, 'Sending zero length transaction queue');
11821 var allRun = queue.every(function (transaction) { return transaction.status === 0 /* RUN */; });
11822 // If they're all run (and not sent), we can send them. Else, we must wait.
11823 if (allRun) {
11824 repoSendTransactionQueue(repo, treeGetPath(node), queue);
11825 }
11826 }
11827 else if (treeHasChildren(node)) {
11828 treeForEachChild(node, function (childNode) {
11829 repoSendReadyTransactions(repo, childNode);
11830 });
11831 }
11832}
11833/**
11834 * Given a list of run transactions, send them to the server and then handle
11835 * the result (success or failure).
11836 *
11837 * @param path - The location of the queue.
11838 * @param queue - Queue of transactions under the specified location.
11839 */
11840function repoSendTransactionQueue(repo, path, queue) {
11841 // Mark transactions as sent and increment retry count!
11842 var setsToIgnore = queue.map(function (txn) {
11843 return txn.currentWriteId;
11844 });
11845 var latestState = repoGetLatestState(repo, path, setsToIgnore);
11846 var snapToSend = latestState;
11847 var latestHash = latestState.hash();
11848 for (var i = 0; i < queue.length; i++) {
11849 var txn = queue[i];
11850 util.assert(txn.status === 0 /* RUN */, 'tryToSendTransactionQueue_: items in queue should all be run.');
11851 txn.status = 1 /* SENT */;
11852 txn.retryCount++;
11853 var relativePath = newRelativePath(path, txn.path);
11854 // If we've gotten to this point, the output snapshot must be defined.
11855 snapToSend = snapToSend.updateChild(relativePath /** @type {!Node} */, txn.currentOutputSnapshotRaw);
11856 }
11857 var dataToSend = snapToSend.val(true);
11858 var pathToSend = path;
11859 // Send the put.
11860 repo.server_.put(pathToSend.toString(), dataToSend, function (status) {
11861 repoLog(repo, 'transaction put response', {
11862 path: pathToSend.toString(),
11863 status: status
11864 });
11865 var events = [];
11866 if (status === 'ok') {
11867 // Queue up the callbacks and fire them after cleaning up all of our
11868 // transaction state, since the callback could trigger more
11869 // transactions or sets.
11870 var callbacks = [];
11871 var _loop_1 = function (i) {
11872 queue[i].status = 2 /* COMPLETED */;
11873 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId));
11874 if (queue[i].onComplete) {
11875 // We never unset the output snapshot, and given that this
11876 // transaction is complete, it should be set
11877 callbacks.push(function () {
11878 return queue[i].onComplete(null, true, queue[i].currentOutputSnapshotResolved);
11879 });
11880 }
11881 queue[i].unwatcher();
11882 };
11883 for (var i = 0; i < queue.length; i++) {
11884 _loop_1(i);
11885 }
11886 // Now remove the completed transactions.
11887 repoPruneCompletedTransactionsBelowNode(repo, treeSubTree(repo.transactionQueueTree_, path));
11888 // There may be pending transactions that we can now send.
11889 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11890 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11891 // Finally, trigger onComplete callbacks.
11892 for (var i = 0; i < callbacks.length; i++) {
11893 exceptionGuard(callbacks[i]);
11894 }
11895 }
11896 else {
11897 // transactions are no longer sent. Update their status appropriately.
11898 if (status === 'datastale') {
11899 for (var i = 0; i < queue.length; i++) {
11900 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) {
11901 queue[i].status = 4 /* NEEDS_ABORT */;
11902 }
11903 else {
11904 queue[i].status = 0 /* RUN */;
11905 }
11906 }
11907 }
11908 else {
11909 warn('transaction at ' + pathToSend.toString() + ' failed: ' + status);
11910 for (var i = 0; i < queue.length; i++) {
11911 queue[i].status = 4 /* NEEDS_ABORT */;
11912 queue[i].abortReason = status;
11913 }
11914 }
11915 repoRerunTransactions(repo, path);
11916 }
11917 }, latestHash);
11918}
11919/**
11920 * Finds all transactions dependent on the data at changedPath and reruns them.
11921 *
11922 * Should be called any time cached data changes.
11923 *
11924 * Return the highest path that was affected by rerunning transactions. This
11925 * is the path at which events need to be raised for.
11926 *
11927 * @param changedPath - The path in mergedData that changed.
11928 * @returns The rootmost path that was affected by rerunning transactions.
11929 */
11930function repoRerunTransactions(repo, changedPath) {
11931 var rootMostTransactionNode = repoGetAncestorTransactionNode(repo, changedPath);
11932 var path = treeGetPath(rootMostTransactionNode);
11933 var queue = repoBuildTransactionQueue(repo, rootMostTransactionNode);
11934 repoRerunTransactionQueue(repo, queue, path);
11935 return path;
11936}
11937/**
11938 * Does all the work of rerunning transactions (as well as cleans up aborted
11939 * transactions and whatnot).
11940 *
11941 * @param queue - The queue of transactions to run.
11942 * @param path - The path the queue is for.
11943 */
11944function repoRerunTransactionQueue(repo, queue, path) {
11945 if (queue.length === 0) {
11946 return; // Nothing to do!
11947 }
11948 // Queue up the callbacks and fire them after cleaning up all of our
11949 // transaction state, since the callback could trigger more transactions or
11950 // sets.
11951 var callbacks = [];
11952 var events = [];
11953 // Ignore all of the sets we're going to re-run.
11954 var txnsToRerun = queue.filter(function (q) {
11955 return q.status === 0 /* RUN */;
11956 });
11957 var setsToIgnore = txnsToRerun.map(function (q) {
11958 return q.currentWriteId;
11959 });
11960 var _loop_2 = function (i) {
11961 var transaction = queue[i];
11962 var relativePath = newRelativePath(path, transaction.path);
11963 var abortTransaction = false, abortReason;
11964 util.assert(relativePath !== null, 'rerunTransactionsUnderNode_: relativePath should not be null.');
11965 if (transaction.status === 4 /* NEEDS_ABORT */) {
11966 abortTransaction = true;
11967 abortReason = transaction.abortReason;
11968 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11969 }
11970 else if (transaction.status === 0 /* RUN */) {
11971 if (transaction.retryCount >= MAX_TRANSACTION_RETRIES) {
11972 abortTransaction = true;
11973 abortReason = 'maxretry';
11974 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11975 }
11976 else {
11977 // This code reruns a transaction
11978 var currentNode = repoGetLatestState(repo, transaction.path, setsToIgnore);
11979 transaction.currentInputSnapshot = currentNode;
11980 var newData = queue[i].update(currentNode.val());
11981 if (newData !== undefined) {
11982 validateFirebaseData('transaction failed: Data returned ', newData, transaction.path);
11983 var newDataNode = nodeFromJSON(newData);
11984 var hasExplicitPriority = typeof newData === 'object' &&
11985 newData != null &&
11986 util.contains(newData, '.priority');
11987 if (!hasExplicitPriority) {
11988 // Keep the old priority if there wasn't a priority explicitly specified.
11989 newDataNode = newDataNode.updatePriority(currentNode.getPriority());
11990 }
11991 var oldWriteId = transaction.currentWriteId;
11992 var serverValues = repoGenerateServerValues(repo);
11993 var newNodeResolved = resolveDeferredValueSnapshot(newDataNode, currentNode, serverValues);
11994 transaction.currentOutputSnapshotRaw = newDataNode;
11995 transaction.currentOutputSnapshotResolved = newNodeResolved;
11996 transaction.currentWriteId = repoGetNextWriteId(repo);
11997 // Mutates setsToIgnore in place
11998 setsToIgnore.splice(setsToIgnore.indexOf(oldWriteId), 1);
11999 events = events.concat(syncTreeApplyUserOverwrite(repo.serverSyncTree_, transaction.path, newNodeResolved, transaction.currentWriteId, transaction.applyLocally));
12000 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, oldWriteId, true));
12001 }
12002 else {
12003 abortTransaction = true;
12004 abortReason = 'nodata';
12005 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12006 }
12007 }
12008 }
12009 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
12010 events = [];
12011 if (abortTransaction) {
12012 // Abort.
12013 queue[i].status = 2 /* COMPLETED */;
12014 // Removing a listener can trigger pruning which can muck with
12015 // mergedData/visibleData (as it prunes data). So defer the unwatcher
12016 // until we're done.
12017 (function (unwatcher) {
12018 setTimeout(unwatcher, Math.floor(0));
12019 })(queue[i].unwatcher);
12020 if (queue[i].onComplete) {
12021 if (abortReason === 'nodata') {
12022 callbacks.push(function () {
12023 return queue[i].onComplete(null, false, queue[i].currentInputSnapshot);
12024 });
12025 }
12026 else {
12027 callbacks.push(function () {
12028 return queue[i].onComplete(new Error(abortReason), false, null);
12029 });
12030 }
12031 }
12032 }
12033 };
12034 for (var i = 0; i < queue.length; i++) {
12035 _loop_2(i);
12036 }
12037 // Clean up completed transactions.
12038 repoPruneCompletedTransactionsBelowNode(repo, repo.transactionQueueTree_);
12039 // Now fire callbacks, now that we're in a good, known state.
12040 for (var i = 0; i < callbacks.length; i++) {
12041 exceptionGuard(callbacks[i]);
12042 }
12043 // Try to send the transaction result to the server.
12044 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
12045}
12046/**
12047 * Returns the rootmost ancestor node of the specified path that has a pending
12048 * transaction on it, or just returns the node for the given path if there are
12049 * no pending transactions on any ancestor.
12050 *
12051 * @param path - The location to start at.
12052 * @returns The rootmost node with a transaction.
12053 */
12054function repoGetAncestorTransactionNode(repo, path) {
12055 var front;
12056 // Start at the root and walk deeper into the tree towards path until we
12057 // find a node with pending transactions.
12058 var transactionNode = repo.transactionQueueTree_;
12059 front = pathGetFront(path);
12060 while (front !== null && treeGetValue(transactionNode) === undefined) {
12061 transactionNode = treeSubTree(transactionNode, front);
12062 path = pathPopFront(path);
12063 front = pathGetFront(path);
12064 }
12065 return transactionNode;
12066}
12067/**
12068 * Builds the queue of all transactions at or below the specified
12069 * transactionNode.
12070 *
12071 * @param transactionNode
12072 * @returns The generated queue.
12073 */
12074function repoBuildTransactionQueue(repo, transactionNode) {
12075 // Walk any child transaction queues and aggregate them into a single queue.
12076 var transactionQueue = [];
12077 repoAggregateTransactionQueuesForNode(repo, transactionNode, transactionQueue);
12078 // Sort them by the order the transactions were created.
12079 transactionQueue.sort(function (a, b) { return a.order - b.order; });
12080 return transactionQueue;
12081}
12082function repoAggregateTransactionQueuesForNode(repo, node, queue) {
12083 var nodeQueue = treeGetValue(node);
12084 if (nodeQueue) {
12085 for (var i = 0; i < nodeQueue.length; i++) {
12086 queue.push(nodeQueue[i]);
12087 }
12088 }
12089 treeForEachChild(node, function (child) {
12090 repoAggregateTransactionQueuesForNode(repo, child, queue);
12091 });
12092}
12093/**
12094 * Remove COMPLETED transactions at or below this node in the transactionQueueTree_.
12095 */
12096function repoPruneCompletedTransactionsBelowNode(repo, node) {
12097 var queue = treeGetValue(node);
12098 if (queue) {
12099 var to = 0;
12100 for (var from = 0; from < queue.length; from++) {
12101 if (queue[from].status !== 2 /* COMPLETED */) {
12102 queue[to] = queue[from];
12103 to++;
12104 }
12105 }
12106 queue.length = to;
12107 treeSetValue(node, queue.length > 0 ? queue : undefined);
12108 }
12109 treeForEachChild(node, function (childNode) {
12110 repoPruneCompletedTransactionsBelowNode(repo, childNode);
12111 });
12112}
12113/**
12114 * Aborts all transactions on ancestors or descendants of the specified path.
12115 * Called when doing a set() or update() since we consider them incompatible
12116 * with transactions.
12117 *
12118 * @param path - Path for which we want to abort related transactions.
12119 */
12120function repoAbortTransactions(repo, path) {
12121 var affectedPath = treeGetPath(repoGetAncestorTransactionNode(repo, path));
12122 var transactionNode = treeSubTree(repo.transactionQueueTree_, path);
12123 treeForEachAncestor(transactionNode, function (node) {
12124 repoAbortTransactionsOnNode(repo, node);
12125 });
12126 repoAbortTransactionsOnNode(repo, transactionNode);
12127 treeForEachDescendant(transactionNode, function (node) {
12128 repoAbortTransactionsOnNode(repo, node);
12129 });
12130 return affectedPath;
12131}
12132/**
12133 * Abort transactions stored in this transaction queue node.
12134 *
12135 * @param node - Node to abort transactions for.
12136 */
12137function repoAbortTransactionsOnNode(repo, node) {
12138 var queue = treeGetValue(node);
12139 if (queue) {
12140 // Queue up the callbacks and fire them after cleaning up all of our
12141 // transaction state, since the callback could trigger more transactions
12142 // or sets.
12143 var callbacks = [];
12144 // Go through queue. Any already-sent transactions must be marked for
12145 // abort, while the unsent ones can be immediately aborted and removed.
12146 var events = [];
12147 var lastSent = -1;
12148 for (var i = 0; i < queue.length; i++) {
12149 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) ;
12150 else if (queue[i].status === 1 /* SENT */) {
12151 util.assert(lastSent === i - 1, 'All SENT items should be at beginning of queue.');
12152 lastSent = i;
12153 // Mark transaction for abort when it comes back.
12154 queue[i].status = 3 /* SENT_NEEDS_ABORT */;
12155 queue[i].abortReason = 'set';
12156 }
12157 else {
12158 util.assert(queue[i].status === 0 /* RUN */, 'Unexpected transaction status in abort');
12159 // We can abort it immediately.
12160 queue[i].unwatcher();
12161 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId, true));
12162 if (queue[i].onComplete) {
12163 callbacks.push(queue[i].onComplete.bind(null, new Error('set'), false, null));
12164 }
12165 }
12166 }
12167 if (lastSent === -1) {
12168 // We're not waiting for any sent transactions. We can clear the queue.
12169 treeSetValue(node, undefined);
12170 }
12171 else {
12172 // Remove the transactions we aborted.
12173 queue.length = lastSent + 1;
12174 }
12175 // Now fire the callbacks.
12176 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, treeGetPath(node), events);
12177 for (var i = 0; i < callbacks.length; i++) {
12178 exceptionGuard(callbacks[i]);
12179 }
12180 }
12181}
12182
12183/**
12184 * @license
12185 * Copyright 2017 Google LLC
12186 *
12187 * Licensed under the Apache License, Version 2.0 (the "License");
12188 * you may not use this file except in compliance with the License.
12189 * You may obtain a copy of the License at
12190 *
12191 * http://www.apache.org/licenses/LICENSE-2.0
12192 *
12193 * Unless required by applicable law or agreed to in writing, software
12194 * distributed under the License is distributed on an "AS IS" BASIS,
12195 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12196 * See the License for the specific language governing permissions and
12197 * limitations under the License.
12198 */
12199function decodePath(pathString) {
12200 var pathStringDecoded = '';
12201 var pieces = pathString.split('/');
12202 for (var i = 0; i < pieces.length; i++) {
12203 if (pieces[i].length > 0) {
12204 var piece = pieces[i];
12205 try {
12206 piece = decodeURIComponent(piece.replace(/\+/g, ' '));
12207 }
12208 catch (e) { }
12209 pathStringDecoded += '/' + piece;
12210 }
12211 }
12212 return pathStringDecoded;
12213}
12214/**
12215 * @returns key value hash
12216 */
12217function decodeQuery(queryString) {
12218 var e_1, _a;
12219 var results = {};
12220 if (queryString.charAt(0) === '?') {
12221 queryString = queryString.substring(1);
12222 }
12223 try {
12224 for (var _b = tslib.__values(queryString.split('&')), _c = _b.next(); !_c.done; _c = _b.next()) {
12225 var segment = _c.value;
12226 if (segment.length === 0) {
12227 continue;
12228 }
12229 var kv = segment.split('=');
12230 if (kv.length === 2) {
12231 results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
12232 }
12233 else {
12234 warn("Invalid query segment '" + segment + "' in query '" + queryString + "'");
12235 }
12236 }
12237 }
12238 catch (e_1_1) { e_1 = { error: e_1_1 }; }
12239 finally {
12240 try {
12241 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
12242 }
12243 finally { if (e_1) throw e_1.error; }
12244 }
12245 return results;
12246}
12247var parseRepoInfo = function (dataURL, nodeAdmin) {
12248 var parsedUrl = parseDatabaseURL(dataURL), namespace = parsedUrl.namespace;
12249 if (parsedUrl.domain === 'firebase.com') {
12250 fatal(parsedUrl.host +
12251 ' is no longer supported. ' +
12252 'Please use <YOUR FIREBASE>.firebaseio.com instead');
12253 }
12254 // Catch common error of uninitialized namespace value.
12255 if ((!namespace || namespace === 'undefined') &&
12256 parsedUrl.domain !== 'localhost') {
12257 fatal('Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com');
12258 }
12259 if (!parsedUrl.secure) {
12260 warnIfPageIsSecure();
12261 }
12262 var webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
12263 return {
12264 repoInfo: new RepoInfo(parsedUrl.host, parsedUrl.secure, namespace, nodeAdmin, webSocketOnly,
12265 /*persistenceKey=*/ '',
12266 /*includeNamespaceInQueryParams=*/ namespace !== parsedUrl.subdomain),
12267 path: new Path(parsedUrl.pathString)
12268 };
12269};
12270var parseDatabaseURL = function (dataURL) {
12271 // Default to empty strings in the event of a malformed string.
12272 var host = '', domain = '', subdomain = '', pathString = '', namespace = '';
12273 // Always default to SSL, unless otherwise specified.
12274 var secure = true, scheme = 'https', port = 443;
12275 // Don't do any validation here. The caller is responsible for validating the result of parsing.
12276 if (typeof dataURL === 'string') {
12277 // Parse scheme.
12278 var colonInd = dataURL.indexOf('//');
12279 if (colonInd >= 0) {
12280 scheme = dataURL.substring(0, colonInd - 1);
12281 dataURL = dataURL.substring(colonInd + 2);
12282 }
12283 // Parse host, path, and query string.
12284 var slashInd = dataURL.indexOf('/');
12285 if (slashInd === -1) {
12286 slashInd = dataURL.length;
12287 }
12288 var questionMarkInd = dataURL.indexOf('?');
12289 if (questionMarkInd === -1) {
12290 questionMarkInd = dataURL.length;
12291 }
12292 host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
12293 if (slashInd < questionMarkInd) {
12294 // For pathString, questionMarkInd will always come after slashInd
12295 pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
12296 }
12297 var queryParams = decodeQuery(dataURL.substring(Math.min(dataURL.length, questionMarkInd)));
12298 // If we have a port, use scheme for determining if it's secure.
12299 colonInd = host.indexOf(':');
12300 if (colonInd >= 0) {
12301 secure = scheme === 'https' || scheme === 'wss';
12302 port = parseInt(host.substring(colonInd + 1), 10);
12303 }
12304 else {
12305 colonInd = host.length;
12306 }
12307 var hostWithoutPort = host.slice(0, colonInd);
12308 if (hostWithoutPort.toLowerCase() === 'localhost') {
12309 domain = 'localhost';
12310 }
12311 else if (hostWithoutPort.split('.').length <= 2) {
12312 domain = hostWithoutPort;
12313 }
12314 else {
12315 // Interpret the subdomain of a 3 or more component URL as the namespace name.
12316 var dotInd = host.indexOf('.');
12317 subdomain = host.substring(0, dotInd).toLowerCase();
12318 domain = host.substring(dotInd + 1);
12319 // Normalize namespaces to lowercase to share storage / connection.
12320 namespace = subdomain;
12321 }
12322 // Always treat the value of the `ns` as the namespace name if it is present.
12323 if ('ns' in queryParams) {
12324 namespace = queryParams['ns'];
12325 }
12326 }
12327 return {
12328 host: host,
12329 port: port,
12330 domain: domain,
12331 subdomain: subdomain,
12332 secure: secure,
12333 scheme: scheme,
12334 pathString: pathString,
12335 namespace: namespace
12336 };
12337};
12338
12339/**
12340 * @license
12341 * Copyright 2017 Google LLC
12342 *
12343 * Licensed under the Apache License, Version 2.0 (the "License");
12344 * you may not use this file except in compliance with the License.
12345 * You may obtain a copy of the License at
12346 *
12347 * http://www.apache.org/licenses/LICENSE-2.0
12348 *
12349 * Unless required by applicable law or agreed to in writing, software
12350 * distributed under the License is distributed on an "AS IS" BASIS,
12351 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12352 * See the License for the specific language governing permissions and
12353 * limitations under the License.
12354 */
12355/**
12356 * Encapsulates the data needed to raise an event
12357 */
12358var DataEvent = /** @class */ (function () {
12359 /**
12360 * @param eventType - One of: value, child_added, child_changed, child_moved, child_removed
12361 * @param eventRegistration - The function to call to with the event data. User provided
12362 * @param snapshot - The data backing the event
12363 * @param prevName - Optional, the name of the previous child for child_* events.
12364 */
12365 function DataEvent(eventType, eventRegistration, snapshot, prevName) {
12366 this.eventType = eventType;
12367 this.eventRegistration = eventRegistration;
12368 this.snapshot = snapshot;
12369 this.prevName = prevName;
12370 }
12371 DataEvent.prototype.getPath = function () {
12372 var ref = this.snapshot.ref;
12373 if (this.eventType === 'value') {
12374 return ref._path;
12375 }
12376 else {
12377 return ref.parent._path;
12378 }
12379 };
12380 DataEvent.prototype.getEventType = function () {
12381 return this.eventType;
12382 };
12383 DataEvent.prototype.getEventRunner = function () {
12384 return this.eventRegistration.getEventRunner(this);
12385 };
12386 DataEvent.prototype.toString = function () {
12387 return (this.getPath().toString() +
12388 ':' +
12389 this.eventType +
12390 ':' +
12391 util.stringify(this.snapshot.exportVal()));
12392 };
12393 return DataEvent;
12394}());
12395var CancelEvent = /** @class */ (function () {
12396 function CancelEvent(eventRegistration, error, path) {
12397 this.eventRegistration = eventRegistration;
12398 this.error = error;
12399 this.path = path;
12400 }
12401 CancelEvent.prototype.getPath = function () {
12402 return this.path;
12403 };
12404 CancelEvent.prototype.getEventType = function () {
12405 return 'cancel';
12406 };
12407 CancelEvent.prototype.getEventRunner = function () {
12408 return this.eventRegistration.getEventRunner(this);
12409 };
12410 CancelEvent.prototype.toString = function () {
12411 return this.path.toString() + ':cancel';
12412 };
12413 return CancelEvent;
12414}());
12415
12416/**
12417 * @license
12418 * Copyright 2017 Google LLC
12419 *
12420 * Licensed under the Apache License, Version 2.0 (the "License");
12421 * you may not use this file except in compliance with the License.
12422 * You may obtain a copy of the License at
12423 *
12424 * http://www.apache.org/licenses/LICENSE-2.0
12425 *
12426 * Unless required by applicable law or agreed to in writing, software
12427 * distributed under the License is distributed on an "AS IS" BASIS,
12428 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12429 * See the License for the specific language governing permissions and
12430 * limitations under the License.
12431 */
12432/**
12433 * A wrapper class that converts events from the database@exp SDK to the legacy
12434 * Database SDK. Events are not converted directly as event registration relies
12435 * on reference comparison of the original user callback (see `matches()`) and
12436 * relies on equality of the legacy SDK's `context` object.
12437 */
12438var CallbackContext = /** @class */ (function () {
12439 function CallbackContext(snapshotCallback, cancelCallback) {
12440 this.snapshotCallback = snapshotCallback;
12441 this.cancelCallback = cancelCallback;
12442 }
12443 CallbackContext.prototype.onValue = function (expDataSnapshot, previousChildName) {
12444 this.snapshotCallback.call(null, expDataSnapshot, previousChildName);
12445 };
12446 CallbackContext.prototype.onCancel = function (error) {
12447 util.assert(this.hasCancelCallback, 'Raising a cancel event on a listener with no cancel callback');
12448 return this.cancelCallback.call(null, error);
12449 };
12450 Object.defineProperty(CallbackContext.prototype, "hasCancelCallback", {
12451 get: function () {
12452 return !!this.cancelCallback;
12453 },
12454 enumerable: false,
12455 configurable: true
12456 });
12457 CallbackContext.prototype.matches = function (other) {
12458 return (this.snapshotCallback === other.snapshotCallback ||
12459 (this.snapshotCallback.userCallback !== undefined &&
12460 this.snapshotCallback.userCallback ===
12461 other.snapshotCallback.userCallback &&
12462 this.snapshotCallback.context === other.snapshotCallback.context));
12463 };
12464 return CallbackContext;
12465}());
12466
12467/**
12468 * @license
12469 * Copyright 2021 Google LLC
12470 *
12471 * Licensed under the Apache License, Version 2.0 (the "License");
12472 * you may not use this file except in compliance with the License.
12473 * You may obtain a copy of the License at
12474 *
12475 * http://www.apache.org/licenses/LICENSE-2.0
12476 *
12477 * Unless required by applicable law or agreed to in writing, software
12478 * distributed under the License is distributed on an "AS IS" BASIS,
12479 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12480 * See the License for the specific language governing permissions and
12481 * limitations under the License.
12482 */
12483/**
12484 * The `onDisconnect` class allows you to write or clear data when your client
12485 * disconnects from the Database server. These updates occur whether your
12486 * client disconnects cleanly or not, so you can rely on them to clean up data
12487 * even if a connection is dropped or a client crashes.
12488 *
12489 * The `onDisconnect` class is most commonly used to manage presence in
12490 * applications where it is useful to detect how many clients are connected and
12491 * when other clients disconnect. See
12492 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12493 * for more information.
12494 *
12495 * To avoid problems when a connection is dropped before the requests can be
12496 * transferred to the Database server, these functions should be called before
12497 * writing any data.
12498 *
12499 * Note that `onDisconnect` operations are only triggered once. If you want an
12500 * operation to occur each time a disconnect occurs, you'll need to re-establish
12501 * the `onDisconnect` operations each time you reconnect.
12502 */
12503var OnDisconnect = /** @class */ (function () {
12504 /** @hideconstructor */
12505 function OnDisconnect(_repo, _path) {
12506 this._repo = _repo;
12507 this._path = _path;
12508 }
12509 /**
12510 * Cancels all previously queued `onDisconnect()` set or update events for this
12511 * location and all children.
12512 *
12513 * If a write has been queued for this location via a `set()` or `update()` at a
12514 * parent location, the write at this location will be canceled, though writes
12515 * to sibling locations will still occur.
12516 *
12517 * @returns Resolves when synchronization to the server is complete.
12518 */
12519 OnDisconnect.prototype.cancel = function () {
12520 var deferred = new util.Deferred();
12521 repoOnDisconnectCancel(this._repo, this._path, deferred.wrapCallback(function () { }));
12522 return deferred.promise;
12523 };
12524 /**
12525 * Ensures the data at this location is deleted when the client is disconnected
12526 * (due to closing the browser, navigating to a new page, or network issues).
12527 *
12528 * @returns Resolves when synchronization to the server is complete.
12529 */
12530 OnDisconnect.prototype.remove = function () {
12531 validateWritablePath('OnDisconnect.remove', this._path);
12532 var deferred = new util.Deferred();
12533 repoOnDisconnectSet(this._repo, this._path, null, deferred.wrapCallback(function () { }));
12534 return deferred.promise;
12535 };
12536 /**
12537 * Ensures the data at this location is set to the specified value when the
12538 * client is disconnected (due to closing the browser, navigating to a new page,
12539 * or network issues).
12540 *
12541 * `set()` is especially useful for implementing "presence" systems, where a
12542 * value should be changed or cleared when a user disconnects so that they
12543 * appear "offline" to other users. See
12544 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12545 * for more information.
12546 *
12547 * Note that `onDisconnect` operations are only triggered once. If you want an
12548 * operation to occur each time a disconnect occurs, you'll need to re-establish
12549 * the `onDisconnect` operations each time.
12550 *
12551 * @param value - The value to be written to this location on disconnect (can
12552 * be an object, array, string, number, boolean, or null).
12553 * @returns Resolves when synchronization to the Database is complete.
12554 */
12555 OnDisconnect.prototype.set = function (value) {
12556 validateWritablePath('OnDisconnect.set', this._path);
12557 validateFirebaseDataArg('OnDisconnect.set', value, this._path, false);
12558 var deferred = new util.Deferred();
12559 repoOnDisconnectSet(this._repo, this._path, value, deferred.wrapCallback(function () { }));
12560 return deferred.promise;
12561 };
12562 /**
12563 * Ensures the data at this location is set to the specified value and priority
12564 * when the client is disconnected (due to closing the browser, navigating to a
12565 * new page, or network issues).
12566 *
12567 * @param value - The value to be written to this location on disconnect (can
12568 * be an object, array, string, number, boolean, or null).
12569 * @param priority - The priority to be written (string, number, or null).
12570 * @returns Resolves when synchronization to the Database is complete.
12571 */
12572 OnDisconnect.prototype.setWithPriority = function (value, priority) {
12573 validateWritablePath('OnDisconnect.setWithPriority', this._path);
12574 validateFirebaseDataArg('OnDisconnect.setWithPriority', value, this._path, false);
12575 validatePriority('OnDisconnect.setWithPriority', priority, false);
12576 var deferred = new util.Deferred();
12577 repoOnDisconnectSetWithPriority(this._repo, this._path, value, priority, deferred.wrapCallback(function () { }));
12578 return deferred.promise;
12579 };
12580 /**
12581 * Writes multiple values at this location when the client is disconnected (due
12582 * to closing the browser, navigating to a new page, or network issues).
12583 *
12584 * The `values` argument contains multiple property-value pairs that will be
12585 * written to the Database together. Each child property can either be a simple
12586 * property (for example, "name") or a relative path (for example, "name/first")
12587 * from the current location to the data to update.
12588 *
12589 * As opposed to the `set()` method, `update()` can be use to selectively update
12590 * only the referenced properties at the current location (instead of replacing
12591 * all the child properties at the current location).
12592 *
12593 * @param values - Object containing multiple values.
12594 * @returns Resolves when synchronization to the Database is complete.
12595 */
12596 OnDisconnect.prototype.update = function (values) {
12597 validateWritablePath('OnDisconnect.update', this._path);
12598 validateFirebaseMergeDataArg('OnDisconnect.update', values, this._path, false);
12599 var deferred = new util.Deferred();
12600 repoOnDisconnectUpdate(this._repo, this._path, values, deferred.wrapCallback(function () { }));
12601 return deferred.promise;
12602 };
12603 return OnDisconnect;
12604}());
12605
12606/**
12607 * @license
12608 * Copyright 2020 Google LLC
12609 *
12610 * Licensed under the Apache License, Version 2.0 (the "License");
12611 * you may not use this file except in compliance with the License.
12612 * You may obtain a copy of the License at
12613 *
12614 * http://www.apache.org/licenses/LICENSE-2.0
12615 *
12616 * Unless required by applicable law or agreed to in writing, software
12617 * distributed under the License is distributed on an "AS IS" BASIS,
12618 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12619 * See the License for the specific language governing permissions and
12620 * limitations under the License.
12621 */
12622/**
12623 * @internal
12624 */
12625var QueryImpl = /** @class */ (function () {
12626 /**
12627 * @hideconstructor
12628 */
12629 function QueryImpl(_repo, _path, _queryParams, _orderByCalled) {
12630 this._repo = _repo;
12631 this._path = _path;
12632 this._queryParams = _queryParams;
12633 this._orderByCalled = _orderByCalled;
12634 }
12635 Object.defineProperty(QueryImpl.prototype, "key", {
12636 get: function () {
12637 if (pathIsEmpty(this._path)) {
12638 return null;
12639 }
12640 else {
12641 return pathGetBack(this._path);
12642 }
12643 },
12644 enumerable: false,
12645 configurable: true
12646 });
12647 Object.defineProperty(QueryImpl.prototype, "ref", {
12648 get: function () {
12649 return new ReferenceImpl(this._repo, this._path);
12650 },
12651 enumerable: false,
12652 configurable: true
12653 });
12654 Object.defineProperty(QueryImpl.prototype, "_queryIdentifier", {
12655 get: function () {
12656 var obj = queryParamsGetQueryObject(this._queryParams);
12657 var id = ObjectToUniqueKey(obj);
12658 return id === '{}' ? 'default' : id;
12659 },
12660 enumerable: false,
12661 configurable: true
12662 });
12663 Object.defineProperty(QueryImpl.prototype, "_queryObject", {
12664 /**
12665 * An object representation of the query parameters used by this Query.
12666 */
12667 get: function () {
12668 return queryParamsGetQueryObject(this._queryParams);
12669 },
12670 enumerable: false,
12671 configurable: true
12672 });
12673 QueryImpl.prototype.isEqual = function (other) {
12674 other = util.getModularInstance(other);
12675 if (!(other instanceof QueryImpl)) {
12676 return false;
12677 }
12678 var sameRepo = this._repo === other._repo;
12679 var samePath = pathEquals(this._path, other._path);
12680 var sameQueryIdentifier = this._queryIdentifier === other._queryIdentifier;
12681 return sameRepo && samePath && sameQueryIdentifier;
12682 };
12683 QueryImpl.prototype.toJSON = function () {
12684 return this.toString();
12685 };
12686 QueryImpl.prototype.toString = function () {
12687 return this._repo.toString() + pathToUrlEncodedString(this._path);
12688 };
12689 return QueryImpl;
12690}());
12691/**
12692 * Validates that no other order by call has been made
12693 */
12694function validateNoPreviousOrderByCall(query, fnName) {
12695 if (query._orderByCalled === true) {
12696 throw new Error(fnName + ": You can't combine multiple orderBy calls.");
12697 }
12698}
12699/**
12700 * Validates start/end values for queries.
12701 */
12702function validateQueryEndpoints(params) {
12703 var startNode = null;
12704 var endNode = null;
12705 if (params.hasStart()) {
12706 startNode = params.getIndexStartValue();
12707 }
12708 if (params.hasEnd()) {
12709 endNode = params.getIndexEndValue();
12710 }
12711 if (params.getIndex() === KEY_INDEX) {
12712 var tooManyArgsError = 'Query: When ordering by key, you may only pass one argument to ' +
12713 'startAt(), endAt(), or equalTo().';
12714 var wrongArgTypeError = 'Query: When ordering by key, the argument passed to startAt(), startAfter(), ' +
12715 'endAt(), endBefore(), or equalTo() must be a string.';
12716 if (params.hasStart()) {
12717 var startName = params.getIndexStartName();
12718 if (startName !== MIN_NAME) {
12719 throw new Error(tooManyArgsError);
12720 }
12721 else if (typeof startNode !== 'string') {
12722 throw new Error(wrongArgTypeError);
12723 }
12724 }
12725 if (params.hasEnd()) {
12726 var endName = params.getIndexEndName();
12727 if (endName !== MAX_NAME) {
12728 throw new Error(tooManyArgsError);
12729 }
12730 else if (typeof endNode !== 'string') {
12731 throw new Error(wrongArgTypeError);
12732 }
12733 }
12734 }
12735 else if (params.getIndex() === PRIORITY_INDEX) {
12736 if ((startNode != null && !isValidPriority(startNode)) ||
12737 (endNode != null && !isValidPriority(endNode))) {
12738 throw new Error('Query: When ordering by priority, the first argument passed to startAt(), ' +
12739 'startAfter() endAt(), endBefore(), or equalTo() must be a valid priority value ' +
12740 '(null, a number, or a string).');
12741 }
12742 }
12743 else {
12744 util.assert(params.getIndex() instanceof PathIndex ||
12745 params.getIndex() === VALUE_INDEX, 'unknown index type.');
12746 if ((startNode != null && typeof startNode === 'object') ||
12747 (endNode != null && typeof endNode === 'object')) {
12748 throw new Error('Query: First argument passed to startAt(), startAfter(), endAt(), endBefore(), or ' +
12749 'equalTo() cannot be an object.');
12750 }
12751 }
12752}
12753/**
12754 * Validates that limit* has been called with the correct combination of parameters
12755 */
12756function validateLimit(params) {
12757 if (params.hasStart() &&
12758 params.hasEnd() &&
12759 params.hasLimit() &&
12760 !params.hasAnchoredLimit()) {
12761 throw new Error("Query: Can't combine startAt(), startAfter(), endAt(), endBefore(), and limit(). Use " +
12762 'limitToFirst() or limitToLast() instead.');
12763 }
12764}
12765/**
12766 * @internal
12767 */
12768var ReferenceImpl = /** @class */ (function (_super) {
12769 tslib.__extends(ReferenceImpl, _super);
12770 /** @hideconstructor */
12771 function ReferenceImpl(repo, path) {
12772 return _super.call(this, repo, path, new QueryParams(), false) || this;
12773 }
12774 Object.defineProperty(ReferenceImpl.prototype, "parent", {
12775 get: function () {
12776 var parentPath = pathParent(this._path);
12777 return parentPath === null
12778 ? null
12779 : new ReferenceImpl(this._repo, parentPath);
12780 },
12781 enumerable: false,
12782 configurable: true
12783 });
12784 Object.defineProperty(ReferenceImpl.prototype, "root", {
12785 get: function () {
12786 var ref = this;
12787 while (ref.parent !== null) {
12788 ref = ref.parent;
12789 }
12790 return ref;
12791 },
12792 enumerable: false,
12793 configurable: true
12794 });
12795 return ReferenceImpl;
12796}(QueryImpl));
12797/**
12798 * A `DataSnapshot` contains data from a Database location.
12799 *
12800 * Any time you read data from the Database, you receive the data as a
12801 * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach
12802 * with `on()` or `once()`. You can extract the contents of the snapshot as a
12803 * JavaScript object by calling the `val()` method. Alternatively, you can
12804 * traverse into the snapshot by calling `child()` to return child snapshots
12805 * (which you could then call `val()` on).
12806 *
12807 * A `DataSnapshot` is an efficiently generated, immutable copy of the data at
12808 * a Database location. It cannot be modified and will never change (to modify
12809 * data, you always call the `set()` method on a `Reference` directly).
12810 */
12811var DataSnapshot = /** @class */ (function () {
12812 /**
12813 * @param _node - A SnapshotNode to wrap.
12814 * @param ref - The location this snapshot came from.
12815 * @param _index - The iteration order for this snapshot
12816 * @hideconstructor
12817 */
12818 function DataSnapshot(_node,
12819 /**
12820 * The location of this DataSnapshot.
12821 */
12822 ref, _index) {
12823 this._node = _node;
12824 this.ref = ref;
12825 this._index = _index;
12826 }
12827 Object.defineProperty(DataSnapshot.prototype, "priority", {
12828 /**
12829 * Gets the priority value of the data in this `DataSnapshot`.
12830 *
12831 * Applications need not use priority but can order collections by
12832 * ordinary properties (see
12833 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data |Sorting and filtering data}
12834 * ).
12835 */
12836 get: function () {
12837 // typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
12838 return this._node.getPriority().val();
12839 },
12840 enumerable: false,
12841 configurable: true
12842 });
12843 Object.defineProperty(DataSnapshot.prototype, "key", {
12844 /**
12845 * The key (last part of the path) of the location of this `DataSnapshot`.
12846 *
12847 * The last token in a Database location is considered its key. For example,
12848 * "ada" is the key for the /users/ada/ node. Accessing the key on any
12849 * `DataSnapshot` will return the key for the location that generated it.
12850 * However, accessing the key on the root URL of a Database will return
12851 * `null`.
12852 */
12853 get: function () {
12854 return this.ref.key;
12855 },
12856 enumerable: false,
12857 configurable: true
12858 });
12859 Object.defineProperty(DataSnapshot.prototype, "size", {
12860 /** Returns the number of child properties of this `DataSnapshot`. */
12861 get: function () {
12862 return this._node.numChildren();
12863 },
12864 enumerable: false,
12865 configurable: true
12866 });
12867 /**
12868 * Gets another `DataSnapshot` for the location at the specified relative path.
12869 *
12870 * Passing a relative path to the `child()` method of a DataSnapshot returns
12871 * another `DataSnapshot` for the location at the specified relative path. The
12872 * relative path can either be a simple child name (for example, "ada") or a
12873 * deeper, slash-separated path (for example, "ada/name/first"). If the child
12874 * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot`
12875 * whose value is `null`) is returned.
12876 *
12877 * @param path - A relative path to the location of child data.
12878 */
12879 DataSnapshot.prototype.child = function (path) {
12880 var childPath = new Path(path);
12881 var childRef = child(this.ref, path);
12882 return new DataSnapshot(this._node.getChild(childPath), childRef, PRIORITY_INDEX);
12883 };
12884 /**
12885 * Returns true if this `DataSnapshot` contains any data. It is slightly more
12886 * efficient than using `snapshot.val() !== null`.
12887 */
12888 DataSnapshot.prototype.exists = function () {
12889 return !this._node.isEmpty();
12890 };
12891 /**
12892 * Exports the entire contents of the DataSnapshot as a JavaScript object.
12893 *
12894 * The `exportVal()` method is similar to `val()`, except priority information
12895 * is included (if available), making it suitable for backing up your data.
12896 *
12897 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12898 * Array, string, number, boolean, or `null`).
12899 */
12900 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12901 DataSnapshot.prototype.exportVal = function () {
12902 return this._node.val(true);
12903 };
12904 /**
12905 * Enumerates the top-level children in the `DataSnapshot`.
12906 *
12907 * Because of the way JavaScript objects work, the ordering of data in the
12908 * JavaScript object returned by `val()` is not guaranteed to match the
12909 * ordering on the server nor the ordering of `onChildAdded()` events. That is
12910 * where `forEach()` comes in handy. It guarantees the children of a
12911 * `DataSnapshot` will be iterated in their query order.
12912 *
12913 * If no explicit `orderBy*()` method is used, results are returned
12914 * ordered by key (unless priorities are used, in which case, results are
12915 * returned by priority).
12916 *
12917 * @param action - A function that will be called for each child DataSnapshot.
12918 * The callback can return true to cancel further enumeration.
12919 * @returns true if enumeration was canceled due to your callback returning
12920 * true.
12921 */
12922 DataSnapshot.prototype.forEach = function (action) {
12923 var _this = this;
12924 if (this._node.isLeafNode()) {
12925 return false;
12926 }
12927 var childrenNode = this._node;
12928 // Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
12929 return !!childrenNode.forEachChild(this._index, function (key, node) {
12930 return action(new DataSnapshot(node, child(_this.ref, key), PRIORITY_INDEX));
12931 });
12932 };
12933 /**
12934 * Returns true if the specified child path has (non-null) data.
12935 *
12936 * @param path - A relative path to the location of a potential child.
12937 * @returns `true` if data exists at the specified child path; else
12938 * `false`.
12939 */
12940 DataSnapshot.prototype.hasChild = function (path) {
12941 var childPath = new Path(path);
12942 return !this._node.getChild(childPath).isEmpty();
12943 };
12944 /**
12945 * Returns whether or not the `DataSnapshot` has any non-`null` child
12946 * properties.
12947 *
12948 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
12949 * children. If it does, you can enumerate them using `forEach()`. If it
12950 * doesn't, then either this snapshot contains a primitive value (which can be
12951 * retrieved with `val()`) or it is empty (in which case, `val()` will return
12952 * `null`).
12953 *
12954 * @returns true if this snapshot has any children; else false.
12955 */
12956 DataSnapshot.prototype.hasChildren = function () {
12957 if (this._node.isLeafNode()) {
12958 return false;
12959 }
12960 else {
12961 return !this._node.isEmpty();
12962 }
12963 };
12964 /**
12965 * Returns a JSON-serializable representation of this object.
12966 */
12967 DataSnapshot.prototype.toJSON = function () {
12968 return this.exportVal();
12969 };
12970 /**
12971 * Extracts a JavaScript value from a `DataSnapshot`.
12972 *
12973 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
12974 * scalar type (string, number, or boolean), an array, or an object. It may
12975 * also return null, indicating that the `DataSnapshot` is empty (contains no
12976 * data).
12977 *
12978 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12979 * Array, string, number, boolean, or `null`).
12980 */
12981 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12982 DataSnapshot.prototype.val = function () {
12983 return this._node.val();
12984 };
12985 return DataSnapshot;
12986}());
12987/**
12988 *
12989 * Returns a `Reference` representing the location in the Database
12990 * corresponding to the provided path. If no path is provided, the `Reference`
12991 * will point to the root of the Database.
12992 *
12993 * @param db - The database instance to obtain a reference for.
12994 * @param path - Optional path representing the location the returned
12995 * `Reference` will point. If not provided, the returned `Reference` will
12996 * point to the root of the Database.
12997 * @returns If a path is provided, a `Reference`
12998 * pointing to the provided path. Otherwise, a `Reference` pointing to the
12999 * root of the Database.
13000 */
13001function ref(db, path) {
13002 db = util.getModularInstance(db);
13003 db._checkNotDeleted('ref');
13004 return path !== undefined ? child(db._root, path) : db._root;
13005}
13006/**
13007 * Returns a `Reference` representing the location in the Database
13008 * corresponding to the provided Firebase URL.
13009 *
13010 * An exception is thrown if the URL is not a valid Firebase Database URL or it
13011 * has a different domain than the current `Database` instance.
13012 *
13013 * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored
13014 * and are not applied to the returned `Reference`.
13015 *
13016 * @param db - The database instance to obtain a reference for.
13017 * @param url - The Firebase URL at which the returned `Reference` will
13018 * point.
13019 * @returns A `Reference` pointing to the provided
13020 * Firebase URL.
13021 */
13022function refFromURL(db, url) {
13023 db = util.getModularInstance(db);
13024 db._checkNotDeleted('refFromURL');
13025 var parsedURL = parseRepoInfo(url, db._repo.repoInfo_.nodeAdmin);
13026 validateUrl('refFromURL', parsedURL);
13027 var repoInfo = parsedURL.repoInfo;
13028 if (!db._repo.repoInfo_.isCustomHost() &&
13029 repoInfo.host !== db._repo.repoInfo_.host) {
13030 fatal('refFromURL' +
13031 ': Host name does not match the current database: ' +
13032 '(found ' +
13033 repoInfo.host +
13034 ' but expected ' +
13035 db._repo.repoInfo_.host +
13036 ')');
13037 }
13038 return ref(db, parsedURL.path.toString());
13039}
13040/**
13041 * Gets a `Reference` for the location at the specified relative path.
13042 *
13043 * The relative path can either be a simple child name (for example, "ada") or
13044 * a deeper slash-separated path (for example, "ada/name/first").
13045 *
13046 * @param parent - The parent location.
13047 * @param path - A relative path from this location to the desired child
13048 * location.
13049 * @returns The specified child location.
13050 */
13051function child(parent, path) {
13052 parent = util.getModularInstance(parent);
13053 if (pathGetFront(parent._path) === null) {
13054 validateRootPathString('child', 'path', path, false);
13055 }
13056 else {
13057 validatePathString('child', 'path', path, false);
13058 }
13059 return new ReferenceImpl(parent._repo, pathChild(parent._path, path));
13060}
13061/**
13062 * Returns an `OnDisconnect` object - see
13063 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
13064 * for more information on how to use it.
13065 *
13066 * @param ref - The reference to add OnDisconnect triggers for.
13067 */
13068function onDisconnect(ref) {
13069 ref = util.getModularInstance(ref);
13070 return new OnDisconnect(ref._repo, ref._path);
13071}
13072/**
13073 * Generates a new child location using a unique key and returns its
13074 * `Reference`.
13075 *
13076 * This is the most common pattern for adding data to a collection of items.
13077 *
13078 * If you provide a value to `push()`, the value is written to the
13079 * generated location. If you don't pass a value, nothing is written to the
13080 * database and the child remains empty (but you can use the `Reference`
13081 * elsewhere).
13082 *
13083 * The unique keys generated by `push()` are ordered by the current time, so the
13084 * resulting list of items is chronologically sorted. The keys are also
13085 * designed to be unguessable (they contain 72 random bits of entropy).
13086 *
13087 * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}
13088 * </br>See {@link ttps://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html | The 2^120 Ways to Ensure Unique Identifiers}
13089 *
13090 * @param parent - The parent location.
13091 * @param value - Optional value to be written at the generated location.
13092 * @returns Combined `Promise` and `Reference`; resolves when write is complete,
13093 * but can be used immediately as the `Reference` to the child location.
13094 */
13095function push(parent, value) {
13096 parent = util.getModularInstance(parent);
13097 validateWritablePath('push', parent._path);
13098 validateFirebaseDataArg('push', value, parent._path, true);
13099 var now = repoServerTime(parent._repo);
13100 var name = nextPushId(now);
13101 // push() returns a ThennableReference whose promise is fulfilled with a
13102 // regular Reference. We use child() to create handles to two different
13103 // references. The first is turned into a ThennableReference below by adding
13104 // then() and catch() methods and is used as the return value of push(). The
13105 // second remains a regular Reference and is used as the fulfilled value of
13106 // the first ThennableReference.
13107 var thennablePushRef = child(parent, name);
13108 var pushRef = child(parent, name);
13109 var promise;
13110 if (value != null) {
13111 promise = set(pushRef, value).then(function () { return pushRef; });
13112 }
13113 else {
13114 promise = Promise.resolve(pushRef);
13115 }
13116 thennablePushRef.then = promise.then.bind(promise);
13117 thennablePushRef.catch = promise.then.bind(promise, undefined);
13118 return thennablePushRef;
13119}
13120/**
13121 * Removes the data at this Database location.
13122 *
13123 * Any data at child locations will also be deleted.
13124 *
13125 * The effect of the remove will be visible immediately and the corresponding
13126 * event 'value' will be triggered. Synchronization of the remove to the
13127 * Firebase servers will also be started, and the returned Promise will resolve
13128 * when complete. If provided, the onComplete callback will be called
13129 * asynchronously after synchronization has finished.
13130 *
13131 * @param ref - The location to remove.
13132 * @returns Resolves when remove on server is complete.
13133 */
13134function remove(ref) {
13135 validateWritablePath('remove', ref._path);
13136 return set(ref, null);
13137}
13138/**
13139 * Writes data to this Database location.
13140 *
13141 * This will overwrite any data at this location and all child locations.
13142 *
13143 * The effect of the write will be visible immediately, and the corresponding
13144 * events ("value", "child_added", etc.) will be triggered. Synchronization of
13145 * the data to the Firebase servers will also be started, and the returned
13146 * Promise will resolve when complete. If provided, the `onComplete` callback
13147 * will be called asynchronously after synchronization has finished.
13148 *
13149 * Passing `null` for the new value is equivalent to calling `remove()`; namely,
13150 * all data at this location and all child locations will be deleted.
13151 *
13152 * `set()` will remove any priority stored at this location, so if priority is
13153 * meant to be preserved, you need to use `setWithPriority()` instead.
13154 *
13155 * Note that modifying data with `set()` will cancel any pending transactions
13156 * at that location, so extreme care should be taken if mixing `set()` and
13157 * `transaction()` to modify the same data.
13158 *
13159 * A single `set()` will generate a single "value" event at the location where
13160 * the `set()` was performed.
13161 *
13162 * @param ref - The location to write to.
13163 * @param value - The value to be written (string, number, boolean, object,
13164 * array, or null).
13165 * @returns Resolves when write to server is complete.
13166 */
13167function set(ref, value) {
13168 ref = util.getModularInstance(ref);
13169 validateWritablePath('set', ref._path);
13170 validateFirebaseDataArg('set', value, ref._path, false);
13171 var deferred = new util.Deferred();
13172 repoSetWithPriority(ref._repo, ref._path, value,
13173 /*priority=*/ null, deferred.wrapCallback(function () { }));
13174 return deferred.promise;
13175}
13176/**
13177 * Sets a priority for the data at this Database location.
13178 *
13179 * Applications need not use priority but can order collections by
13180 * ordinary properties (see
13181 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13182 * ).
13183 *
13184 * @param ref - The location to write to.
13185 * @param priority - The priority to be written (string, number, or null).
13186 * @returns Resolves when write to server is complete.
13187 */
13188function setPriority(ref, priority) {
13189 ref = util.getModularInstance(ref);
13190 validateWritablePath('setPriority', ref._path);
13191 validatePriority('setPriority', priority, false);
13192 var deferred = new util.Deferred();
13193 repoSetWithPriority(ref._repo, pathChild(ref._path, '.priority'), priority, null, deferred.wrapCallback(function () { }));
13194 return deferred.promise;
13195}
13196/**
13197 * Writes data the Database location. Like `set()` but also specifies the
13198 * priority for that data.
13199 *
13200 * Applications need not use priority but can order collections by
13201 * ordinary properties (see
13202 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13203 * ).
13204 *
13205 * @param ref - The location to write to.
13206 * @param value - The value to be written (string, number, boolean, object,
13207 * array, or null).
13208 * @param priority - The priority to be written (string, number, or null).
13209 * @returns Resolves when write to server is complete.
13210 */
13211function setWithPriority(ref, value, priority) {
13212 validateWritablePath('setWithPriority', ref._path);
13213 validateFirebaseDataArg('setWithPriority', value, ref._path, false);
13214 validatePriority('setWithPriority', priority, false);
13215 if (ref.key === '.length' || ref.key === '.keys') {
13216 throw 'setWithPriority failed: ' + ref.key + ' is a read-only object.';
13217 }
13218 var deferred = new util.Deferred();
13219 repoSetWithPriority(ref._repo, ref._path, value, priority, deferred.wrapCallback(function () { }));
13220 return deferred.promise;
13221}
13222/**
13223 * Writes multiple values to the Database at once.
13224 *
13225 * The `values` argument contains multiple property-value pairs that will be
13226 * written to the Database together. Each child property can either be a simple
13227 * property (for example, "name") or a relative path (for example,
13228 * "name/first") from the current location to the data to update.
13229 *
13230 * As opposed to the `set()` method, `update()` can be use to selectively update
13231 * only the referenced properties at the current location (instead of replacing
13232 * all the child properties at the current location).
13233 *
13234 * The effect of the write will be visible immediately, and the corresponding
13235 * events ('value', 'child_added', etc.) will be triggered. Synchronization of
13236 * the data to the Firebase servers will also be started, and the returned
13237 * Promise will resolve when complete. If provided, the `onComplete` callback
13238 * will be called asynchronously after synchronization has finished.
13239 *
13240 * A single `update()` will generate a single "value" event at the location
13241 * where the `update()` was performed, regardless of how many children were
13242 * modified.
13243 *
13244 * Note that modifying data with `update()` will cancel any pending
13245 * transactions at that location, so extreme care should be taken if mixing
13246 * `update()` and `transaction()` to modify the same data.
13247 *
13248 * Passing `null` to `update()` will remove the data at this location.
13249 *
13250 * See
13251 * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}.
13252 *
13253 * @param ref - The location to write to.
13254 * @param values - Object containing multiple values.
13255 * @returns Resolves when update on server is complete.
13256 */
13257function update(ref, values) {
13258 validateFirebaseMergeDataArg('update', values, ref._path, false);
13259 var deferred = new util.Deferred();
13260 repoUpdate(ref._repo, ref._path, values, deferred.wrapCallback(function () { }));
13261 return deferred.promise;
13262}
13263/**
13264 * Gets the most up-to-date result for this query.
13265 *
13266 * @param query - The query to run.
13267 * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is
13268 * available, or rejects if the client is unable to return a value (e.g., if the
13269 * server is unreachable and there is nothing cached).
13270 */
13271function get(query) {
13272 query = util.getModularInstance(query);
13273 return repoGetValue(query._repo, query).then(function (node) {
13274 return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
13275 });
13276}
13277/**
13278 * Represents registration for 'value' events.
13279 */
13280var ValueEventRegistration = /** @class */ (function () {
13281 function ValueEventRegistration(callbackContext) {
13282 this.callbackContext = callbackContext;
13283 }
13284 ValueEventRegistration.prototype.respondsTo = function (eventType) {
13285 return eventType === 'value';
13286 };
13287 ValueEventRegistration.prototype.createEvent = function (change, query) {
13288 var index = query._queryParams.getIndex();
13289 return new DataEvent('value', this, new DataSnapshot(change.snapshotNode, new ReferenceImpl(query._repo, query._path), index));
13290 };
13291 ValueEventRegistration.prototype.getEventRunner = function (eventData) {
13292 var _this = this;
13293 if (eventData.getEventType() === 'cancel') {
13294 return function () {
13295 return _this.callbackContext.onCancel(eventData.error);
13296 };
13297 }
13298 else {
13299 return function () {
13300 return _this.callbackContext.onValue(eventData.snapshot, null);
13301 };
13302 }
13303 };
13304 ValueEventRegistration.prototype.createCancelEvent = function (error, path) {
13305 if (this.callbackContext.hasCancelCallback) {
13306 return new CancelEvent(this, error, path);
13307 }
13308 else {
13309 return null;
13310 }
13311 };
13312 ValueEventRegistration.prototype.matches = function (other) {
13313 if (!(other instanceof ValueEventRegistration)) {
13314 return false;
13315 }
13316 else if (!other.callbackContext || !this.callbackContext) {
13317 // If no callback specified, we consider it to match any callback.
13318 return true;
13319 }
13320 else {
13321 return other.callbackContext.matches(this.callbackContext);
13322 }
13323 };
13324 ValueEventRegistration.prototype.hasAnyCallback = function () {
13325 return this.callbackContext !== null;
13326 };
13327 return ValueEventRegistration;
13328}());
13329/**
13330 * Represents the registration of a child_x event.
13331 */
13332var ChildEventRegistration = /** @class */ (function () {
13333 function ChildEventRegistration(eventType, callbackContext) {
13334 this.eventType = eventType;
13335 this.callbackContext = callbackContext;
13336 }
13337 ChildEventRegistration.prototype.respondsTo = function (eventType) {
13338 var eventToCheck = eventType === 'children_added' ? 'child_added' : eventType;
13339 eventToCheck =
13340 eventToCheck === 'children_removed' ? 'child_removed' : eventToCheck;
13341 return this.eventType === eventToCheck;
13342 };
13343 ChildEventRegistration.prototype.createCancelEvent = function (error, path) {
13344 if (this.callbackContext.hasCancelCallback) {
13345 return new CancelEvent(this, error, path);
13346 }
13347 else {
13348 return null;
13349 }
13350 };
13351 ChildEventRegistration.prototype.createEvent = function (change, query) {
13352 util.assert(change.childName != null, 'Child events should have a childName.');
13353 var childRef = child(new ReferenceImpl(query._repo, query._path), change.childName);
13354 var index = query._queryParams.getIndex();
13355 return new DataEvent(change.type, this, new DataSnapshot(change.snapshotNode, childRef, index), change.prevName);
13356 };
13357 ChildEventRegistration.prototype.getEventRunner = function (eventData) {
13358 var _this = this;
13359 if (eventData.getEventType() === 'cancel') {
13360 return function () {
13361 return _this.callbackContext.onCancel(eventData.error);
13362 };
13363 }
13364 else {
13365 return function () {
13366 return _this.callbackContext.onValue(eventData.snapshot, eventData.prevName);
13367 };
13368 }
13369 };
13370 ChildEventRegistration.prototype.matches = function (other) {
13371 if (other instanceof ChildEventRegistration) {
13372 return (this.eventType === other.eventType &&
13373 (!this.callbackContext ||
13374 !other.callbackContext ||
13375 this.callbackContext.matches(other.callbackContext)));
13376 }
13377 return false;
13378 };
13379 ChildEventRegistration.prototype.hasAnyCallback = function () {
13380 return !!this.callbackContext;
13381 };
13382 return ChildEventRegistration;
13383}());
13384function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) {
13385 var cancelCallback;
13386 if (typeof cancelCallbackOrListenOptions === 'object') {
13387 cancelCallback = undefined;
13388 options = cancelCallbackOrListenOptions;
13389 }
13390 if (typeof cancelCallbackOrListenOptions === 'function') {
13391 cancelCallback = cancelCallbackOrListenOptions;
13392 }
13393 if (options && options.onlyOnce) {
13394 var userCallback_1 = callback;
13395 var onceCallback = function (dataSnapshot, previousChildName) {
13396 repoRemoveEventCallbackForQuery(query._repo, query, container);
13397 userCallback_1(dataSnapshot, previousChildName);
13398 };
13399 onceCallback.userCallback = callback.userCallback;
13400 onceCallback.context = callback.context;
13401 callback = onceCallback;
13402 }
13403 var callbackContext = new CallbackContext(callback, cancelCallback || undefined);
13404 var container = eventType === 'value'
13405 ? new ValueEventRegistration(callbackContext)
13406 : new ChildEventRegistration(eventType, callbackContext);
13407 repoAddEventCallbackForQuery(query._repo, query, container);
13408 return function () { return repoRemoveEventCallbackForQuery(query._repo, query, container); };
13409}
13410function onValue(query, callback, cancelCallbackOrListenOptions, options) {
13411 return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options);
13412}
13413function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) {
13414 return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options);
13415}
13416function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) {
13417 return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options);
13418}
13419function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) {
13420 return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options);
13421}
13422function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) {
13423 return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options);
13424}
13425/**
13426 * Detaches a callback previously attached with `on()`.
13427 *
13428 * Detach a callback previously attached with `on()`. Note that if `on()` was
13429 * called multiple times with the same eventType and callback, the callback
13430 * will be called multiple times for each event, and `off()` must be called
13431 * multiple times to remove the callback. Calling `off()` on a parent listener
13432 * will not automatically remove listeners registered on child nodes, `off()`
13433 * must also be called on any child listeners to remove the callback.
13434 *
13435 * If a callback is not specified, all callbacks for the specified eventType
13436 * will be removed. Similarly, if no eventType is specified, all callbacks
13437 * for the `Reference` will be removed.
13438 *
13439 * Individual listeners can also be removed by invoking their unsubscribe
13440 * callbacks.
13441 *
13442 * @param query - The query that the listener was registered with.
13443 * @param eventType - One of the following strings: "value", "child_added",
13444 * "child_changed", "child_removed", or "child_moved." If omitted, all callbacks
13445 * for the `Reference` will be removed.
13446 * @param callback - The callback function that was passed to `on()` or
13447 * `undefined` to remove all callbacks.
13448 */
13449function off(query, eventType, callback) {
13450 var container = null;
13451 var expCallback = callback ? new CallbackContext(callback) : null;
13452 if (eventType === 'value') {
13453 container = new ValueEventRegistration(expCallback);
13454 }
13455 else if (eventType) {
13456 container = new ChildEventRegistration(eventType, expCallback);
13457 }
13458 repoRemoveEventCallbackForQuery(query._repo, query, container);
13459}
13460/**
13461 * A `QueryConstraint` is used to narrow the set of documents returned by a
13462 * Database query. `QueryConstraint`s are created by invoking {@link endAt},
13463 * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link
13464 * limitToFirst}, {@link limitToLast}, {@link orderByChild},
13465 * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} ,
13466 * {@link orderByValue} or {@link equalTo} and
13467 * can then be passed to {@link query} to create a new query instance that
13468 * also contains this `QueryConstraint`.
13469 */
13470var QueryConstraint = /** @class */ (function () {
13471 function QueryConstraint() {
13472 }
13473 return QueryConstraint;
13474}());
13475var QueryEndAtConstraint = /** @class */ (function (_super) {
13476 tslib.__extends(QueryEndAtConstraint, _super);
13477 function QueryEndAtConstraint(_value, _key) {
13478 var _this = _super.call(this) || this;
13479 _this._value = _value;
13480 _this._key = _key;
13481 return _this;
13482 }
13483 QueryEndAtConstraint.prototype._apply = function (query) {
13484 validateFirebaseDataArg('endAt', this._value, query._path, true);
13485 var newParams = queryParamsEndAt(query._queryParams, this._value, this._key);
13486 validateLimit(newParams);
13487 validateQueryEndpoints(newParams);
13488 if (query._queryParams.hasEnd()) {
13489 throw new Error('endAt: Starting point was already set (by another call to endAt, ' +
13490 'endBefore or equalTo).');
13491 }
13492 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13493 };
13494 return QueryEndAtConstraint;
13495}(QueryConstraint));
13496/**
13497 * Creates a `QueryConstraint` with the specified ending point.
13498 *
13499 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13500 * allows you to choose arbitrary starting and ending points for your queries.
13501 *
13502 * The ending point is inclusive, so children with exactly the specified value
13503 * will be included in the query. The optional key argument can be used to
13504 * further limit the range of the query. If it is specified, then children that
13505 * have exactly the specified value must also have a key name less than or equal
13506 * to the specified key.
13507 *
13508 * You can read more about `endAt()` in
13509 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13510 *
13511 * @param value - The value to end at. The argument type depends on which
13512 * `orderBy*()` function was used in this query. Specify a value that matches
13513 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13514 * value must be a string.
13515 * @param key - The child key to end at, among the children with the previously
13516 * specified priority. This argument is only allowed if ordering by child,
13517 * value, or priority.
13518 */
13519function endAt(value, key) {
13520 validateKey('endAt', 'key', key, true);
13521 return new QueryEndAtConstraint(value, key);
13522}
13523var QueryEndBeforeConstraint = /** @class */ (function (_super) {
13524 tslib.__extends(QueryEndBeforeConstraint, _super);
13525 function QueryEndBeforeConstraint(_value, _key) {
13526 var _this = _super.call(this) || this;
13527 _this._value = _value;
13528 _this._key = _key;
13529 return _this;
13530 }
13531 QueryEndBeforeConstraint.prototype._apply = function (query) {
13532 validateFirebaseDataArg('endBefore', this._value, query._path, false);
13533 var newParams = queryParamsEndBefore(query._queryParams, this._value, this._key);
13534 validateLimit(newParams);
13535 validateQueryEndpoints(newParams);
13536 if (query._queryParams.hasEnd()) {
13537 throw new Error('endBefore: Starting point was already set (by another call to endAt, ' +
13538 'endBefore or equalTo).');
13539 }
13540 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13541 };
13542 return QueryEndBeforeConstraint;
13543}(QueryConstraint));
13544/**
13545 * Creates a `QueryConstraint` with the specified ending point (exclusive).
13546 *
13547 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13548 * allows you to choose arbitrary starting and ending points for your queries.
13549 *
13550 * The ending point is exclusive. If only a value is provided, children
13551 * with a value less than the specified value will be included in the query.
13552 * If a key is specified, then children must have a value lesss than or equal
13553 * to the specified value and a a key name less than the specified key.
13554 *
13555 * @param value - The value to end before. The argument type depends on which
13556 * `orderBy*()` function was used in this query. Specify a value that matches
13557 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13558 * value must be a string.
13559 * @param key - The child key to end before, among the children with the
13560 * previously specified priority. This argument is only allowed if ordering by
13561 * child, value, or priority.
13562 */
13563function endBefore(value, key) {
13564 validateKey('endBefore', 'key', key, true);
13565 return new QueryEndBeforeConstraint(value, key);
13566}
13567var QueryStartAtConstraint = /** @class */ (function (_super) {
13568 tslib.__extends(QueryStartAtConstraint, _super);
13569 function QueryStartAtConstraint(_value, _key) {
13570 var _this = _super.call(this) || this;
13571 _this._value = _value;
13572 _this._key = _key;
13573 return _this;
13574 }
13575 QueryStartAtConstraint.prototype._apply = function (query) {
13576 validateFirebaseDataArg('startAt', this._value, query._path, true);
13577 var newParams = queryParamsStartAt(query._queryParams, this._value, this._key);
13578 validateLimit(newParams);
13579 validateQueryEndpoints(newParams);
13580 if (query._queryParams.hasStart()) {
13581 throw new Error('startAt: Starting point was already set (by another call to startAt, ' +
13582 'startBefore or equalTo).');
13583 }
13584 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13585 };
13586 return QueryStartAtConstraint;
13587}(QueryConstraint));
13588/**
13589 * Creates a `QueryConstraint` with the specified starting point.
13590 *
13591 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13592 * allows you to choose arbitrary starting and ending points for your queries.
13593 *
13594 * The starting point is inclusive, so children with exactly the specified value
13595 * will be included in the query. The optional key argument can be used to
13596 * further limit the range of the query. If it is specified, then children that
13597 * have exactly the specified value must also have a key name greater than or
13598 * equal to the specified key.
13599 *
13600 * You can read more about `startAt()` in
13601 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13602 *
13603 * @param value - The value to start at. The argument type depends on which
13604 * `orderBy*()` function was used in this query. Specify a value that matches
13605 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13606 * value must be a string.
13607 * @param key - The child key to start at. This argument is only allowed if
13608 * ordering by child, value, or priority.
13609 */
13610function startAt(value, key) {
13611 if (value === void 0) { value = null; }
13612 validateKey('startAt', 'key', key, true);
13613 return new QueryStartAtConstraint(value, key);
13614}
13615var QueryStartAfterConstraint = /** @class */ (function (_super) {
13616 tslib.__extends(QueryStartAfterConstraint, _super);
13617 function QueryStartAfterConstraint(_value, _key) {
13618 var _this = _super.call(this) || this;
13619 _this._value = _value;
13620 _this._key = _key;
13621 return _this;
13622 }
13623 QueryStartAfterConstraint.prototype._apply = function (query) {
13624 validateFirebaseDataArg('startAfter', this._value, query._path, false);
13625 var newParams = queryParamsStartAfter(query._queryParams, this._value, this._key);
13626 validateLimit(newParams);
13627 validateQueryEndpoints(newParams);
13628 if (query._queryParams.hasStart()) {
13629 throw new Error('startAfter: Starting point was already set (by another call to startAt, ' +
13630 'startAfter, or equalTo).');
13631 }
13632 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13633 };
13634 return QueryStartAfterConstraint;
13635}(QueryConstraint));
13636/**
13637 * Creates a `QueryConstraint` with the specified starting point (exclusive).
13638 *
13639 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13640 * allows you to choose arbitrary starting and ending points for your queries.
13641 *
13642 * The starting point is exclusive. If only a value is provided, children
13643 * with a value greater than the specified value will be included in the query.
13644 * If a key is specified, then children must have a value greater than or equal
13645 * to the specified value and a a key name greater than the specified key.
13646 *
13647 * @param value - The value to start after. The argument type depends on which
13648 * `orderBy*()` function was used in this query. Specify a value that matches
13649 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13650 * value must be a string.
13651 * @param key - The child key to start after. This argument is only allowed if
13652 * ordering by child, value, or priority.
13653 */
13654function startAfter(value, key) {
13655 validateKey('startAfter', 'key', key, true);
13656 return new QueryStartAfterConstraint(value, key);
13657}
13658var QueryLimitToFirstConstraint = /** @class */ (function (_super) {
13659 tslib.__extends(QueryLimitToFirstConstraint, _super);
13660 function QueryLimitToFirstConstraint(_limit) {
13661 var _this = _super.call(this) || this;
13662 _this._limit = _limit;
13663 return _this;
13664 }
13665 QueryLimitToFirstConstraint.prototype._apply = function (query) {
13666 if (query._queryParams.hasLimit()) {
13667 throw new Error('limitToFirst: Limit was already set (by another call to limitToFirst ' +
13668 'or limitToLast).');
13669 }
13670 return new QueryImpl(query._repo, query._path, queryParamsLimitToFirst(query._queryParams, this._limit), query._orderByCalled);
13671 };
13672 return QueryLimitToFirstConstraint;
13673}(QueryConstraint));
13674/**
13675 * Creates a new `QueryConstraint` that if limited to the first specific number
13676 * of children.
13677 *
13678 * The `limitToFirst()` method is used to set a maximum number of children to be
13679 * synced for a given callback. If we set a limit of 100, we will initially only
13680 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13681 * stored in our Database, a `child_added` event will fire for each message.
13682 * However, if we have over 100 messages, we will only receive a `child_added`
13683 * event for the first 100 ordered messages. As items change, we will receive
13684 * `child_removed` events for each item that drops out of the active list so
13685 * that the total number stays at 100.
13686 *
13687 * You can read more about `limitToFirst()` in
13688 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13689 *
13690 * @param limit - The maximum number of nodes to include in this query.
13691 */
13692function limitToFirst(limit) {
13693 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13694 throw new Error('limitToFirst: First argument must be a positive integer.');
13695 }
13696 return new QueryLimitToFirstConstraint(limit);
13697}
13698var QueryLimitToLastConstraint = /** @class */ (function (_super) {
13699 tslib.__extends(QueryLimitToLastConstraint, _super);
13700 function QueryLimitToLastConstraint(_limit) {
13701 var _this = _super.call(this) || this;
13702 _this._limit = _limit;
13703 return _this;
13704 }
13705 QueryLimitToLastConstraint.prototype._apply = function (query) {
13706 if (query._queryParams.hasLimit()) {
13707 throw new Error('limitToLast: Limit was already set (by another call to limitToFirst ' +
13708 'or limitToLast).');
13709 }
13710 return new QueryImpl(query._repo, query._path, queryParamsLimitToLast(query._queryParams, this._limit), query._orderByCalled);
13711 };
13712 return QueryLimitToLastConstraint;
13713}(QueryConstraint));
13714/**
13715 * Creates a new `QueryConstraint` that is limited to return only the last
13716 * specified number of children.
13717 *
13718 * The `limitToLast()` method is used to set a maximum number of children to be
13719 * synced for a given callback. If we set a limit of 100, we will initially only
13720 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13721 * stored in our Database, a `child_added` event will fire for each message.
13722 * However, if we have over 100 messages, we will only receive a `child_added`
13723 * event for the last 100 ordered messages. As items change, we will receive
13724 * `child_removed` events for each item that drops out of the active list so
13725 * that the total number stays at 100.
13726 *
13727 * You can read more about `limitToLast()` in
13728 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13729 *
13730 * @param limit - The maximum number of nodes to include in this query.
13731 */
13732function limitToLast(limit) {
13733 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13734 throw new Error('limitToLast: First argument must be a positive integer.');
13735 }
13736 return new QueryLimitToLastConstraint(limit);
13737}
13738var QueryOrderByChildConstraint = /** @class */ (function (_super) {
13739 tslib.__extends(QueryOrderByChildConstraint, _super);
13740 function QueryOrderByChildConstraint(_path) {
13741 var _this = _super.call(this) || this;
13742 _this._path = _path;
13743 return _this;
13744 }
13745 QueryOrderByChildConstraint.prototype._apply = function (query) {
13746 validateNoPreviousOrderByCall(query, 'orderByChild');
13747 var parsedPath = new Path(this._path);
13748 if (pathIsEmpty(parsedPath)) {
13749 throw new Error('orderByChild: cannot pass in empty path. Use orderByValue() instead.');
13750 }
13751 var index = new PathIndex(parsedPath);
13752 var newParams = queryParamsOrderBy(query._queryParams, index);
13753 validateQueryEndpoints(newParams);
13754 return new QueryImpl(query._repo, query._path, newParams,
13755 /*orderByCalled=*/ true);
13756 };
13757 return QueryOrderByChildConstraint;
13758}(QueryConstraint));
13759/**
13760 * Creates a new `QueryConstraint` that orders by the specified child key.
13761 *
13762 * Queries can only order by one key at a time. Calling `orderByChild()`
13763 * multiple times on the same query is an error.
13764 *
13765 * Firebase queries allow you to order your data by any child key on the fly.
13766 * However, if you know in advance what your indexes will be, you can define
13767 * them via the .indexOn rule in your Security Rules for better performance. See
13768 * the{@link https://firebase.google.com/docs/database/security/indexing-data}
13769 * rule for more information.
13770 *
13771 * You can read more about `orderByChild()` in
13772 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13773 *
13774 * @param path - The path to order by.
13775 */
13776function orderByChild(path) {
13777 if (path === '$key') {
13778 throw new Error('orderByChild: "$key" is invalid. Use orderByKey() instead.');
13779 }
13780 else if (path === '$priority') {
13781 throw new Error('orderByChild: "$priority" is invalid. Use orderByPriority() instead.');
13782 }
13783 else if (path === '$value') {
13784 throw new Error('orderByChild: "$value" is invalid. Use orderByValue() instead.');
13785 }
13786 validatePathString('orderByChild', 'path', path, false);
13787 return new QueryOrderByChildConstraint(path);
13788}
13789var QueryOrderByKeyConstraint = /** @class */ (function (_super) {
13790 tslib.__extends(QueryOrderByKeyConstraint, _super);
13791 function QueryOrderByKeyConstraint() {
13792 return _super !== null && _super.apply(this, arguments) || this;
13793 }
13794 QueryOrderByKeyConstraint.prototype._apply = function (query) {
13795 validateNoPreviousOrderByCall(query, 'orderByKey');
13796 var newParams = queryParamsOrderBy(query._queryParams, KEY_INDEX);
13797 validateQueryEndpoints(newParams);
13798 return new QueryImpl(query._repo, query._path, newParams,
13799 /*orderByCalled=*/ true);
13800 };
13801 return QueryOrderByKeyConstraint;
13802}(QueryConstraint));
13803/**
13804 * Creates a new `QueryConstraint` that orders by the key.
13805 *
13806 * Sorts the results of a query by their (ascending) key values.
13807 *
13808 * You can read more about `orderByKey()` in
13809 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13810 */
13811function orderByKey() {
13812 return new QueryOrderByKeyConstraint();
13813}
13814var QueryOrderByPriorityConstraint = /** @class */ (function (_super) {
13815 tslib.__extends(QueryOrderByPriorityConstraint, _super);
13816 function QueryOrderByPriorityConstraint() {
13817 return _super !== null && _super.apply(this, arguments) || this;
13818 }
13819 QueryOrderByPriorityConstraint.prototype._apply = function (query) {
13820 validateNoPreviousOrderByCall(query, 'orderByPriority');
13821 var newParams = queryParamsOrderBy(query._queryParams, PRIORITY_INDEX);
13822 validateQueryEndpoints(newParams);
13823 return new QueryImpl(query._repo, query._path, newParams,
13824 /*orderByCalled=*/ true);
13825 };
13826 return QueryOrderByPriorityConstraint;
13827}(QueryConstraint));
13828/**
13829 * Creates a new `QueryConstraint` that orders by priority.
13830 *
13831 * Applications need not use priority but can order collections by
13832 * ordinary properties (see
13833 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}
13834 * for alternatives to priority.
13835 */
13836function orderByPriority() {
13837 return new QueryOrderByPriorityConstraint();
13838}
13839var QueryOrderByValueConstraint = /** @class */ (function (_super) {
13840 tslib.__extends(QueryOrderByValueConstraint, _super);
13841 function QueryOrderByValueConstraint() {
13842 return _super !== null && _super.apply(this, arguments) || this;
13843 }
13844 QueryOrderByValueConstraint.prototype._apply = function (query) {
13845 validateNoPreviousOrderByCall(query, 'orderByValue');
13846 var newParams = queryParamsOrderBy(query._queryParams, VALUE_INDEX);
13847 validateQueryEndpoints(newParams);
13848 return new QueryImpl(query._repo, query._path, newParams,
13849 /*orderByCalled=*/ true);
13850 };
13851 return QueryOrderByValueConstraint;
13852}(QueryConstraint));
13853/**
13854 * Creates a new `QueryConstraint` that orders by value.
13855 *
13856 * If the children of a query are all scalar values (string, number, or
13857 * boolean), you can order the results by their (ascending) values.
13858 *
13859 * You can read more about `orderByValue()` in
13860 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13861 */
13862function orderByValue() {
13863 return new QueryOrderByValueConstraint();
13864}
13865var QueryEqualToValueConstraint = /** @class */ (function (_super) {
13866 tslib.__extends(QueryEqualToValueConstraint, _super);
13867 function QueryEqualToValueConstraint(_value, _key) {
13868 var _this = _super.call(this) || this;
13869 _this._value = _value;
13870 _this._key = _key;
13871 return _this;
13872 }
13873 QueryEqualToValueConstraint.prototype._apply = function (query) {
13874 validateFirebaseDataArg('equalTo', this._value, query._path, false);
13875 if (query._queryParams.hasStart()) {
13876 throw new Error('equalTo: Starting point was already set (by another call to startAt/startAfter or ' +
13877 'equalTo).');
13878 }
13879 if (query._queryParams.hasEnd()) {
13880 throw new Error('equalTo: Ending point was already set (by another call to endAt/endBefore or ' +
13881 'equalTo).');
13882 }
13883 return new QueryEndAtConstraint(this._value, this._key)._apply(new QueryStartAtConstraint(this._value, this._key)._apply(query));
13884 };
13885 return QueryEqualToValueConstraint;
13886}(QueryConstraint));
13887/**
13888 * Creates a `QueryConstraint` that includes children that match the specified
13889 * value.
13890 *
13891 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13892 * allows you to choose arbitrary starting and ending points for your queries.
13893 *
13894 * The optional key argument can be used to further limit the range of the
13895 * query. If it is specified, then children that have exactly the specified
13896 * value must also have exactly the specified key as their key name. This can be
13897 * used to filter result sets with many matches for the same value.
13898 *
13899 * You can read more about `equalTo()` in
13900 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13901 *
13902 * @param value - The value to match for. The argument type depends on which
13903 * `orderBy*()` function was used in this query. Specify a value that matches
13904 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13905 * value must be a string.
13906 * @param key - The child key to start at, among the children with the
13907 * previously specified priority. This argument is only allowed if ordering by
13908 * child, value, or priority.
13909 */
13910function equalTo(value, key) {
13911 validateKey('equalTo', 'key', key, true);
13912 return new QueryEqualToValueConstraint(value, key);
13913}
13914/**
13915 * Creates a new immutable instance of `Query` that is extended to also include
13916 * additional query constraints.
13917 *
13918 * @param query - The Query instance to use as a base for the new constraints.
13919 * @param queryConstraints - The list of `QueryConstraint`s to apply.
13920 * @throws if any of the provided query constraints cannot be combined with the
13921 * existing or new constraints.
13922 */
13923function query(query) {
13924 var e_1, _a;
13925 var queryConstraints = [];
13926 for (var _i = 1; _i < arguments.length; _i++) {
13927 queryConstraints[_i - 1] = arguments[_i];
13928 }
13929 var queryImpl = util.getModularInstance(query);
13930 try {
13931 for (var queryConstraints_1 = tslib.__values(queryConstraints), queryConstraints_1_1 = queryConstraints_1.next(); !queryConstraints_1_1.done; queryConstraints_1_1 = queryConstraints_1.next()) {
13932 var constraint = queryConstraints_1_1.value;
13933 queryImpl = constraint._apply(queryImpl);
13934 }
13935 }
13936 catch (e_1_1) { e_1 = { error: e_1_1 }; }
13937 finally {
13938 try {
13939 if (queryConstraints_1_1 && !queryConstraints_1_1.done && (_a = queryConstraints_1.return)) _a.call(queryConstraints_1);
13940 }
13941 finally { if (e_1) throw e_1.error; }
13942 }
13943 return queryImpl;
13944}
13945/**
13946 * Define reference constructor in various modules
13947 *
13948 * We are doing this here to avoid several circular
13949 * dependency issues
13950 */
13951syncPointSetReferenceConstructor(ReferenceImpl);
13952syncTreeSetReferenceConstructor(ReferenceImpl);
13953
13954/**
13955 * @license
13956 * Copyright 2020 Google LLC
13957 *
13958 * Licensed under the Apache License, Version 2.0 (the "License");
13959 * you may not use this file except in compliance with the License.
13960 * You may obtain a copy of the License at
13961 *
13962 * http://www.apache.org/licenses/LICENSE-2.0
13963 *
13964 * Unless required by applicable law or agreed to in writing, software
13965 * distributed under the License is distributed on an "AS IS" BASIS,
13966 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13967 * See the License for the specific language governing permissions and
13968 * limitations under the License.
13969 */
13970/**
13971 * This variable is also defined in the firebase Node.js Admin SDK. Before
13972 * modifying this definition, consult the definition in:
13973 *
13974 * https://github.com/firebase/firebase-admin-node
13975 *
13976 * and make sure the two are consistent.
13977 */
13978var FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST';
13979/**
13980 * Creates and caches `Repo` instances.
13981 */
13982var repos = {};
13983/**
13984 * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes).
13985 */
13986var useRestClient = false;
13987/**
13988 * Update an existing `Repo` in place to point to a new host/port.
13989 */
13990function repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider) {
13991 repo.repoInfo_ = new RepoInfo(host + ":" + port,
13992 /* secure= */ false, repo.repoInfo_.namespace, repo.repoInfo_.webSocketOnly, repo.repoInfo_.nodeAdmin, repo.repoInfo_.persistenceKey, repo.repoInfo_.includeNamespaceInQueryParams);
13993 if (tokenProvider) {
13994 repo.authTokenProvider_ = tokenProvider;
13995 }
13996}
13997/**
13998 * This function should only ever be called to CREATE a new database instance.
13999 * @internal
14000 */
14001function repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url, nodeAdmin) {
14002 var dbUrl = url || app.options.databaseURL;
14003 if (dbUrl === undefined) {
14004 if (!app.options.projectId) {
14005 fatal("Can't determine Firebase Database URL. Be sure to include " +
14006 ' a Project ID when calling firebase.initializeApp().');
14007 }
14008 log('Using default host for project ', app.options.projectId);
14009 dbUrl = app.options.projectId + "-default-rtdb.firebaseio.com";
14010 }
14011 var parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14012 var repoInfo = parsedUrl.repoInfo;
14013 var isEmulator;
14014 var dbEmulatorHost = undefined;
14015 if (typeof process !== 'undefined') {
14016 dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR];
14017 }
14018 if (dbEmulatorHost) {
14019 isEmulator = true;
14020 dbUrl = "http://" + dbEmulatorHost + "?ns=" + repoInfo.namespace;
14021 parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14022 repoInfo = parsedUrl.repoInfo;
14023 }
14024 else {
14025 isEmulator = !parsedUrl.repoInfo.secure;
14026 }
14027 var authTokenProvider = nodeAdmin && isEmulator
14028 ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER)
14029 : new FirebaseAuthTokenProvider(app.name, app.options, authProvider);
14030 validateUrl('Invalid Firebase Database URL', parsedUrl);
14031 if (!pathIsEmpty(parsedUrl.path)) {
14032 fatal('Database URL must point to the root of a Firebase Database ' +
14033 '(not including a child path).');
14034 }
14035 var repo = repoManagerCreateRepo(repoInfo, app, authTokenProvider, new AppCheckTokenProvider(app.name, appCheckProvider));
14036 return new Database(repo, app);
14037}
14038/**
14039 * Remove the repo and make sure it is disconnected.
14040 *
14041 */
14042function repoManagerDeleteRepo(repo, appName) {
14043 var appRepos = repos[appName];
14044 // This should never happen...
14045 if (!appRepos || appRepos[repo.key] !== repo) {
14046 fatal("Database " + appName + "(" + repo.repoInfo_ + ") has already been deleted.");
14047 }
14048 repoInterrupt(repo);
14049 delete appRepos[repo.key];
14050}
14051/**
14052 * Ensures a repo doesn't already exist and then creates one using the
14053 * provided app.
14054 *
14055 * @param repoInfo - The metadata about the Repo
14056 * @returns The Repo object for the specified server / repoName.
14057 */
14058function repoManagerCreateRepo(repoInfo, app, authTokenProvider, appCheckProvider) {
14059 var appRepos = repos[app.name];
14060 if (!appRepos) {
14061 appRepos = {};
14062 repos[app.name] = appRepos;
14063 }
14064 var repo = appRepos[repoInfo.toURLString()];
14065 if (repo) {
14066 fatal('Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.');
14067 }
14068 repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider);
14069 appRepos[repoInfo.toURLString()] = repo;
14070 return repo;
14071}
14072/**
14073 * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos.
14074 */
14075function repoManagerForceRestClient(forceRestClient) {
14076 useRestClient = forceRestClient;
14077}
14078/**
14079 * Class representing a Firebase Realtime Database.
14080 */
14081var Database = /** @class */ (function () {
14082 /** @hideconstructor */
14083 function Database(_repoInternal,
14084 /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */
14085 app) {
14086 this._repoInternal = _repoInternal;
14087 this.app = app;
14088 /** Represents a `Database` instance. */
14089 this['type'] = 'database';
14090 /** Track if the instance has been used (root or repo accessed) */
14091 this._instanceStarted = false;
14092 }
14093 Object.defineProperty(Database.prototype, "_repo", {
14094 get: function () {
14095 if (!this._instanceStarted) {
14096 repoStart(this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride']);
14097 this._instanceStarted = true;
14098 }
14099 return this._repoInternal;
14100 },
14101 enumerable: false,
14102 configurable: true
14103 });
14104 Object.defineProperty(Database.prototype, "_root", {
14105 get: function () {
14106 if (!this._rootInternal) {
14107 this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath());
14108 }
14109 return this._rootInternal;
14110 },
14111 enumerable: false,
14112 configurable: true
14113 });
14114 Database.prototype._delete = function () {
14115 if (this._rootInternal !== null) {
14116 repoManagerDeleteRepo(this._repo, this.app.name);
14117 this._repoInternal = null;
14118 this._rootInternal = null;
14119 }
14120 return Promise.resolve();
14121 };
14122 Database.prototype._checkNotDeleted = function (apiName) {
14123 if (this._rootInternal === null) {
14124 fatal('Cannot call ' + apiName + ' on a deleted database.');
14125 }
14126 };
14127 return Database;
14128}());
14129/**
14130 * Returns the instance of the Realtime Database SDK that is associated
14131 * with the provided {@link @firebase/app#FirebaseApp}. Initializes a new instance with
14132 * with default settings if no instance exists or if the existing instance uses
14133 * a custom database URL.
14134 *
14135 * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned Realtime
14136 * Database instance is associated with.
14137 * @param url - The URL of the Realtime Database instance to connect to. If not
14138 * provided, the SDK connects to the default instance of the Firebase App.
14139 * @returns The `Database` instance of the provided app.
14140 */
14141function getDatabase(app$1, url) {
14142 if (app$1 === void 0) { app$1 = app.getApp(); }
14143 return app._getProvider(app$1, 'database').getImmediate({
14144 identifier: url
14145 });
14146}
14147/**
14148 * Modify the provided instance to communicate with the Realtime Database
14149 * emulator.
14150 *
14151 * <p>Note: This method must be called before performing any other operation.
14152 *
14153 * @param db - The instance to modify.
14154 * @param host - The emulator host (ex: localhost)
14155 * @param port - The emulator port (ex: 8080)
14156 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
14157 */
14158function connectDatabaseEmulator(db, host, port, options) {
14159 if (options === void 0) { options = {}; }
14160 db = util.getModularInstance(db);
14161 db._checkNotDeleted('useEmulator');
14162 if (db._instanceStarted) {
14163 fatal('Cannot call useEmulator() after instance has already been initialized.');
14164 }
14165 var repo = db._repoInternal;
14166 var tokenProvider = undefined;
14167 if (repo.repoInfo_.nodeAdmin) {
14168 if (options.mockUserToken) {
14169 fatal('mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".');
14170 }
14171 tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
14172 }
14173 else if (options.mockUserToken) {
14174 var token = typeof options.mockUserToken === 'string'
14175 ? options.mockUserToken
14176 : util.createMockUserToken(options.mockUserToken, db.app.options.projectId);
14177 tokenProvider = new EmulatorTokenProvider(token);
14178 }
14179 // Modify the repo to apply emulator settings
14180 repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider);
14181}
14182/**
14183 * Disconnects from the server (all Database operations will be completed
14184 * offline).
14185 *
14186 * The client automatically maintains a persistent connection to the Database
14187 * server, which will remain active indefinitely and reconnect when
14188 * disconnected. However, the `goOffline()` and `goOnline()` methods may be used
14189 * to control the client connection in cases where a persistent connection is
14190 * undesirable.
14191 *
14192 * While offline, the client will no longer receive data updates from the
14193 * Database. However, all Database operations performed locally will continue to
14194 * immediately fire events, allowing your application to continue behaving
14195 * normally. Additionally, each operation performed locally will automatically
14196 * be queued and retried upon reconnection to the Database server.
14197 *
14198 * To reconnect to the Database and begin receiving remote events, see
14199 * `goOnline()`.
14200 *
14201 * @param db - The instance to disconnect.
14202 */
14203function goOffline(db) {
14204 db = util.getModularInstance(db);
14205 db._checkNotDeleted('goOffline');
14206 repoInterrupt(db._repo);
14207}
14208/**
14209 * Reconnects to the server and synchronizes the offline Database state
14210 * with the server state.
14211 *
14212 * This method should be used after disabling the active connection with
14213 * `goOffline()`. Once reconnected, the client will transmit the proper data
14214 * and fire the appropriate events so that your client "catches up"
14215 * automatically.
14216 *
14217 * @param db - The instance to reconnect.
14218 */
14219function goOnline(db) {
14220 db = util.getModularInstance(db);
14221 db._checkNotDeleted('goOnline');
14222 repoResume(db._repo);
14223}
14224function enableLogging(logger, persistent) {
14225 enableLogging$1(logger, persistent);
14226}
14227
14228/**
14229 * @license
14230 * Copyright 2021 Google LLC
14231 *
14232 * Licensed under the Apache License, Version 2.0 (the "License");
14233 * you may not use this file except in compliance with the License.
14234 * You may obtain a copy of the License at
14235 *
14236 * http://www.apache.org/licenses/LICENSE-2.0
14237 *
14238 * Unless required by applicable law or agreed to in writing, software
14239 * distributed under the License is distributed on an "AS IS" BASIS,
14240 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14241 * See the License for the specific language governing permissions and
14242 * limitations under the License.
14243 */
14244function registerDatabase(variant) {
14245 setSDKVersion(app.SDK_VERSION);
14246 app._registerComponent(new component.Component('database', function (container, _a) {
14247 var url = _a.instanceIdentifier;
14248 var app = container.getProvider('app').getImmediate();
14249 var authProvider = container.getProvider('auth-internal');
14250 var appCheckProvider = container.getProvider('app-check-internal');
14251 return repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url);
14252 }, "PUBLIC" /* PUBLIC */).setMultipleInstances(true));
14253 app.registerVersion(name, version, variant);
14254}
14255
14256/**
14257 * @license
14258 * Copyright 2020 Google LLC
14259 *
14260 * Licensed under the Apache License, Version 2.0 (the "License");
14261 * you may not use this file except in compliance with the License.
14262 * You may obtain a copy of the License at
14263 *
14264 * http://www.apache.org/licenses/LICENSE-2.0
14265 *
14266 * Unless required by applicable law or agreed to in writing, software
14267 * distributed under the License is distributed on an "AS IS" BASIS,
14268 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14269 * See the License for the specific language governing permissions and
14270 * limitations under the License.
14271 */
14272var SERVER_TIMESTAMP = {
14273 '.sv': 'timestamp'
14274};
14275/**
14276 * Returns a placeholder value for auto-populating the current timestamp (time
14277 * since the Unix epoch, in milliseconds) as determined by the Firebase
14278 * servers.
14279 */
14280function serverTimestamp() {
14281 return SERVER_TIMESTAMP;
14282}
14283/**
14284 * Returns a placeholder value that can be used to atomically increment the
14285 * current database value by the provided delta.
14286 *
14287 * @param delta - the amount to modify the current value atomically.
14288 * @returns A placeholder value for modifying data atomically server-side.
14289 */
14290function increment(delta) {
14291 return {
14292 '.sv': {
14293 'increment': delta
14294 }
14295 };
14296}
14297
14298/**
14299 * @license
14300 * Copyright 2020 Google LLC
14301 *
14302 * Licensed under the Apache License, Version 2.0 (the "License");
14303 * you may not use this file except in compliance with the License.
14304 * You may obtain a copy of the License at
14305 *
14306 * http://www.apache.org/licenses/LICENSE-2.0
14307 *
14308 * Unless required by applicable law or agreed to in writing, software
14309 * distributed under the License is distributed on an "AS IS" BASIS,
14310 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14311 * See the License for the specific language governing permissions and
14312 * limitations under the License.
14313 */
14314/**
14315 * A type for the resolve value of {@link runTransaction}.
14316 */
14317var TransactionResult = /** @class */ (function () {
14318 /** @hideconstructor */
14319 function TransactionResult(
14320 /** Whether the transaction was successfully committed. */
14321 committed,
14322 /** The resulting data snapshot. */
14323 snapshot) {
14324 this.committed = committed;
14325 this.snapshot = snapshot;
14326 }
14327 /** Returns a JSON-serializable representation of this object. */
14328 TransactionResult.prototype.toJSON = function () {
14329 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14330 };
14331 return TransactionResult;
14332}());
14333/**
14334 * Atomically modifies the data at this location.
14335 *
14336 * Atomically modify the data at this location. Unlike a normal `set()`, which
14337 * just overwrites the data regardless of its previous value, `runTransaction()` is
14338 * used to modify the existing value to a new value, ensuring there are no
14339 * conflicts with other clients writing to the same location at the same time.
14340 *
14341 * To accomplish this, you pass `runTransaction()` an update function which is
14342 * used to transform the current value into a new value. If another client
14343 * writes to the location before your new value is successfully written, your
14344 * update function will be called again with the new current value, and the
14345 * write will be retried. This will happen repeatedly until your write succeeds
14346 * without conflict or you abort the transaction by not returning a value from
14347 * your update function.
14348 *
14349 * Note: Modifying data with `set()` will cancel any pending transactions at
14350 * that location, so extreme care should be taken if mixing `set()` and
14351 * `runTransaction()` to update the same data.
14352 *
14353 * Note: When using transactions with Security and Firebase Rules in place, be
14354 * aware that a client needs `.read` access in addition to `.write` access in
14355 * order to perform a transaction. This is because the client-side nature of
14356 * transactions requires the client to read the data in order to transactionally
14357 * update it.
14358 *
14359 * @param ref - The location to atomically modify.
14360 * @param transactionUpdate - A developer-supplied function which will be passed
14361 * the current data stored at this location (as a JavaScript object). The
14362 * function should return the new value it would like written (as a JavaScript
14363 * object). If `undefined` is returned (i.e. you return with no arguments) the
14364 * transaction will be aborted and the data at this location will not be
14365 * modified.
14366 * @param options - An options object to configure transactions.
14367 * @returns A `Promise` that can optionally be used instead of the `onComplete`
14368 * callback to handle success and failure.
14369 */
14370function runTransaction(ref,
14371// eslint-disable-next-line @typescript-eslint/no-explicit-any
14372transactionUpdate, options) {
14373 var _a;
14374 ref = util.getModularInstance(ref);
14375 validateWritablePath('Reference.transaction', ref._path);
14376 if (ref.key === '.length' || ref.key === '.keys') {
14377 throw ('Reference.transaction failed: ' + ref.key + ' is a read-only object.');
14378 }
14379 var applyLocally = (_a = options === null || options === void 0 ? void 0 : options.applyLocally) !== null && _a !== void 0 ? _a : true;
14380 var deferred = new util.Deferred();
14381 var promiseComplete = function (error, committed, node) {
14382 var dataSnapshot = null;
14383 if (error) {
14384 deferred.reject(error);
14385 }
14386 else {
14387 dataSnapshot = new DataSnapshot(node, new ReferenceImpl(ref._repo, ref._path), PRIORITY_INDEX);
14388 deferred.resolve(new TransactionResult(committed, dataSnapshot));
14389 }
14390 };
14391 // Add a watch to make sure we get server updates.
14392 var unwatcher = onValue(ref, function () { });
14393 repoStartTransaction(ref._repo, ref._path, transactionUpdate, promiseComplete, unwatcher, applyLocally);
14394 return deferred.promise;
14395}
14396
14397/**
14398 * @license
14399 * Copyright 2017 Google LLC
14400 *
14401 * Licensed under the Apache License, Version 2.0 (the "License");
14402 * you may not use this file except in compliance with the License.
14403 * You may obtain a copy of the License at
14404 *
14405 * http://www.apache.org/licenses/LICENSE-2.0
14406 *
14407 * Unless required by applicable law or agreed to in writing, software
14408 * distributed under the License is distributed on an "AS IS" BASIS,
14409 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14410 * See the License for the specific language governing permissions and
14411 * limitations under the License.
14412 */
14413// eslint-disable-next-line @typescript-eslint/no-explicit-any
14414PersistentConnection.prototype.simpleListen = function (pathString, onComplete) {
14415 this.sendRequest('q', { p: pathString }, onComplete);
14416};
14417// eslint-disable-next-line @typescript-eslint/no-explicit-any
14418PersistentConnection.prototype.echo = function (data, onEcho) {
14419 this.sendRequest('echo', { d: data }, onEcho);
14420};
14421/**
14422 * @internal
14423 */
14424var hijackHash = function (newHash) {
14425 var oldPut = PersistentConnection.prototype.put;
14426 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
14427 if (hash !== undefined) {
14428 hash = newHash();
14429 }
14430 oldPut.call(this, pathString, data, onComplete, hash);
14431 };
14432 return function () {
14433 PersistentConnection.prototype.put = oldPut;
14434 };
14435};
14436/**
14437 * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection.
14438 * @internal
14439 */
14440var forceRestClient = function (forceRestClient) {
14441 repoManagerForceRestClient(forceRestClient);
14442};
14443
14444/**
14445 * @license
14446 * Copyright 2021 Google LLC
14447 *
14448 * Licensed under the Apache License, Version 2.0 (the "License");
14449 * you may not use this file except in compliance with the License.
14450 * You may obtain a copy of the License at
14451 *
14452 * http://www.apache.org/licenses/LICENSE-2.0
14453 *
14454 * Unless required by applicable law or agreed to in writing, software
14455 * distributed under the License is distributed on an "AS IS" BASIS,
14456 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14457 * See the License for the specific language governing permissions and
14458 * limitations under the License.
14459 */
14460setWebSocketImpl(fayeWebsocket.Client);
14461registerDatabase('node');
14462
14463exports.DataSnapshot = DataSnapshot;
14464exports.Database = Database;
14465exports.OnDisconnect = OnDisconnect;
14466exports.QueryConstraint = QueryConstraint;
14467exports.TransactionResult = TransactionResult;
14468exports._QueryImpl = QueryImpl;
14469exports._QueryParams = QueryParams;
14470exports._ReferenceImpl = ReferenceImpl;
14471exports._TEST_ACCESS_forceRestClient = forceRestClient;
14472exports._TEST_ACCESS_hijackHash = hijackHash;
14473exports._repoManagerDatabaseFromApp = repoManagerDatabaseFromApp;
14474exports._setSDKVersion = setSDKVersion;
14475exports._validatePathString = validatePathString;
14476exports._validateWritablePath = validateWritablePath;
14477exports.child = child;
14478exports.connectDatabaseEmulator = connectDatabaseEmulator;
14479exports.enableLogging = enableLogging;
14480exports.endAt = endAt;
14481exports.endBefore = endBefore;
14482exports.equalTo = equalTo;
14483exports.get = get;
14484exports.getDatabase = getDatabase;
14485exports.goOffline = goOffline;
14486exports.goOnline = goOnline;
14487exports.increment = increment;
14488exports.limitToFirst = limitToFirst;
14489exports.limitToLast = limitToLast;
14490exports.off = off;
14491exports.onChildAdded = onChildAdded;
14492exports.onChildChanged = onChildChanged;
14493exports.onChildMoved = onChildMoved;
14494exports.onChildRemoved = onChildRemoved;
14495exports.onDisconnect = onDisconnect;
14496exports.onValue = onValue;
14497exports.orderByChild = orderByChild;
14498exports.orderByKey = orderByKey;
14499exports.orderByPriority = orderByPriority;
14500exports.orderByValue = orderByValue;
14501exports.push = push;
14502exports.query = query;
14503exports.ref = ref;
14504exports.refFromURL = refFromURL;
14505exports.remove = remove;
14506exports.runTransaction = runTransaction;
14507exports.serverTimestamp = serverTimestamp;
14508exports.set = set;
14509exports.setPriority = setPriority;
14510exports.setWithPriority = setWithPriority;
14511exports.startAfter = startAfter;
14512exports.startAt = startAt;
14513exports.update = update;
14514//# sourceMappingURL=index.node.cjs.js.map