UNPKG

602 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var Websocket = require('faye-websocket');
6var util = require('@firebase/util');
7var tslib = require('tslib');
8var logger$1 = require('@firebase/logger');
9
10function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
12var Websocket__default = /*#__PURE__*/_interopDefaultLegacy(Websocket);
13
14/**
15 * @license
16 * Copyright 2017 Google LLC
17 *
18 * Licensed under the Apache License, Version 2.0 (the "License");
19 * you may not use this file except in compliance with the License.
20 * You may obtain a copy of the License at
21 *
22 * http://www.apache.org/licenses/LICENSE-2.0
23 *
24 * Unless required by applicable law or agreed to in writing, software
25 * distributed under the License is distributed on an "AS IS" BASIS,
26 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27 * See the License for the specific language governing permissions and
28 * limitations under the License.
29 */
30var PROTOCOL_VERSION = '5';
31var VERSION_PARAM = 'v';
32var TRANSPORT_SESSION_PARAM = 's';
33var REFERER_PARAM = 'r';
34var FORGE_REF = 'f';
35// Matches console.firebase.google.com, firebase-console-*.corp.google.com and
36// firebase.corp.google.com
37var FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/;
38var LAST_SESSION_PARAM = 'ls';
39var APPLICATION_ID_PARAM = 'p';
40var APP_CHECK_TOKEN_PARAM = 'ac';
41var WEBSOCKET = 'websocket';
42var LONG_POLLING = 'long_polling';
43
44/**
45 * @license
46 * Copyright 2017 Google LLC
47 *
48 * Licensed under the Apache License, Version 2.0 (the "License");
49 * you may not use this file except in compliance with the License.
50 * You may obtain a copy of the License at
51 *
52 * http://www.apache.org/licenses/LICENSE-2.0
53 *
54 * Unless required by applicable law or agreed to in writing, software
55 * distributed under the License is distributed on an "AS IS" BASIS,
56 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
57 * See the License for the specific language governing permissions and
58 * limitations under the License.
59 */
60/**
61 * Wraps a DOM Storage object and:
62 * - automatically encode objects as JSON strings before storing them to allow us to store arbitrary types.
63 * - prefixes names with "firebase:" to avoid collisions with app data.
64 *
65 * We automatically (see storage.js) create two such wrappers, one for sessionStorage,
66 * and one for localStorage.
67 *
68 */
69var DOMStorageWrapper = /** @class */ (function () {
70 /**
71 * @param domStorage_ - The underlying storage object (e.g. localStorage or sessionStorage)
72 */
73 function DOMStorageWrapper(domStorage_) {
74 this.domStorage_ = domStorage_;
75 // Use a prefix to avoid collisions with other stuff saved by the app.
76 this.prefix_ = 'firebase:';
77 }
78 /**
79 * @param key - The key to save the value under
80 * @param value - The value being stored, or null to remove the key.
81 */
82 DOMStorageWrapper.prototype.set = function (key, value) {
83 if (value == null) {
84 this.domStorage_.removeItem(this.prefixedName_(key));
85 }
86 else {
87 this.domStorage_.setItem(this.prefixedName_(key), util.stringify(value));
88 }
89 };
90 /**
91 * @returns The value that was stored under this key, or null
92 */
93 DOMStorageWrapper.prototype.get = function (key) {
94 var storedVal = this.domStorage_.getItem(this.prefixedName_(key));
95 if (storedVal == null) {
96 return null;
97 }
98 else {
99 return util.jsonEval(storedVal);
100 }
101 };
102 DOMStorageWrapper.prototype.remove = function (key) {
103 this.domStorage_.removeItem(this.prefixedName_(key));
104 };
105 DOMStorageWrapper.prototype.prefixedName_ = function (name) {
106 return this.prefix_ + name;
107 };
108 DOMStorageWrapper.prototype.toString = function () {
109 return this.domStorage_.toString();
110 };
111 return DOMStorageWrapper;
112}());
113
114/**
115 * @license
116 * Copyright 2017 Google LLC
117 *
118 * Licensed under the Apache License, Version 2.0 (the "License");
119 * you may not use this file except in compliance with the License.
120 * You may obtain a copy of the License at
121 *
122 * http://www.apache.org/licenses/LICENSE-2.0
123 *
124 * Unless required by applicable law or agreed to in writing, software
125 * distributed under the License is distributed on an "AS IS" BASIS,
126 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
127 * See the License for the specific language governing permissions and
128 * limitations under the License.
129 */
130/**
131 * An in-memory storage implementation that matches the API of DOMStorageWrapper
132 * (TODO: create interface for both to implement).
133 */
134var MemoryStorage = /** @class */ (function () {
135 function MemoryStorage() {
136 this.cache_ = {};
137 this.isInMemoryStorage = true;
138 }
139 MemoryStorage.prototype.set = function (key, value) {
140 if (value == null) {
141 delete this.cache_[key];
142 }
143 else {
144 this.cache_[key] = value;
145 }
146 };
147 MemoryStorage.prototype.get = function (key) {
148 if (util.contains(this.cache_, key)) {
149 return this.cache_[key];
150 }
151 return null;
152 };
153 MemoryStorage.prototype.remove = function (key) {
154 delete this.cache_[key];
155 };
156 return MemoryStorage;
157}());
158
159/**
160 * @license
161 * Copyright 2017 Google LLC
162 *
163 * Licensed under the Apache License, Version 2.0 (the "License");
164 * you may not use this file except in compliance with the License.
165 * You may obtain a copy of the License at
166 *
167 * http://www.apache.org/licenses/LICENSE-2.0
168 *
169 * Unless required by applicable law or agreed to in writing, software
170 * distributed under the License is distributed on an "AS IS" BASIS,
171 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
172 * See the License for the specific language governing permissions and
173 * limitations under the License.
174 */
175/**
176 * Helper to create a DOMStorageWrapper or else fall back to MemoryStorage.
177 * TODO: Once MemoryStorage and DOMStorageWrapper have a shared interface this method annotation should change
178 * to reflect this type
179 *
180 * @param domStorageName - Name of the underlying storage object
181 * (e.g. 'localStorage' or 'sessionStorage').
182 * @returns Turning off type information until a common interface is defined.
183 */
184var createStoragefor = function (domStorageName) {
185 try {
186 // NOTE: just accessing "localStorage" or "window['localStorage']" may throw a security exception,
187 // so it must be inside the try/catch.
188 if (typeof window !== 'undefined' &&
189 typeof window[domStorageName] !== 'undefined') {
190 // Need to test cache. Just because it's here doesn't mean it works
191 var domStorage = window[domStorageName];
192 domStorage.setItem('firebase:sentinel', 'cache');
193 domStorage.removeItem('firebase:sentinel');
194 return new DOMStorageWrapper(domStorage);
195 }
196 }
197 catch (e) { }
198 // Failed to create wrapper. Just return in-memory storage.
199 // TODO: log?
200 return new MemoryStorage();
201};
202/** A storage object that lasts across sessions */
203var PersistentStorage = createStoragefor('localStorage');
204/** A storage object that only lasts one session */
205var SessionStorage = createStoragefor('sessionStorage');
206
207/**
208 * @license
209 * Copyright 2017 Google LLC
210 *
211 * Licensed under the Apache License, Version 2.0 (the "License");
212 * you may not use this file except in compliance with the License.
213 * You may obtain a copy of the License at
214 *
215 * http://www.apache.org/licenses/LICENSE-2.0
216 *
217 * Unless required by applicable law or agreed to in writing, software
218 * distributed under the License is distributed on an "AS IS" BASIS,
219 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
220 * See the License for the specific language governing permissions and
221 * limitations under the License.
222 */
223var logClient = new logger$1.Logger('@firebase/database');
224/**
225 * Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
226 */
227var LUIDGenerator = (function () {
228 var id = 1;
229 return function () {
230 return id++;
231 };
232})();
233/**
234 * Sha1 hash of the input string
235 * @param str - The string to hash
236 * @returns {!string} The resulting hash
237 */
238var sha1 = function (str) {
239 var utf8Bytes = util.stringToByteArray(str);
240 var sha1 = new util.Sha1();
241 sha1.update(utf8Bytes);
242 var sha1Bytes = sha1.digest();
243 return util.base64.encodeByteArray(sha1Bytes);
244};
245var buildLogMessage_ = function () {
246 var varArgs = [];
247 for (var _i = 0; _i < arguments.length; _i++) {
248 varArgs[_i] = arguments[_i];
249 }
250 var message = '';
251 for (var i = 0; i < varArgs.length; i++) {
252 var arg = varArgs[i];
253 if (Array.isArray(arg) ||
254 (arg &&
255 typeof arg === 'object' &&
256 // eslint-disable-next-line @typescript-eslint/no-explicit-any
257 typeof arg.length === 'number')) {
258 message += buildLogMessage_.apply(null, arg);
259 }
260 else if (typeof arg === 'object') {
261 message += util.stringify(arg);
262 }
263 else {
264 message += arg;
265 }
266 message += ' ';
267 }
268 return message;
269};
270/**
271 * Use this for all debug messages in Firebase.
272 */
273var logger = null;
274/**
275 * Flag to check for log availability on first log message
276 */
277var firstLog_ = true;
278/**
279 * The implementation of Firebase.enableLogging (defined here to break dependencies)
280 * @param logger_ - A flag to turn on logging, or a custom logger
281 * @param persistent - Whether or not to persist logging settings across refreshes
282 */
283var enableLogging$1 = function (logger_, persistent) {
284 util.assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
285 if (logger_ === true) {
286 logClient.logLevel = logger$1.LogLevel.VERBOSE;
287 logger = logClient.log.bind(logClient);
288 if (persistent) {
289 SessionStorage.set('logging_enabled', true);
290 }
291 }
292 else if (typeof logger_ === 'function') {
293 logger = logger_;
294 }
295 else {
296 logger = null;
297 SessionStorage.remove('logging_enabled');
298 }
299};
300var log = function () {
301 var varArgs = [];
302 for (var _i = 0; _i < arguments.length; _i++) {
303 varArgs[_i] = arguments[_i];
304 }
305 if (firstLog_ === true) {
306 firstLog_ = false;
307 if (logger === null && SessionStorage.get('logging_enabled') === true) {
308 enableLogging$1(true);
309 }
310 }
311 if (logger) {
312 var message = buildLogMessage_.apply(null, varArgs);
313 logger(message);
314 }
315};
316var logWrapper = function (prefix) {
317 return function () {
318 var varArgs = [];
319 for (var _i = 0; _i < arguments.length; _i++) {
320 varArgs[_i] = arguments[_i];
321 }
322 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
323 };
324};
325var error = function () {
326 var varArgs = [];
327 for (var _i = 0; _i < arguments.length; _i++) {
328 varArgs[_i] = arguments[_i];
329 }
330 var message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
331 logClient.error(message);
332};
333var fatal = function () {
334 var varArgs = [];
335 for (var _i = 0; _i < arguments.length; _i++) {
336 varArgs[_i] = arguments[_i];
337 }
338 var message = "FIREBASE FATAL ERROR: " + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
339 logClient.error(message);
340 throw new Error(message);
341};
342var warn = function () {
343 var varArgs = [];
344 for (var _i = 0; _i < arguments.length; _i++) {
345 varArgs[_i] = arguments[_i];
346 }
347 var message = 'FIREBASE WARNING: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
348 logClient.warn(message);
349};
350/**
351 * Logs a warning if the containing page uses https. Called when a call to new Firebase
352 * does not use https.
353 */
354var warnIfPageIsSecure = function () {
355 // Be very careful accessing browser globals. Who knows what may or may not exist.
356 if (typeof window !== 'undefined' &&
357 window.location &&
358 window.location.protocol &&
359 window.location.protocol.indexOf('https:') !== -1) {
360 warn('Insecure Firebase access from a secure page. ' +
361 'Please use https in calls to new Firebase().');
362 }
363};
364/**
365 * Returns true if data is NaN, or +/- Infinity.
366 */
367var isInvalidJSONNumber = function (data) {
368 return (typeof data === 'number' &&
369 (data !== data || // NaN
370 data === Number.POSITIVE_INFINITY ||
371 data === Number.NEGATIVE_INFINITY));
372};
373var executeWhenDOMReady = function (fn) {
374 if (util.isNodeSdk() || document.readyState === 'complete') {
375 fn();
376 }
377 else {
378 // Modeled after jQuery. Try DOMContentLoaded and onreadystatechange (which
379 // fire before onload), but fall back to onload.
380 var called_1 = false;
381 var wrappedFn_1 = function () {
382 if (!document.body) {
383 setTimeout(wrappedFn_1, Math.floor(10));
384 return;
385 }
386 if (!called_1) {
387 called_1 = true;
388 fn();
389 }
390 };
391 if (document.addEventListener) {
392 document.addEventListener('DOMContentLoaded', wrappedFn_1, false);
393 // fallback to onload.
394 window.addEventListener('load', wrappedFn_1, false);
395 // eslint-disable-next-line @typescript-eslint/no-explicit-any
396 }
397 else if (document.attachEvent) {
398 // IE.
399 // eslint-disable-next-line @typescript-eslint/no-explicit-any
400 document.attachEvent('onreadystatechange', function () {
401 if (document.readyState === 'complete') {
402 wrappedFn_1();
403 }
404 });
405 // fallback to onload.
406 // eslint-disable-next-line @typescript-eslint/no-explicit-any
407 window.attachEvent('onload', wrappedFn_1);
408 // jQuery has an extra hack for IE that we could employ (based on
409 // http://javascript.nwbox.com/IEContentLoaded/) But it looks really old.
410 // I'm hoping we don't need it.
411 }
412 }
413};
414/**
415 * Minimum key name. Invalid for actual data, used as a marker to sort before any valid names
416 */
417var MIN_NAME = '[MIN_NAME]';
418/**
419 * Maximum key name. Invalid for actual data, used as a marker to sort above any valid names
420 */
421var MAX_NAME = '[MAX_NAME]';
422/**
423 * Compares valid Firebase key names, plus min and max name
424 */
425var nameCompare = function (a, b) {
426 if (a === b) {
427 return 0;
428 }
429 else if (a === MIN_NAME || b === MAX_NAME) {
430 return -1;
431 }
432 else if (b === MIN_NAME || a === MAX_NAME) {
433 return 1;
434 }
435 else {
436 var aAsInt = tryParseInt(a), bAsInt = tryParseInt(b);
437 if (aAsInt !== null) {
438 if (bAsInt !== null) {
439 return aAsInt - bAsInt === 0 ? a.length - b.length : aAsInt - bAsInt;
440 }
441 else {
442 return -1;
443 }
444 }
445 else if (bAsInt !== null) {
446 return 1;
447 }
448 else {
449 return a < b ? -1 : 1;
450 }
451 }
452};
453/**
454 * @returns {!number} comparison result.
455 */
456var stringCompare = function (a, b) {
457 if (a === b) {
458 return 0;
459 }
460 else if (a < b) {
461 return -1;
462 }
463 else {
464 return 1;
465 }
466};
467var requireKey = function (key, obj) {
468 if (obj && key in obj) {
469 return obj[key];
470 }
471 else {
472 throw new Error('Missing required key (' + key + ') in object: ' + util.stringify(obj));
473 }
474};
475var ObjectToUniqueKey = function (obj) {
476 if (typeof obj !== 'object' || obj === null) {
477 return util.stringify(obj);
478 }
479 var keys = [];
480 // eslint-disable-next-line guard-for-in
481 for (var k in obj) {
482 keys.push(k);
483 }
484 // Export as json, but with the keys sorted.
485 keys.sort();
486 var key = '{';
487 for (var i = 0; i < keys.length; i++) {
488 if (i !== 0) {
489 key += ',';
490 }
491 key += util.stringify(keys[i]);
492 key += ':';
493 key += ObjectToUniqueKey(obj[keys[i]]);
494 }
495 key += '}';
496 return key;
497};
498/**
499 * Splits a string into a number of smaller segments of maximum size
500 * @param str - The string
501 * @param segsize - The maximum number of chars in the string.
502 * @returns The string, split into appropriately-sized chunks
503 */
504var splitStringBySize = function (str, segsize) {
505 var len = str.length;
506 if (len <= segsize) {
507 return [str];
508 }
509 var dataSegs = [];
510 for (var c = 0; c < len; c += segsize) {
511 if (c + segsize > len) {
512 dataSegs.push(str.substring(c, len));
513 }
514 else {
515 dataSegs.push(str.substring(c, c + segsize));
516 }
517 }
518 return dataSegs;
519};
520/**
521 * Apply a function to each (key, value) pair in an object or
522 * apply a function to each (index, value) pair in an array
523 * @param obj - The object or array to iterate over
524 * @param fn - The function to apply
525 */
526function each(obj, fn) {
527 for (var key in obj) {
528 if (obj.hasOwnProperty(key)) {
529 fn(key, obj[key]);
530 }
531 }
532}
533/**
534 * Borrowed from http://hg.secondlife.com/llsd/src/tip/js/typedarray.js (MIT License)
535 * I made one modification at the end and removed the NaN / Infinity
536 * handling (since it seemed broken [caused an overflow] and we don't need it). See MJL comments.
537 * @param v - A double
538 *
539 */
540var doubleToIEEE754String = function (v) {
541 util.assert(!isInvalidJSONNumber(v), 'Invalid JSON number'); // MJL
542 var ebits = 11, fbits = 52;
543 var bias = (1 << (ebits - 1)) - 1;
544 var s, e, f, ln, i;
545 // Compute sign, exponent, fraction
546 // Skip NaN / Infinity handling --MJL.
547 if (v === 0) {
548 e = 0;
549 f = 0;
550 s = 1 / v === -Infinity ? 1 : 0;
551 }
552 else {
553 s = v < 0;
554 v = Math.abs(v);
555 if (v >= Math.pow(2, 1 - bias)) {
556 // Normalized
557 ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
558 e = ln + bias;
559 f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
560 }
561 else {
562 // Denormalized
563 e = 0;
564 f = Math.round(v / Math.pow(2, 1 - bias - fbits));
565 }
566 }
567 // Pack sign, exponent, fraction
568 var bits = [];
569 for (i = fbits; i; i -= 1) {
570 bits.push(f % 2 ? 1 : 0);
571 f = Math.floor(f / 2);
572 }
573 for (i = ebits; i; i -= 1) {
574 bits.push(e % 2 ? 1 : 0);
575 e = Math.floor(e / 2);
576 }
577 bits.push(s ? 1 : 0);
578 bits.reverse();
579 var str = bits.join('');
580 // Return the data as a hex string. --MJL
581 var hexByteString = '';
582 for (i = 0; i < 64; i += 8) {
583 var hexByte = parseInt(str.substr(i, 8), 2).toString(16);
584 if (hexByte.length === 1) {
585 hexByte = '0' + hexByte;
586 }
587 hexByteString = hexByteString + hexByte;
588 }
589 return hexByteString.toLowerCase();
590};
591/**
592 * Used to detect if we're in a Chrome content script (which executes in an
593 * isolated environment where long-polling doesn't work).
594 */
595var isChromeExtensionContentScript = function () {
596 return !!(typeof window === 'object' &&
597 window['chrome'] &&
598 window['chrome']['extension'] &&
599 !/^chrome/.test(window.location.href));
600};
601/**
602 * Used to detect if we're in a Windows 8 Store app.
603 */
604var isWindowsStoreApp = function () {
605 // Check for the presence of a couple WinRT globals
606 return typeof Windows === 'object' && typeof Windows.UI === 'object';
607};
608/**
609 * Converts a server error code to a Javascript Error
610 */
611function errorForServerCode(code, query) {
612 var reason = 'Unknown Error';
613 if (code === 'too_big') {
614 reason =
615 'The data requested exceeds the maximum size ' +
616 'that can be accessed with a single request.';
617 }
618 else if (code === 'permission_denied') {
619 reason = "Client doesn't have permission to access the desired data.";
620 }
621 else if (code === 'unavailable') {
622 reason = 'The service is unavailable';
623 }
624 var error = new Error(code + ' at ' + query._path.toString() + ': ' + reason);
625 // eslint-disable-next-line @typescript-eslint/no-explicit-any
626 error.code = code.toUpperCase();
627 return error;
628}
629/**
630 * Used to test for integer-looking strings
631 */
632var INTEGER_REGEXP_ = new RegExp('^-?(0*)\\d{1,10}$');
633/**
634 * For use in keys, the minimum possible 32-bit integer.
635 */
636var INTEGER_32_MIN = -2147483648;
637/**
638 * For use in kyes, the maximum possible 32-bit integer.
639 */
640var INTEGER_32_MAX = 2147483647;
641/**
642 * If the string contains a 32-bit integer, return it. Else return null.
643 */
644var tryParseInt = function (str) {
645 if (INTEGER_REGEXP_.test(str)) {
646 var intVal = Number(str);
647 if (intVal >= INTEGER_32_MIN && intVal <= INTEGER_32_MAX) {
648 return intVal;
649 }
650 }
651 return null;
652};
653/**
654 * Helper to run some code but catch any exceptions and re-throw them later.
655 * Useful for preventing user callbacks from breaking internal code.
656 *
657 * Re-throwing the exception from a setTimeout is a little evil, but it's very
658 * convenient (we don't have to try to figure out when is a safe point to
659 * re-throw it), and the behavior seems reasonable:
660 *
661 * * If you aren't pausing on exceptions, you get an error in the console with
662 * the correct stack trace.
663 * * If you're pausing on all exceptions, the debugger will pause on your
664 * exception and then again when we rethrow it.
665 * * If you're only pausing on uncaught exceptions, the debugger will only pause
666 * on us re-throwing it.
667 *
668 * @param fn - The code to guard.
669 */
670var exceptionGuard = function (fn) {
671 try {
672 fn();
673 }
674 catch (e) {
675 // Re-throw exception when it's safe.
676 setTimeout(function () {
677 // It used to be that "throw e" would result in a good console error with
678 // relevant context, but as of Chrome 39, you just get the firebase.js
679 // file/line number where we re-throw it, which is useless. So we log
680 // e.stack explicitly.
681 var stack = e.stack || '';
682 warn('Exception was thrown by user callback.', stack);
683 throw e;
684 }, Math.floor(0));
685 }
686};
687/**
688 * @returns {boolean} true if we think we're currently being crawled.
689 */
690var beingCrawled = function () {
691 var userAgent = (typeof window === 'object' &&
692 window['navigator'] &&
693 window['navigator']['userAgent']) ||
694 '';
695 // For now we whitelist the most popular crawlers. We should refine this to be the set of crawlers we
696 // believe to support JavaScript/AJAX rendering.
697 // NOTE: Google Webmaster Tools doesn't really belong, but their "This is how a visitor to your website
698 // would have seen the page" is flaky if we don't treat it as a crawler.
699 return (userAgent.search(/googlebot|google webmaster tools|bingbot|yahoo! slurp|baiduspider|yandexbot|duckduckbot/i) >= 0);
700};
701/**
702 * Same as setTimeout() except on Node.JS it will /not/ prevent the process from exiting.
703 *
704 * It is removed with clearTimeout() as normal.
705 *
706 * @param fn - Function to run.
707 * @param time - Milliseconds to wait before running.
708 * @returns The setTimeout() return value.
709 */
710var setTimeoutNonBlocking = function (fn, time) {
711 var timeout = setTimeout(fn, time);
712 // eslint-disable-next-line @typescript-eslint/no-explicit-any
713 if (typeof timeout === 'object' && timeout['unref']) {
714 // eslint-disable-next-line @typescript-eslint/no-explicit-any
715 timeout['unref']();
716 }
717 return timeout;
718};
719
720/**
721 * @license
722 * Copyright 2017 Google LLC
723 *
724 * Licensed under the Apache License, Version 2.0 (the "License");
725 * you may not use this file except in compliance with the License.
726 * You may obtain a copy of the License at
727 *
728 * http://www.apache.org/licenses/LICENSE-2.0
729 *
730 * Unless required by applicable law or agreed to in writing, software
731 * distributed under the License is distributed on an "AS IS" BASIS,
732 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
733 * See the License for the specific language governing permissions and
734 * limitations under the License.
735 */
736/**
737 * A class that holds metadata about a Repo object
738 */
739var RepoInfo = /** @class */ (function () {
740 /**
741 * @param host - Hostname portion of the url for the repo
742 * @param secure - Whether or not this repo is accessed over ssl
743 * @param namespace - The namespace represented by the repo
744 * @param webSocketOnly - Whether to prefer websockets over all other transports (used by Nest).
745 * @param nodeAdmin - Whether this instance uses Admin SDK credentials
746 * @param persistenceKey - Override the default session persistence storage key
747 */
748 function RepoInfo(host, secure, namespace, webSocketOnly, nodeAdmin, persistenceKey, includeNamespaceInQueryParams) {
749 if (nodeAdmin === void 0) { nodeAdmin = false; }
750 if (persistenceKey === void 0) { persistenceKey = ''; }
751 if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
752 this.secure = secure;
753 this.namespace = namespace;
754 this.webSocketOnly = webSocketOnly;
755 this.nodeAdmin = nodeAdmin;
756 this.persistenceKey = persistenceKey;
757 this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
758 this._host = host.toLowerCase();
759 this._domain = this._host.substr(this._host.indexOf('.') + 1);
760 this.internalHost =
761 PersistentStorage.get('host:' + host) || this._host;
762 }
763 RepoInfo.prototype.isCacheableHost = function () {
764 return this.internalHost.substr(0, 2) === 's-';
765 };
766 RepoInfo.prototype.isCustomHost = function () {
767 return (this._domain !== 'firebaseio.com' &&
768 this._domain !== 'firebaseio-demo.com');
769 };
770 Object.defineProperty(RepoInfo.prototype, "host", {
771 get: function () {
772 return this._host;
773 },
774 set: function (newHost) {
775 if (newHost !== this.internalHost) {
776 this.internalHost = newHost;
777 if (this.isCacheableHost()) {
778 PersistentStorage.set('host:' + this._host, this.internalHost);
779 }
780 }
781 },
782 enumerable: false,
783 configurable: true
784 });
785 RepoInfo.prototype.toString = function () {
786 var str = this.toURLString();
787 if (this.persistenceKey) {
788 str += '<' + this.persistenceKey + '>';
789 }
790 return str;
791 };
792 RepoInfo.prototype.toURLString = function () {
793 var protocol = this.secure ? 'https://' : 'http://';
794 var query = this.includeNamespaceInQueryParams
795 ? "?ns=" + this.namespace
796 : '';
797 return "" + protocol + this.host + "/" + query;
798 };
799 return RepoInfo;
800}());
801function repoInfoNeedsQueryParam(repoInfo) {
802 return (repoInfo.host !== repoInfo.internalHost ||
803 repoInfo.isCustomHost() ||
804 repoInfo.includeNamespaceInQueryParams);
805}
806/**
807 * Returns the websocket URL for this repo
808 * @param repoInfo - RepoInfo object
809 * @param type - of connection
810 * @param params - list
811 * @returns The URL for this repo
812 */
813function repoInfoConnectionURL(repoInfo, type, params) {
814 util.assert(typeof type === 'string', 'typeof type must == string');
815 util.assert(typeof params === 'object', 'typeof params must == object');
816 var connURL;
817 if (type === WEBSOCKET) {
818 connURL =
819 (repoInfo.secure ? 'wss://' : 'ws://') + repoInfo.internalHost + '/.ws?';
820 }
821 else if (type === LONG_POLLING) {
822 connURL =
823 (repoInfo.secure ? 'https://' : 'http://') +
824 repoInfo.internalHost +
825 '/.lp?';
826 }
827 else {
828 throw new Error('Unknown connection type: ' + type);
829 }
830 if (repoInfoNeedsQueryParam(repoInfo)) {
831 params['ns'] = repoInfo.namespace;
832 }
833 var pairs = [];
834 each(params, function (key, value) {
835 pairs.push(key + '=' + value);
836 });
837 return connURL + pairs.join('&');
838}
839
840/**
841 * @license
842 * Copyright 2017 Google LLC
843 *
844 * Licensed under the Apache License, Version 2.0 (the "License");
845 * you may not use this file except in compliance with the License.
846 * You may obtain a copy of the License at
847 *
848 * http://www.apache.org/licenses/LICENSE-2.0
849 *
850 * Unless required by applicable law or agreed to in writing, software
851 * distributed under the License is distributed on an "AS IS" BASIS,
852 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
853 * See the License for the specific language governing permissions and
854 * limitations under the License.
855 */
856/**
857 * Tracks a collection of stats.
858 */
859var StatsCollection = /** @class */ (function () {
860 function StatsCollection() {
861 this.counters_ = {};
862 }
863 StatsCollection.prototype.incrementCounter = function (name, amount) {
864 if (amount === void 0) { amount = 1; }
865 if (!util.contains(this.counters_, name)) {
866 this.counters_[name] = 0;
867 }
868 this.counters_[name] += amount;
869 };
870 StatsCollection.prototype.get = function () {
871 return util.deepCopy(this.counters_);
872 };
873 return StatsCollection;
874}());
875
876/**
877 * @license
878 * Copyright 2017 Google LLC
879 *
880 * Licensed under the Apache License, Version 2.0 (the "License");
881 * you may not use this file except in compliance with the License.
882 * You may obtain a copy of the License at
883 *
884 * http://www.apache.org/licenses/LICENSE-2.0
885 *
886 * Unless required by applicable law or agreed to in writing, software
887 * distributed under the License is distributed on an "AS IS" BASIS,
888 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
889 * See the License for the specific language governing permissions and
890 * limitations under the License.
891 */
892var collections = {};
893var reporters = {};
894function statsManagerGetCollection(repoInfo) {
895 var hashString = repoInfo.toString();
896 if (!collections[hashString]) {
897 collections[hashString] = new StatsCollection();
898 }
899 return collections[hashString];
900}
901function statsManagerGetOrCreateReporter(repoInfo, creatorFunction) {
902 var hashString = repoInfo.toString();
903 if (!reporters[hashString]) {
904 reporters[hashString] = creatorFunction();
905 }
906 return reporters[hashString];
907}
908
909/**
910 * @license
911 * Copyright 2019 Google LLC
912 *
913 * Licensed under the Apache License, Version 2.0 (the "License");
914 * you may not use this file except in compliance with the License.
915 * You may obtain a copy of the License at
916 *
917 * http://www.apache.org/licenses/LICENSE-2.0
918 *
919 * Unless required by applicable law or agreed to in writing, software
920 * distributed under the License is distributed on an "AS IS" BASIS,
921 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
922 * See the License for the specific language governing permissions and
923 * limitations under the License.
924 */
925/** The semver (www.semver.org) version of the SDK. */
926var SDK_VERSION = '';
927/**
928 * SDK_VERSION should be set before any database instance is created
929 * @internal
930 */
931function setSDKVersion(version) {
932 SDK_VERSION = version;
933}
934
935/**
936 * @license
937 * Copyright 2017 Google LLC
938 *
939 * Licensed under the Apache License, Version 2.0 (the "License");
940 * you may not use this file except in compliance with the License.
941 * You may obtain a copy of the License at
942 *
943 * http://www.apache.org/licenses/LICENSE-2.0
944 *
945 * Unless required by applicable law or agreed to in writing, software
946 * distributed under the License is distributed on an "AS IS" BASIS,
947 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
948 * See the License for the specific language governing permissions and
949 * limitations under the License.
950 */
951var WEBSOCKET_MAX_FRAME_SIZE = 16384;
952var WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
953var WebSocketImpl = null;
954if (typeof MozWebSocket !== 'undefined') {
955 WebSocketImpl = MozWebSocket;
956}
957else if (typeof WebSocket !== 'undefined') {
958 WebSocketImpl = WebSocket;
959}
960function setWebSocketImpl(impl) {
961 WebSocketImpl = impl;
962}
963/**
964 * Create a new websocket connection with the given callbacks.
965 */
966var WebSocketConnection = /** @class */ (function () {
967 /**
968 * @param connId identifier for this transport
969 * @param repoInfo The info for the websocket endpoint.
970 * @param applicationId The Firebase App ID for this project.
971 * @param appCheckToken The App Check Token for this client.
972 * @param authToken The Auth Token for this client.
973 * @param transportSessionId Optional transportSessionId if this is connecting
974 * to an existing transport session
975 * @param lastSessionId Optional lastSessionId if there was a previous
976 * connection
977 */
978 function WebSocketConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
979 this.connId = connId;
980 this.applicationId = applicationId;
981 this.appCheckToken = appCheckToken;
982 this.authToken = authToken;
983 this.keepaliveTimer = null;
984 this.frames = null;
985 this.totalFrames = 0;
986 this.bytesSent = 0;
987 this.bytesReceived = 0;
988 this.log_ = logWrapper(this.connId);
989 this.stats_ = statsManagerGetCollection(repoInfo);
990 this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId);
991 this.nodeAdmin = repoInfo.nodeAdmin;
992 }
993 /**
994 * @param repoInfo - The info for the websocket endpoint.
995 * @param transportSessionId - Optional transportSessionId if this is connecting to an existing transport
996 * session
997 * @param lastSessionId - Optional lastSessionId if there was a previous connection
998 * @returns connection url
999 */
1000 WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId) {
1001 var urlParams = {};
1002 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1003 if (!util.isNodeSdk() &&
1004 typeof location !== 'undefined' &&
1005 location.hostname &&
1006 FORGE_DOMAIN_RE.test(location.hostname)) {
1007 urlParams[REFERER_PARAM] = FORGE_REF;
1008 }
1009 if (transportSessionId) {
1010 urlParams[TRANSPORT_SESSION_PARAM] = transportSessionId;
1011 }
1012 if (lastSessionId) {
1013 urlParams[LAST_SESSION_PARAM] = lastSessionId;
1014 }
1015 if (appCheckToken) {
1016 urlParams[APP_CHECK_TOKEN_PARAM] = appCheckToken;
1017 }
1018 if (applicationId) {
1019 urlParams[APPLICATION_ID_PARAM] = applicationId;
1020 }
1021 return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
1022 };
1023 /**
1024 * @param onMessage - Callback when messages arrive
1025 * @param onDisconnect - Callback with connection lost.
1026 */
1027 WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
1028 var _this = this;
1029 this.onDisconnect = onDisconnect;
1030 this.onMessage = onMessage;
1031 this.log_('Websocket connecting to ' + this.connURL);
1032 this.everConnected_ = false;
1033 // Assume failure until proven otherwise.
1034 PersistentStorage.set('previous_websocket_failure', true);
1035 try {
1036 var options = void 0;
1037 if (util.isNodeSdk()) {
1038 var device = this.nodeAdmin ? 'AdminNode' : 'Node';
1039 // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
1040 var options_1 = {
1041 headers: {
1042 'User-Agent': "Firebase/" + PROTOCOL_VERSION + "/" + SDK_VERSION + "/" + process.platform + "/" + device,
1043 'X-Firebase-GMPID': this.applicationId || ''
1044 }
1045 };
1046 // If using Node with admin creds, AppCheck-related checks are unnecessary.
1047 // Note that we send the credentials here even if they aren't admin credentials, which is
1048 // not a problem.
1049 // Note that this header is just used to bypass appcheck, and the token should still be sent
1050 // through the websocket connection once it is established.
1051 if (this.authToken) {
1052 options_1.headers['Authorization'] = "Bearer " + this.authToken;
1053 }
1054 if (this.appCheckToken) {
1055 options_1.headers['X-Firebase-AppCheck'] = this.appCheckToken;
1056 }
1057 // Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
1058 var env = process['env'];
1059 var proxy = this.connURL.indexOf('wss://') === 0
1060 ? env['HTTPS_PROXY'] || env['https_proxy']
1061 : env['HTTP_PROXY'] || env['http_proxy'];
1062 if (proxy) {
1063 options_1['proxy'] = { origin: proxy };
1064 }
1065 }
1066 this.mySock = new WebSocketImpl(this.connURL, [], options);
1067 }
1068 catch (e) {
1069 this.log_('Error instantiating WebSocket.');
1070 var error = e.message || e.data;
1071 if (error) {
1072 this.log_(error);
1073 }
1074 this.onClosed_();
1075 return;
1076 }
1077 this.mySock.onopen = function () {
1078 _this.log_('Websocket connected.');
1079 _this.everConnected_ = true;
1080 };
1081 this.mySock.onclose = function () {
1082 _this.log_('Websocket connection was disconnected.');
1083 _this.mySock = null;
1084 _this.onClosed_();
1085 };
1086 this.mySock.onmessage = function (m) {
1087 _this.handleIncomingFrame(m);
1088 };
1089 this.mySock.onerror = function (e) {
1090 _this.log_('WebSocket error. Closing connection.');
1091 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1092 var error = e.message || e.data;
1093 if (error) {
1094 _this.log_(error);
1095 }
1096 _this.onClosed_();
1097 };
1098 };
1099 /**
1100 * No-op for websockets, we don't need to do anything once the connection is confirmed as open
1101 */
1102 WebSocketConnection.prototype.start = function () { };
1103 WebSocketConnection.forceDisallow = function () {
1104 WebSocketConnection.forceDisallow_ = true;
1105 };
1106 WebSocketConnection.isAvailable = function () {
1107 var isOldAndroid = false;
1108 if (typeof navigator !== 'undefined' && navigator.userAgent) {
1109 var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
1110 var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
1111 if (oldAndroidMatch && oldAndroidMatch.length > 1) {
1112 if (parseFloat(oldAndroidMatch[1]) < 4.4) {
1113 isOldAndroid = true;
1114 }
1115 }
1116 }
1117 return (!isOldAndroid &&
1118 WebSocketImpl !== null &&
1119 !WebSocketConnection.forceDisallow_);
1120 };
1121 /**
1122 * Returns true if we previously failed to connect with this transport.
1123 */
1124 WebSocketConnection.previouslyFailed = function () {
1125 // If our persistent storage is actually only in-memory storage,
1126 // we default to assuming that it previously failed to be safe.
1127 return (PersistentStorage.isInMemoryStorage ||
1128 PersistentStorage.get('previous_websocket_failure') === true);
1129 };
1130 WebSocketConnection.prototype.markConnectionHealthy = function () {
1131 PersistentStorage.remove('previous_websocket_failure');
1132 };
1133 WebSocketConnection.prototype.appendFrame_ = function (data) {
1134 this.frames.push(data);
1135 if (this.frames.length === this.totalFrames) {
1136 var fullMess = this.frames.join('');
1137 this.frames = null;
1138 var jsonMess = util.jsonEval(fullMess);
1139 //handle the message
1140 this.onMessage(jsonMess);
1141 }
1142 };
1143 /**
1144 * @param frameCount - The number of frames we are expecting from the server
1145 */
1146 WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
1147 this.totalFrames = frameCount;
1148 this.frames = [];
1149 };
1150 /**
1151 * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
1152 * @returns Any remaining data to be process, or null if there is none
1153 */
1154 WebSocketConnection.prototype.extractFrameCount_ = function (data) {
1155 util.assert(this.frames === null, 'We already have a frame buffer');
1156 // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
1157 // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
1158 if (data.length <= 6) {
1159 var frameCount = Number(data);
1160 if (!isNaN(frameCount)) {
1161 this.handleNewFrameCount_(frameCount);
1162 return null;
1163 }
1164 }
1165 this.handleNewFrameCount_(1);
1166 return data;
1167 };
1168 /**
1169 * Process a websocket frame that has arrived from the server.
1170 * @param mess - The frame data
1171 */
1172 WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
1173 if (this.mySock === null) {
1174 return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
1175 }
1176 var data = mess['data'];
1177 this.bytesReceived += data.length;
1178 this.stats_.incrementCounter('bytes_received', data.length);
1179 this.resetKeepAlive();
1180 if (this.frames !== null) {
1181 // we're buffering
1182 this.appendFrame_(data);
1183 }
1184 else {
1185 // try to parse out a frame count, otherwise, assume 1 and process it
1186 var remainingData = this.extractFrameCount_(data);
1187 if (remainingData !== null) {
1188 this.appendFrame_(remainingData);
1189 }
1190 }
1191 };
1192 /**
1193 * Send a message to the server
1194 * @param data - The JSON object to transmit
1195 */
1196 WebSocketConnection.prototype.send = function (data) {
1197 this.resetKeepAlive();
1198 var dataStr = util.stringify(data);
1199 this.bytesSent += dataStr.length;
1200 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1201 //We can only fit a certain amount in each websocket frame, so we need to split this request
1202 //up into multiple pieces if it doesn't fit in one request.
1203 var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
1204 //Send the length header
1205 if (dataSegs.length > 1) {
1206 this.sendString_(String(dataSegs.length));
1207 }
1208 //Send the actual data in segments.
1209 for (var i = 0; i < dataSegs.length; i++) {
1210 this.sendString_(dataSegs[i]);
1211 }
1212 };
1213 WebSocketConnection.prototype.shutdown_ = function () {
1214 this.isClosed_ = true;
1215 if (this.keepaliveTimer) {
1216 clearInterval(this.keepaliveTimer);
1217 this.keepaliveTimer = null;
1218 }
1219 if (this.mySock) {
1220 this.mySock.close();
1221 this.mySock = null;
1222 }
1223 };
1224 WebSocketConnection.prototype.onClosed_ = function () {
1225 if (!this.isClosed_) {
1226 this.log_('WebSocket is closing itself');
1227 this.shutdown_();
1228 // since this is an internal close, trigger the close listener
1229 if (this.onDisconnect) {
1230 this.onDisconnect(this.everConnected_);
1231 this.onDisconnect = null;
1232 }
1233 }
1234 };
1235 /**
1236 * External-facing close handler.
1237 * Close the websocket and kill the connection.
1238 */
1239 WebSocketConnection.prototype.close = function () {
1240 if (!this.isClosed_) {
1241 this.log_('WebSocket is being closed');
1242 this.shutdown_();
1243 }
1244 };
1245 /**
1246 * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
1247 * the last activity.
1248 */
1249 WebSocketConnection.prototype.resetKeepAlive = function () {
1250 var _this = this;
1251 clearInterval(this.keepaliveTimer);
1252 this.keepaliveTimer = setInterval(function () {
1253 //If there has been no websocket activity for a while, send a no-op
1254 if (_this.mySock) {
1255 _this.sendString_('0');
1256 }
1257 _this.resetKeepAlive();
1258 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1259 }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
1260 };
1261 /**
1262 * Send a string over the websocket.
1263 *
1264 * @param str - String to send.
1265 */
1266 WebSocketConnection.prototype.sendString_ = function (str) {
1267 // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
1268 // calls for some unknown reason. We treat these as an error and disconnect.
1269 // See https://app.asana.com/0/58926111402292/68021340250410
1270 try {
1271 this.mySock.send(str);
1272 }
1273 catch (e) {
1274 this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
1275 setTimeout(this.onClosed_.bind(this), 0);
1276 }
1277 };
1278 /**
1279 * Number of response before we consider the connection "healthy."
1280 */
1281 WebSocketConnection.responsesRequiredToBeHealthy = 2;
1282 /**
1283 * Time to wait for the connection te become healthy before giving up.
1284 */
1285 WebSocketConnection.healthyTimeout = 30000;
1286 return WebSocketConnection;
1287}());
1288
1289/**
1290 * @license
1291 * Copyright 2021 Google LLC
1292 *
1293 * Licensed under the Apache License, Version 2.0 (the "License");
1294 * you may not use this file except in compliance with the License.
1295 * You may obtain a copy of the License at
1296 *
1297 * http://www.apache.org/licenses/LICENSE-2.0
1298 *
1299 * Unless required by applicable law or agreed to in writing, software
1300 * distributed under the License is distributed on an "AS IS" BASIS,
1301 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1302 * See the License for the specific language governing permissions and
1303 * limitations under the License.
1304 */
1305/**
1306 * Abstraction around AppCheck's token fetching capabilities.
1307 */
1308var AppCheckTokenProvider = /** @class */ (function () {
1309 function AppCheckTokenProvider(appName_, appCheckProvider) {
1310 var _this = this;
1311 this.appName_ = appName_;
1312 this.appCheckProvider = appCheckProvider;
1313 this.appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
1314 if (!this.appCheck) {
1315 appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.get().then(function (appCheck) { return (_this.appCheck = appCheck); });
1316 }
1317 }
1318 AppCheckTokenProvider.prototype.getToken = function (forceRefresh) {
1319 var _this = this;
1320 if (!this.appCheck) {
1321 return new Promise(function (resolve, reject) {
1322 // Support delayed initialization of FirebaseAppCheck. This allows our
1323 // customers to initialize the RTDB SDK before initializing Firebase
1324 // AppCheck and ensures that all requests are authenticated if a token
1325 // becomes available before the timoeout below expires.
1326 setTimeout(function () {
1327 if (_this.appCheck) {
1328 _this.getToken(forceRefresh).then(resolve, reject);
1329 }
1330 else {
1331 resolve(null);
1332 }
1333 }, 0);
1334 });
1335 }
1336 return this.appCheck.getToken(forceRefresh);
1337 };
1338 AppCheckTokenProvider.prototype.addTokenChangeListener = function (listener) {
1339 var _a;
1340 (_a = this.appCheckProvider) === null || _a === void 0 ? void 0 : _a.get().then(function (appCheck) { return appCheck.addTokenListener(listener); });
1341 };
1342 AppCheckTokenProvider.prototype.notifyForInvalidToken = function () {
1343 warn("Provided AppCheck credentials for the app named \"" + this.appName_ + "\" " +
1344 'are invalid. This usually indicates your app was not initialized correctly.');
1345 };
1346 return AppCheckTokenProvider;
1347}());
1348
1349/**
1350 * @license
1351 * Copyright 2017 Google LLC
1352 *
1353 * Licensed under the Apache License, Version 2.0 (the "License");
1354 * you may not use this file except in compliance with the License.
1355 * You may obtain a copy of the License at
1356 *
1357 * http://www.apache.org/licenses/LICENSE-2.0
1358 *
1359 * Unless required by applicable law or agreed to in writing, software
1360 * distributed under the License is distributed on an "AS IS" BASIS,
1361 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1362 * See the License for the specific language governing permissions and
1363 * limitations under the License.
1364 */
1365/**
1366 * Abstraction around FirebaseApp's token fetching capabilities.
1367 */
1368var FirebaseAuthTokenProvider = /** @class */ (function () {
1369 function FirebaseAuthTokenProvider(appName_, firebaseOptions_, authProvider_) {
1370 var _this = this;
1371 this.appName_ = appName_;
1372 this.firebaseOptions_ = firebaseOptions_;
1373 this.authProvider_ = authProvider_;
1374 this.auth_ = null;
1375 this.auth_ = authProvider_.getImmediate({ optional: true });
1376 if (!this.auth_) {
1377 authProvider_.onInit(function (auth) { return (_this.auth_ = auth); });
1378 }
1379 }
1380 FirebaseAuthTokenProvider.prototype.getToken = function (forceRefresh) {
1381 var _this = this;
1382 if (!this.auth_) {
1383 return new Promise(function (resolve, reject) {
1384 // Support delayed initialization of FirebaseAuth. This allows our
1385 // customers to initialize the RTDB SDK before initializing Firebase
1386 // Auth and ensures that all requests are authenticated if a token
1387 // becomes available before the timoeout below expires.
1388 setTimeout(function () {
1389 if (_this.auth_) {
1390 _this.getToken(forceRefresh).then(resolve, reject);
1391 }
1392 else {
1393 resolve(null);
1394 }
1395 }, 0);
1396 });
1397 }
1398 return this.auth_.getToken(forceRefresh).catch(function (error) {
1399 // TODO: Need to figure out all the cases this is raised and whether
1400 // this makes sense.
1401 if (error && error.code === 'auth/token-not-initialized') {
1402 log('Got auth/token-not-initialized error. Treating as null token.');
1403 return null;
1404 }
1405 else {
1406 return Promise.reject(error);
1407 }
1408 });
1409 };
1410 FirebaseAuthTokenProvider.prototype.addTokenChangeListener = function (listener) {
1411 // TODO: We might want to wrap the listener and call it with no args to
1412 // avoid a leaky abstraction, but that makes removing the listener harder.
1413 if (this.auth_) {
1414 this.auth_.addAuthTokenListener(listener);
1415 }
1416 else {
1417 this.authProvider_
1418 .get()
1419 .then(function (auth) { return auth.addAuthTokenListener(listener); });
1420 }
1421 };
1422 FirebaseAuthTokenProvider.prototype.removeTokenChangeListener = function (listener) {
1423 this.authProvider_
1424 .get()
1425 .then(function (auth) { return auth.removeAuthTokenListener(listener); });
1426 };
1427 FirebaseAuthTokenProvider.prototype.notifyForInvalidToken = function () {
1428 var errorMessage = 'Provided authentication credentials for the app named "' +
1429 this.appName_ +
1430 '" are invalid. This usually indicates your app was not ' +
1431 'initialized correctly. ';
1432 if ('credential' in this.firebaseOptions_) {
1433 errorMessage +=
1434 'Make sure the "credential" property provided to initializeApp() ' +
1435 'is authorized to access the specified "databaseURL" and is from the correct ' +
1436 'project.';
1437 }
1438 else if ('serviceAccount' in this.firebaseOptions_) {
1439 errorMessage +=
1440 'Make sure the "serviceAccount" property provided to initializeApp() ' +
1441 'is authorized to access the specified "databaseURL" and is from the correct ' +
1442 'project.';
1443 }
1444 else {
1445 errorMessage +=
1446 'Make sure the "apiKey" and "databaseURL" properties provided to ' +
1447 'initializeApp() match the values provided for your app at ' +
1448 'https://console.firebase.google.com/.';
1449 }
1450 warn(errorMessage);
1451 };
1452 return FirebaseAuthTokenProvider;
1453}());
1454/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */
1455var EmulatorTokenProvider = /** @class */ (function () {
1456 function EmulatorTokenProvider(accessToken) {
1457 this.accessToken = accessToken;
1458 }
1459 EmulatorTokenProvider.prototype.getToken = function (forceRefresh) {
1460 return Promise.resolve({
1461 accessToken: this.accessToken
1462 });
1463 };
1464 EmulatorTokenProvider.prototype.addTokenChangeListener = function (listener) {
1465 // Invoke the listener immediately to match the behavior in Firebase Auth
1466 // (see packages/auth/src/auth.js#L1807)
1467 listener(this.accessToken);
1468 };
1469 EmulatorTokenProvider.prototype.removeTokenChangeListener = function (listener) { };
1470 EmulatorTokenProvider.prototype.notifyForInvalidToken = function () { };
1471 /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */
1472 EmulatorTokenProvider.OWNER = 'owner';
1473 return EmulatorTokenProvider;
1474}());
1475
1476/**
1477 * @license
1478 * Copyright 2017 Google LLC
1479 *
1480 * Licensed under the Apache License, Version 2.0 (the "License");
1481 * you may not use this file except in compliance with the License.
1482 * You may obtain a copy of the License at
1483 *
1484 * http://www.apache.org/licenses/LICENSE-2.0
1485 *
1486 * Unless required by applicable law or agreed to in writing, software
1487 * distributed under the License is distributed on an "AS IS" BASIS,
1488 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1489 * See the License for the specific language governing permissions and
1490 * limitations under the License.
1491 */
1492/**
1493 * This class ensures the packets from the server arrive in order
1494 * This class takes data from the server and ensures it gets passed into the callbacks in order.
1495 */
1496var PacketReceiver = /** @class */ (function () {
1497 /**
1498 * @param onMessage_
1499 */
1500 function PacketReceiver(onMessage_) {
1501 this.onMessage_ = onMessage_;
1502 this.pendingResponses = [];
1503 this.currentResponseNum = 0;
1504 this.closeAfterResponse = -1;
1505 this.onClose = null;
1506 }
1507 PacketReceiver.prototype.closeAfter = function (responseNum, callback) {
1508 this.closeAfterResponse = responseNum;
1509 this.onClose = callback;
1510 if (this.closeAfterResponse < this.currentResponseNum) {
1511 this.onClose();
1512 this.onClose = null;
1513 }
1514 };
1515 /**
1516 * Each message from the server comes with a response number, and an array of data. The responseNumber
1517 * allows us to ensure that we process them in the right order, since we can't be guaranteed that all
1518 * browsers will respond in the same order as the requests we sent
1519 */
1520 PacketReceiver.prototype.handleResponse = function (requestNum, data) {
1521 var _this = this;
1522 this.pendingResponses[requestNum] = data;
1523 var _loop_1 = function () {
1524 var toProcess = this_1.pendingResponses[this_1.currentResponseNum];
1525 delete this_1.pendingResponses[this_1.currentResponseNum];
1526 var _loop_2 = function (i) {
1527 if (toProcess[i]) {
1528 exceptionGuard(function () {
1529 _this.onMessage_(toProcess[i]);
1530 });
1531 }
1532 };
1533 for (var i = 0; i < toProcess.length; ++i) {
1534 _loop_2(i);
1535 }
1536 if (this_1.currentResponseNum === this_1.closeAfterResponse) {
1537 if (this_1.onClose) {
1538 this_1.onClose();
1539 this_1.onClose = null;
1540 }
1541 return "break";
1542 }
1543 this_1.currentResponseNum++;
1544 };
1545 var this_1 = this;
1546 while (this.pendingResponses[this.currentResponseNum]) {
1547 var state_1 = _loop_1();
1548 if (state_1 === "break")
1549 break;
1550 }
1551 };
1552 return PacketReceiver;
1553}());
1554
1555/**
1556 * @license
1557 * Copyright 2017 Google LLC
1558 *
1559 * Licensed under the Apache License, Version 2.0 (the "License");
1560 * you may not use this file except in compliance with the License.
1561 * You may obtain a copy of the License at
1562 *
1563 * http://www.apache.org/licenses/LICENSE-2.0
1564 *
1565 * Unless required by applicable law or agreed to in writing, software
1566 * distributed under the License is distributed on an "AS IS" BASIS,
1567 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1568 * See the License for the specific language governing permissions and
1569 * limitations under the License.
1570 */
1571// URL query parameters associated with longpolling
1572var FIREBASE_LONGPOLL_START_PARAM = 'start';
1573var FIREBASE_LONGPOLL_CLOSE_COMMAND = 'close';
1574var FIREBASE_LONGPOLL_COMMAND_CB_NAME = 'pLPCommand';
1575var FIREBASE_LONGPOLL_DATA_CB_NAME = 'pRTLPCB';
1576var FIREBASE_LONGPOLL_ID_PARAM = 'id';
1577var FIREBASE_LONGPOLL_PW_PARAM = 'pw';
1578var FIREBASE_LONGPOLL_SERIAL_PARAM = 'ser';
1579var FIREBASE_LONGPOLL_CALLBACK_ID_PARAM = 'cb';
1580var FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM = 'seg';
1581var FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET = 'ts';
1582var FIREBASE_LONGPOLL_DATA_PARAM = 'd';
1583var FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM = 'dframe';
1584//Data size constants.
1585//TODO: Perf: the maximum length actually differs from browser to browser.
1586// We should check what browser we're on and set accordingly.
1587var MAX_URL_DATA_SIZE = 1870;
1588var SEG_HEADER_SIZE = 30; //ie: &seg=8299234&ts=982389123&d=
1589var MAX_PAYLOAD_SIZE = MAX_URL_DATA_SIZE - SEG_HEADER_SIZE;
1590/**
1591 * Keepalive period
1592 * send a fresh request at minimum every 25 seconds. Opera has a maximum request
1593 * length of 30 seconds that we can't exceed.
1594 */
1595var KEEPALIVE_REQUEST_INTERVAL = 25000;
1596/**
1597 * How long to wait before aborting a long-polling connection attempt.
1598 */
1599var LP_CONNECT_TIMEOUT = 30000;
1600/**
1601 * This class manages a single long-polling connection.
1602 */
1603var BrowserPollConnection = /** @class */ (function () {
1604 /**
1605 * @param connId An identifier for this connection, used for logging
1606 * @param repoInfo The info for the endpoint to send data to.
1607 * @param applicationId The Firebase App ID for this project.
1608 * @param appCheckToken The AppCheck token for this client.
1609 * @param authToken The AuthToken to use for this connection.
1610 * @param transportSessionId Optional transportSessionid if we are
1611 * reconnecting for an existing transport session
1612 * @param lastSessionId Optional lastSessionId if the PersistentConnection has
1613 * already created a connection previously
1614 */
1615 function BrowserPollConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1616 var _this = this;
1617 this.connId = connId;
1618 this.repoInfo = repoInfo;
1619 this.applicationId = applicationId;
1620 this.appCheckToken = appCheckToken;
1621 this.authToken = authToken;
1622 this.transportSessionId = transportSessionId;
1623 this.lastSessionId = lastSessionId;
1624 this.bytesSent = 0;
1625 this.bytesReceived = 0;
1626 this.everConnected_ = false;
1627 this.log_ = logWrapper(connId);
1628 this.stats_ = statsManagerGetCollection(repoInfo);
1629 this.urlFn = function (params) {
1630 // Always add the token if we have one.
1631 if (_this.appCheckToken) {
1632 params[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1633 }
1634 return repoInfoConnectionURL(repoInfo, LONG_POLLING, params);
1635 };
1636 }
1637 /**
1638 * @param onMessage - Callback when messages arrive
1639 * @param onDisconnect - Callback with connection lost.
1640 */
1641 BrowserPollConnection.prototype.open = function (onMessage, onDisconnect) {
1642 var _this = this;
1643 this.curSegmentNum = 0;
1644 this.onDisconnect_ = onDisconnect;
1645 this.myPacketOrderer = new PacketReceiver(onMessage);
1646 this.isClosed_ = false;
1647 this.connectTimeoutTimer_ = setTimeout(function () {
1648 _this.log_('Timed out trying to connect.');
1649 // Make sure we clear the host cache
1650 _this.onClosed_();
1651 _this.connectTimeoutTimer_ = null;
1652 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1653 }, Math.floor(LP_CONNECT_TIMEOUT));
1654 // Ensure we delay the creation of the iframe until the DOM is loaded.
1655 executeWhenDOMReady(function () {
1656 if (_this.isClosed_) {
1657 return;
1658 }
1659 //Set up a callback that gets triggered once a connection is set up.
1660 _this.scriptTagHolder = new FirebaseIFrameScriptHolder(function () {
1661 var args = [];
1662 for (var _i = 0; _i < arguments.length; _i++) {
1663 args[_i] = arguments[_i];
1664 }
1665 var _a = tslib.__read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2]; _a[3]; _a[4];
1666 _this.incrementIncomingBytes_(args);
1667 if (!_this.scriptTagHolder) {
1668 return; // we closed the connection.
1669 }
1670 if (_this.connectTimeoutTimer_) {
1671 clearTimeout(_this.connectTimeoutTimer_);
1672 _this.connectTimeoutTimer_ = null;
1673 }
1674 _this.everConnected_ = true;
1675 if (command === FIREBASE_LONGPOLL_START_PARAM) {
1676 _this.id = arg1;
1677 _this.password = arg2;
1678 }
1679 else if (command === FIREBASE_LONGPOLL_CLOSE_COMMAND) {
1680 // Don't clear the host cache. We got a response from the server, so we know it's reachable
1681 if (arg1) {
1682 // We aren't expecting any more data (other than what the server's already in the process of sending us
1683 // through our already open polls), so don't send any more.
1684 _this.scriptTagHolder.sendNewPolls = false;
1685 // arg1 in this case is the last response number sent by the server. We should try to receive
1686 // all of the responses up to this one before closing
1687 _this.myPacketOrderer.closeAfter(arg1, function () {
1688 _this.onClosed_();
1689 });
1690 }
1691 else {
1692 _this.onClosed_();
1693 }
1694 }
1695 else {
1696 throw new Error('Unrecognized command received: ' + command);
1697 }
1698 }, function () {
1699 var args = [];
1700 for (var _i = 0; _i < arguments.length; _i++) {
1701 args[_i] = arguments[_i];
1702 }
1703 var _a = tslib.__read(args, 2), pN = _a[0], data = _a[1];
1704 _this.incrementIncomingBytes_(args);
1705 _this.myPacketOrderer.handleResponse(pN, data);
1706 }, function () {
1707 _this.onClosed_();
1708 }, _this.urlFn);
1709 //Send the initial request to connect. The serial number is simply to keep the browser from pulling previous results
1710 //from cache.
1711 var urlParams = {};
1712 urlParams[FIREBASE_LONGPOLL_START_PARAM] = 't';
1713 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = Math.floor(Math.random() * 100000000);
1714 if (_this.scriptTagHolder.uniqueCallbackIdentifier) {
1715 urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] =
1716 _this.scriptTagHolder.uniqueCallbackIdentifier;
1717 }
1718 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1719 if (_this.transportSessionId) {
1720 urlParams[TRANSPORT_SESSION_PARAM] = _this.transportSessionId;
1721 }
1722 if (_this.lastSessionId) {
1723 urlParams[LAST_SESSION_PARAM] = _this.lastSessionId;
1724 }
1725 if (_this.applicationId) {
1726 urlParams[APPLICATION_ID_PARAM] = _this.applicationId;
1727 }
1728 if (_this.appCheckToken) {
1729 urlParams[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1730 }
1731 if (typeof location !== 'undefined' &&
1732 location.hostname &&
1733 FORGE_DOMAIN_RE.test(location.hostname)) {
1734 urlParams[REFERER_PARAM] = FORGE_REF;
1735 }
1736 var connectURL = _this.urlFn(urlParams);
1737 _this.log_('Connecting via long-poll to ' + connectURL);
1738 _this.scriptTagHolder.addTag(connectURL, function () {
1739 /* do nothing */
1740 });
1741 });
1742 };
1743 /**
1744 * Call this when a handshake has completed successfully and we want to consider the connection established
1745 */
1746 BrowserPollConnection.prototype.start = function () {
1747 this.scriptTagHolder.startLongPoll(this.id, this.password);
1748 this.addDisconnectPingFrame(this.id, this.password);
1749 };
1750 /**
1751 * Forces long polling to be considered as a potential transport
1752 */
1753 BrowserPollConnection.forceAllow = function () {
1754 BrowserPollConnection.forceAllow_ = true;
1755 };
1756 /**
1757 * Forces longpolling to not be considered as a potential transport
1758 */
1759 BrowserPollConnection.forceDisallow = function () {
1760 BrowserPollConnection.forceDisallow_ = true;
1761 };
1762 // Static method, use string literal so it can be accessed in a generic way
1763 BrowserPollConnection.isAvailable = function () {
1764 if (util.isNodeSdk()) {
1765 return false;
1766 }
1767 else if (BrowserPollConnection.forceAllow_) {
1768 return true;
1769 }
1770 else {
1771 // NOTE: In React-Native there's normally no 'document', but if you debug a React-Native app in
1772 // the Chrome debugger, 'document' is defined, but document.createElement is null (2015/06/08).
1773 return (!BrowserPollConnection.forceDisallow_ &&
1774 typeof document !== 'undefined' &&
1775 document.createElement != null &&
1776 !isChromeExtensionContentScript() &&
1777 !isWindowsStoreApp());
1778 }
1779 };
1780 /**
1781 * No-op for polling
1782 */
1783 BrowserPollConnection.prototype.markConnectionHealthy = function () { };
1784 /**
1785 * Stops polling and cleans up the iframe
1786 */
1787 BrowserPollConnection.prototype.shutdown_ = function () {
1788 this.isClosed_ = true;
1789 if (this.scriptTagHolder) {
1790 this.scriptTagHolder.close();
1791 this.scriptTagHolder = null;
1792 }
1793 //remove the disconnect frame, which will trigger an XHR call to the server to tell it we're leaving.
1794 if (this.myDisconnFrame) {
1795 document.body.removeChild(this.myDisconnFrame);
1796 this.myDisconnFrame = null;
1797 }
1798 if (this.connectTimeoutTimer_) {
1799 clearTimeout(this.connectTimeoutTimer_);
1800 this.connectTimeoutTimer_ = null;
1801 }
1802 };
1803 /**
1804 * Triggered when this transport is closed
1805 */
1806 BrowserPollConnection.prototype.onClosed_ = function () {
1807 if (!this.isClosed_) {
1808 this.log_('Longpoll is closing itself');
1809 this.shutdown_();
1810 if (this.onDisconnect_) {
1811 this.onDisconnect_(this.everConnected_);
1812 this.onDisconnect_ = null;
1813 }
1814 }
1815 };
1816 /**
1817 * External-facing close handler. RealTime has requested we shut down. Kill our connection and tell the server
1818 * that we've left.
1819 */
1820 BrowserPollConnection.prototype.close = function () {
1821 if (!this.isClosed_) {
1822 this.log_('Longpoll is being closed.');
1823 this.shutdown_();
1824 }
1825 };
1826 /**
1827 * Send the JSON object down to the server. It will need to be stringified, base64 encoded, and then
1828 * broken into chunks (since URLs have a small maximum length).
1829 * @param data - The JSON data to transmit.
1830 */
1831 BrowserPollConnection.prototype.send = function (data) {
1832 var dataStr = util.stringify(data);
1833 this.bytesSent += dataStr.length;
1834 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1835 //first, lets get the base64-encoded data
1836 var base64data = util.base64Encode(dataStr);
1837 //We can only fit a certain amount in each URL, so we need to split this request
1838 //up into multiple pieces if it doesn't fit in one request.
1839 var dataSegs = splitStringBySize(base64data, MAX_PAYLOAD_SIZE);
1840 //Enqueue each segment for transmission. We assign each chunk a sequential ID and a total number
1841 //of segments so that we can reassemble the packet on the server.
1842 for (var i = 0; i < dataSegs.length; i++) {
1843 this.scriptTagHolder.enqueueSegment(this.curSegmentNum, dataSegs.length, dataSegs[i]);
1844 this.curSegmentNum++;
1845 }
1846 };
1847 /**
1848 * This is how we notify the server that we're leaving.
1849 * We aren't able to send requests with DHTML on a window close event, but we can
1850 * trigger XHR requests in some browsers (everything but Opera basically).
1851 */
1852 BrowserPollConnection.prototype.addDisconnectPingFrame = function (id, pw) {
1853 if (util.isNodeSdk()) {
1854 return;
1855 }
1856 this.myDisconnFrame = document.createElement('iframe');
1857 var urlParams = {};
1858 urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
1859 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = id;
1860 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = pw;
1861 this.myDisconnFrame.src = this.urlFn(urlParams);
1862 this.myDisconnFrame.style.display = 'none';
1863 document.body.appendChild(this.myDisconnFrame);
1864 };
1865 /**
1866 * Used to track the bytes received by this client
1867 */
1868 BrowserPollConnection.prototype.incrementIncomingBytes_ = function (args) {
1869 // TODO: This is an annoying perf hit just to track the number of incoming bytes. Maybe it should be opt-in.
1870 var bytesReceived = util.stringify(args).length;
1871 this.bytesReceived += bytesReceived;
1872 this.stats_.incrementCounter('bytes_received', bytesReceived);
1873 };
1874 return BrowserPollConnection;
1875}());
1876/*********************************************************************************************
1877 * A wrapper around an iframe that is used as a long-polling script holder.
1878 *********************************************************************************************/
1879var FirebaseIFrameScriptHolder = /** @class */ (function () {
1880 /**
1881 * @param commandCB - The callback to be called when control commands are recevied from the server.
1882 * @param onMessageCB - The callback to be triggered when responses arrive from the server.
1883 * @param onDisconnect - The callback to be triggered when this tag holder is closed
1884 * @param urlFn - A function that provides the URL of the endpoint to send data to.
1885 */
1886 function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnect, urlFn) {
1887 this.onDisconnect = onDisconnect;
1888 this.urlFn = urlFn;
1889 //We maintain a count of all of the outstanding requests, because if we have too many active at once it can cause
1890 //problems in some browsers.
1891 this.outstandingRequests = new Set();
1892 //A queue of the pending segments waiting for transmission to the server.
1893 this.pendingSegs = [];
1894 //A serial number. We use this for two things:
1895 // 1) A way to ensure the browser doesn't cache responses to polls
1896 // 2) A way to make the server aware when long-polls arrive in a different order than we started them. The
1897 // server needs to release both polls in this case or it will cause problems in Opera since Opera can only execute
1898 // JSONP code in the order it was added to the iframe.
1899 this.currentSerial = Math.floor(Math.random() * 100000000);
1900 // This gets set to false when we're "closing down" the connection (e.g. we're switching transports but there's still
1901 // incoming data from the server that we're waiting for).
1902 this.sendNewPolls = true;
1903 if (!util.isNodeSdk()) {
1904 //Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
1905 //iframes where we put the long-polling script tags. We have two callbacks:
1906 // 1) Command Callback - Triggered for control issues, like starting a connection.
1907 // 2) Message Callback - Triggered when new data arrives.
1908 this.uniqueCallbackIdentifier = LUIDGenerator();
1909 window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
1910 window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] =
1911 onMessageCB;
1912 //Create an iframe for us to add script tags to.
1913 this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_();
1914 // Set the iframe's contents.
1915 var script = '';
1916 // if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
1917 // for ie9, but ie8 needs to do it again in the document itself.
1918 if (this.myIFrame.src &&
1919 this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
1920 var currentDomain = document.domain;
1921 script = '<script>document.domain="' + currentDomain + '";</script>';
1922 }
1923 var iframeContents = '<html><body>' + script + '</body></html>';
1924 try {
1925 this.myIFrame.doc.open();
1926 this.myIFrame.doc.write(iframeContents);
1927 this.myIFrame.doc.close();
1928 }
1929 catch (e) {
1930 log('frame writing exception');
1931 if (e.stack) {
1932 log(e.stack);
1933 }
1934 log(e);
1935 }
1936 }
1937 else {
1938 this.commandCB = commandCB;
1939 this.onMessageCB = onMessageCB;
1940 }
1941 }
1942 /**
1943 * Each browser has its own funny way to handle iframes. Here we mush them all together into one object that I can
1944 * actually use.
1945 */
1946 FirebaseIFrameScriptHolder.createIFrame_ = function () {
1947 var iframe = document.createElement('iframe');
1948 iframe.style.display = 'none';
1949 // This is necessary in order to initialize the document inside the iframe
1950 if (document.body) {
1951 document.body.appendChild(iframe);
1952 try {
1953 // If document.domain has been modified in IE, this will throw an error, and we need to set the
1954 // domain of the iframe's document manually. We can do this via a javascript: url as the src attribute
1955 // Also note that we must do this *after* the iframe has been appended to the page. Otherwise it doesn't work.
1956 var a = iframe.contentWindow.document;
1957 if (!a) {
1958 // Apologies for the log-spam, I need to do something to keep closure from optimizing out the assignment above.
1959 log('No IE domain setting required');
1960 }
1961 }
1962 catch (e) {
1963 var domain = document.domain;
1964 iframe.src =
1965 "javascript:void((function(){document.open();document.domain='" +
1966 domain +
1967 "';document.close();})())";
1968 }
1969 }
1970 else {
1971 // LongPollConnection attempts to delay initialization until the document is ready, so hopefully this
1972 // never gets hit.
1973 throw 'Document body has not initialized. Wait to initialize Firebase until after the document is ready.';
1974 }
1975 // Get the document of the iframe in a browser-specific way.
1976 if (iframe.contentDocument) {
1977 iframe.doc = iframe.contentDocument; // Firefox, Opera, Safari
1978 }
1979 else if (iframe.contentWindow) {
1980 iframe.doc = iframe.contentWindow.document; // Internet Explorer
1981 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1982 }
1983 else if (iframe.document) {
1984 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1985 iframe.doc = iframe.document; //others?
1986 }
1987 return iframe;
1988 };
1989 /**
1990 * Cancel all outstanding queries and remove the frame.
1991 */
1992 FirebaseIFrameScriptHolder.prototype.close = function () {
1993 var _this = this;
1994 //Mark this iframe as dead, so no new requests are sent.
1995 this.alive = false;
1996 if (this.myIFrame) {
1997 //We have to actually remove all of the html inside this iframe before removing it from the
1998 //window, or IE will continue loading and executing the script tags we've already added, which
1999 //can lead to some errors being thrown. Setting innerHTML seems to be the easiest way to do this.
2000 this.myIFrame.doc.body.innerHTML = '';
2001 setTimeout(function () {
2002 if (_this.myIFrame !== null) {
2003 document.body.removeChild(_this.myIFrame);
2004 _this.myIFrame = null;
2005 }
2006 }, Math.floor(0));
2007 }
2008 // Protect from being called recursively.
2009 var onDisconnect = this.onDisconnect;
2010 if (onDisconnect) {
2011 this.onDisconnect = null;
2012 onDisconnect();
2013 }
2014 };
2015 /**
2016 * Actually start the long-polling session by adding the first script tag(s) to the iframe.
2017 * @param id - The ID of this connection
2018 * @param pw - The password for this connection
2019 */
2020 FirebaseIFrameScriptHolder.prototype.startLongPoll = function (id, pw) {
2021 this.myID = id;
2022 this.myPW = pw;
2023 this.alive = true;
2024 //send the initial request. If there are requests queued, make sure that we transmit as many as we are currently able to.
2025 while (this.newRequest_()) { }
2026 };
2027 /**
2028 * This is called any time someone might want a script tag to be added. It adds a script tag when there aren't
2029 * too many outstanding requests and we are still alive.
2030 *
2031 * If there are outstanding packet segments to send, it sends one. If there aren't, it sends a long-poll anyways if
2032 * needed.
2033 */
2034 FirebaseIFrameScriptHolder.prototype.newRequest_ = function () {
2035 // We keep one outstanding request open all the time to receive data, but if we need to send data
2036 // (pendingSegs.length > 0) then we create a new request to send the data. The server will automatically
2037 // close the old request.
2038 if (this.alive &&
2039 this.sendNewPolls &&
2040 this.outstandingRequests.size < (this.pendingSegs.length > 0 ? 2 : 1)) {
2041 //construct our url
2042 this.currentSerial++;
2043 var urlParams = {};
2044 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
2045 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
2046 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = this.currentSerial;
2047 var theURL = this.urlFn(urlParams);
2048 //Now add as much data as we can.
2049 var curDataString = '';
2050 var i = 0;
2051 while (this.pendingSegs.length > 0) {
2052 //first, lets see if the next segment will fit.
2053 var nextSeg = this.pendingSegs[0];
2054 if (nextSeg.d.length +
2055 SEG_HEADER_SIZE +
2056 curDataString.length <=
2057 MAX_URL_DATA_SIZE) {
2058 //great, the segment will fit. Lets append it.
2059 var theSeg = this.pendingSegs.shift();
2060 curDataString =
2061 curDataString +
2062 '&' +
2063 FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM +
2064 i +
2065 '=' +
2066 theSeg.seg +
2067 '&' +
2068 FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET +
2069 i +
2070 '=' +
2071 theSeg.ts +
2072 '&' +
2073 FIREBASE_LONGPOLL_DATA_PARAM +
2074 i +
2075 '=' +
2076 theSeg.d;
2077 i++;
2078 }
2079 else {
2080 break;
2081 }
2082 }
2083 theURL = theURL + curDataString;
2084 this.addLongPollTag_(theURL, this.currentSerial);
2085 return true;
2086 }
2087 else {
2088 return false;
2089 }
2090 };
2091 /**
2092 * Queue a packet for transmission to the server.
2093 * @param segnum - A sequential id for this packet segment used for reassembly
2094 * @param totalsegs - The total number of segments in this packet
2095 * @param data - The data for this segment.
2096 */
2097 FirebaseIFrameScriptHolder.prototype.enqueueSegment = function (segnum, totalsegs, data) {
2098 //add this to the queue of segments to send.
2099 this.pendingSegs.push({ seg: segnum, ts: totalsegs, d: data });
2100 //send the data immediately if there isn't already data being transmitted, unless
2101 //startLongPoll hasn't been called yet.
2102 if (this.alive) {
2103 this.newRequest_();
2104 }
2105 };
2106 /**
2107 * Add a script tag for a regular long-poll request.
2108 * @param url - The URL of the script tag.
2109 * @param serial - The serial number of the request.
2110 */
2111 FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function (url, serial) {
2112 var _this = this;
2113 //remember that we sent this request.
2114 this.outstandingRequests.add(serial);
2115 var doNewRequest = function () {
2116 _this.outstandingRequests.delete(serial);
2117 _this.newRequest_();
2118 };
2119 // If this request doesn't return on its own accord (by the server sending us some data), we'll
2120 // create a new one after the KEEPALIVE interval to make sure we always keep a fresh request open.
2121 var keepaliveTimeout = setTimeout(doNewRequest, Math.floor(KEEPALIVE_REQUEST_INTERVAL));
2122 var readyStateCB = function () {
2123 // Request completed. Cancel the keepalive.
2124 clearTimeout(keepaliveTimeout);
2125 // Trigger a new request so we can continue receiving data.
2126 doNewRequest();
2127 };
2128 this.addTag(url, readyStateCB);
2129 };
2130 /**
2131 * Add an arbitrary script tag to the iframe.
2132 * @param url - The URL for the script tag source.
2133 * @param loadCB - A callback to be triggered once the script has loaded.
2134 */
2135 FirebaseIFrameScriptHolder.prototype.addTag = function (url, loadCB) {
2136 var _this = this;
2137 if (util.isNodeSdk()) {
2138 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2139 this.doNodeLongPoll(url, loadCB);
2140 }
2141 else {
2142 setTimeout(function () {
2143 try {
2144 // if we're already closed, don't add this poll
2145 if (!_this.sendNewPolls) {
2146 return;
2147 }
2148 var newScript_1 = _this.myIFrame.doc.createElement('script');
2149 newScript_1.type = 'text/javascript';
2150 newScript_1.async = true;
2151 newScript_1.src = url;
2152 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2153 newScript_1.onload = newScript_1.onreadystatechange =
2154 function () {
2155 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2156 var rstate = newScript_1.readyState;
2157 if (!rstate || rstate === 'loaded' || rstate === 'complete') {
2158 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2159 newScript_1.onload = newScript_1.onreadystatechange = null;
2160 if (newScript_1.parentNode) {
2161 newScript_1.parentNode.removeChild(newScript_1);
2162 }
2163 loadCB();
2164 }
2165 };
2166 newScript_1.onerror = function () {
2167 log('Long-poll script failed to load: ' + url);
2168 _this.sendNewPolls = false;
2169 _this.close();
2170 };
2171 _this.myIFrame.doc.body.appendChild(newScript_1);
2172 }
2173 catch (e) {
2174 // TODO: we should make this error visible somehow
2175 }
2176 }, Math.floor(1));
2177 }
2178 };
2179 return FirebaseIFrameScriptHolder;
2180}());
2181
2182/**
2183 * @license
2184 * Copyright 2017 Google LLC
2185 *
2186 * Licensed under the Apache License, Version 2.0 (the "License");
2187 * you may not use this file except in compliance with the License.
2188 * You may obtain a copy of the License at
2189 *
2190 * http://www.apache.org/licenses/LICENSE-2.0
2191 *
2192 * Unless required by applicable law or agreed to in writing, software
2193 * distributed under the License is distributed on an "AS IS" BASIS,
2194 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2195 * See the License for the specific language governing permissions and
2196 * limitations under the License.
2197 */
2198/**
2199 * Currently simplistic, this class manages what transport a Connection should use at various stages of its
2200 * lifecycle.
2201 *
2202 * It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
2203 * they are available.
2204 */
2205var TransportManager = /** @class */ (function () {
2206 /**
2207 * @param repoInfo - Metadata around the namespace we're connecting to
2208 */
2209 function TransportManager(repoInfo) {
2210 this.initTransports_(repoInfo);
2211 }
2212 Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
2213 get: function () {
2214 return [BrowserPollConnection, WebSocketConnection];
2215 },
2216 enumerable: false,
2217 configurable: true
2218 });
2219 Object.defineProperty(TransportManager, "IS_TRANSPORT_INITIALIZED", {
2220 /**
2221 * Returns whether transport has been selected to ensure WebSocketConnection or BrowserPollConnection are not called after
2222 * TransportManager has already set up transports_
2223 */
2224 get: function () {
2225 return this.globalTransportInitialized_;
2226 },
2227 enumerable: false,
2228 configurable: true
2229 });
2230 TransportManager.prototype.initTransports_ = function (repoInfo) {
2231 var e_1, _a;
2232 var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
2233 var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
2234 if (repoInfo.webSocketOnly) {
2235 if (!isWebSocketsAvailable) {
2236 warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
2237 }
2238 isSkipPollConnection = true;
2239 }
2240 if (isSkipPollConnection) {
2241 this.transports_ = [WebSocketConnection];
2242 }
2243 else {
2244 var transports = (this.transports_ = []);
2245 try {
2246 for (var _b = tslib.__values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
2247 var transport = _c.value;
2248 if (transport && transport['isAvailable']()) {
2249 transports.push(transport);
2250 }
2251 }
2252 }
2253 catch (e_1_1) { e_1 = { error: e_1_1 }; }
2254 finally {
2255 try {
2256 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
2257 }
2258 finally { if (e_1) throw e_1.error; }
2259 }
2260 TransportManager.globalTransportInitialized_ = true;
2261 }
2262 };
2263 /**
2264 * @returns The constructor for the initial transport to use
2265 */
2266 TransportManager.prototype.initialTransport = function () {
2267 if (this.transports_.length > 0) {
2268 return this.transports_[0];
2269 }
2270 else {
2271 throw new Error('No transports available');
2272 }
2273 };
2274 /**
2275 * @returns The constructor for the next transport, or null
2276 */
2277 TransportManager.prototype.upgradeTransport = function () {
2278 if (this.transports_.length > 1) {
2279 return this.transports_[1];
2280 }
2281 else {
2282 return null;
2283 }
2284 };
2285 // Keeps track of whether the TransportManager has already chosen a transport to use
2286 TransportManager.globalTransportInitialized_ = false;
2287 return TransportManager;
2288}());
2289
2290/**
2291 * @license
2292 * Copyright 2017 Google LLC
2293 *
2294 * Licensed under the Apache License, Version 2.0 (the "License");
2295 * you may not use this file except in compliance with the License.
2296 * You may obtain a copy of the License at
2297 *
2298 * http://www.apache.org/licenses/LICENSE-2.0
2299 *
2300 * Unless required by applicable law or agreed to in writing, software
2301 * distributed under the License is distributed on an "AS IS" BASIS,
2302 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2303 * See the License for the specific language governing permissions and
2304 * limitations under the License.
2305 */
2306// Abort upgrade attempt if it takes longer than 60s.
2307var UPGRADE_TIMEOUT = 60000;
2308// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
2309// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
2310var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
2311// 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)
2312// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
2313// but we've sent/received enough bytes, we don't cancel the connection.
2314var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
2315var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
2316var MESSAGE_TYPE = 't';
2317var MESSAGE_DATA = 'd';
2318var CONTROL_SHUTDOWN = 's';
2319var CONTROL_RESET = 'r';
2320var CONTROL_ERROR = 'e';
2321var CONTROL_PONG = 'o';
2322var SWITCH_ACK = 'a';
2323var END_TRANSMISSION = 'n';
2324var PING = 'p';
2325var SERVER_HELLO = 'h';
2326/**
2327 * Creates a new real-time connection to the server using whichever method works
2328 * best in the current browser.
2329 */
2330var Connection = /** @class */ (function () {
2331 /**
2332 * @param id - an id for this connection
2333 * @param repoInfo_ - the info for the endpoint to connect to
2334 * @param applicationId_ - the Firebase App ID for this project
2335 * @param appCheckToken_ - The App Check Token for this device.
2336 * @param authToken_ - The auth token for this session.
2337 * @param onMessage_ - the callback to be triggered when a server-push message arrives
2338 * @param onReady_ - the callback to be triggered when this connection is ready to send messages.
2339 * @param onDisconnect_ - the callback to be triggered when a connection was lost
2340 * @param onKill_ - the callback to be triggered when this connection has permanently shut down.
2341 * @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
2342 */
2343 function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
2344 this.id = id;
2345 this.repoInfo_ = repoInfo_;
2346 this.applicationId_ = applicationId_;
2347 this.appCheckToken_ = appCheckToken_;
2348 this.authToken_ = authToken_;
2349 this.onMessage_ = onMessage_;
2350 this.onReady_ = onReady_;
2351 this.onDisconnect_ = onDisconnect_;
2352 this.onKill_ = onKill_;
2353 this.lastSessionId = lastSessionId;
2354 this.connectionCount = 0;
2355 this.pendingDataMessages = [];
2356 this.state_ = 0 /* CONNECTING */;
2357 this.log_ = logWrapper('c:' + this.id + ':');
2358 this.transportManager_ = new TransportManager(repoInfo_);
2359 this.log_('Connection created');
2360 this.start_();
2361 }
2362 /**
2363 * Starts a connection attempt
2364 */
2365 Connection.prototype.start_ = function () {
2366 var _this = this;
2367 var conn = this.transportManager_.initialTransport();
2368 this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, null, this.lastSessionId);
2369 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2370 // can consider the transport healthy.
2371 this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
2372 var onMessageReceived = this.connReceiver_(this.conn_);
2373 var onConnectionLost = this.disconnReceiver_(this.conn_);
2374 this.tx_ = this.conn_;
2375 this.rx_ = this.conn_;
2376 this.secondaryConn_ = null;
2377 this.isHealthy_ = false;
2378 /*
2379 * Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
2380 * This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
2381 * Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
2382 * still have the context of your originating frame.
2383 */
2384 setTimeout(function () {
2385 // this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
2386 _this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
2387 }, Math.floor(0));
2388 var healthyTimeoutMS = conn['healthyTimeout'] || 0;
2389 if (healthyTimeoutMS > 0) {
2390 this.healthyTimeout_ = setTimeoutNonBlocking(function () {
2391 _this.healthyTimeout_ = null;
2392 if (!_this.isHealthy_) {
2393 if (_this.conn_ &&
2394 _this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
2395 _this.log_('Connection exceeded healthy timeout but has received ' +
2396 _this.conn_.bytesReceived +
2397 ' bytes. Marking connection healthy.');
2398 _this.isHealthy_ = true;
2399 _this.conn_.markConnectionHealthy();
2400 }
2401 else if (_this.conn_ &&
2402 _this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
2403 _this.log_('Connection exceeded healthy timeout but has sent ' +
2404 _this.conn_.bytesSent +
2405 ' bytes. Leaving connection alive.');
2406 // NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
2407 // the server.
2408 }
2409 else {
2410 _this.log_('Closing unhealthy connection after timeout.');
2411 _this.close();
2412 }
2413 }
2414 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2415 }, Math.floor(healthyTimeoutMS));
2416 }
2417 };
2418 Connection.prototype.nextTransportId_ = function () {
2419 return 'c:' + this.id + ':' + this.connectionCount++;
2420 };
2421 Connection.prototype.disconnReceiver_ = function (conn) {
2422 var _this = this;
2423 return function (everConnected) {
2424 if (conn === _this.conn_) {
2425 _this.onConnectionLost_(everConnected);
2426 }
2427 else if (conn === _this.secondaryConn_) {
2428 _this.log_('Secondary connection lost.');
2429 _this.onSecondaryConnectionLost_();
2430 }
2431 else {
2432 _this.log_('closing an old connection');
2433 }
2434 };
2435 };
2436 Connection.prototype.connReceiver_ = function (conn) {
2437 var _this = this;
2438 return function (message) {
2439 if (_this.state_ !== 2 /* DISCONNECTED */) {
2440 if (conn === _this.rx_) {
2441 _this.onPrimaryMessageReceived_(message);
2442 }
2443 else if (conn === _this.secondaryConn_) {
2444 _this.onSecondaryMessageReceived_(message);
2445 }
2446 else {
2447 _this.log_('message on old connection');
2448 }
2449 }
2450 };
2451 };
2452 /**
2453 * @param dataMsg - An arbitrary data message to be sent to the server
2454 */
2455 Connection.prototype.sendRequest = function (dataMsg) {
2456 // wrap in a data message envelope and send it on
2457 var msg = { t: 'd', d: dataMsg };
2458 this.sendData_(msg);
2459 };
2460 Connection.prototype.tryCleanupConnection = function () {
2461 if (this.tx_ === this.secondaryConn_ && this.rx_ === this.secondaryConn_) {
2462 this.log_('cleaning up and promoting a connection: ' + this.secondaryConn_.connId);
2463 this.conn_ = this.secondaryConn_;
2464 this.secondaryConn_ = null;
2465 // the server will shutdown the old connection
2466 }
2467 };
2468 Connection.prototype.onSecondaryControl_ = function (controlData) {
2469 if (MESSAGE_TYPE in controlData) {
2470 var cmd = controlData[MESSAGE_TYPE];
2471 if (cmd === SWITCH_ACK) {
2472 this.upgradeIfSecondaryHealthy_();
2473 }
2474 else if (cmd === CONTROL_RESET) {
2475 // Most likely the session wasn't valid. Abandon the switch attempt
2476 this.log_('Got a reset on secondary, closing it');
2477 this.secondaryConn_.close();
2478 // If we were already using this connection for something, than we need to fully close
2479 if (this.tx_ === this.secondaryConn_ ||
2480 this.rx_ === this.secondaryConn_) {
2481 this.close();
2482 }
2483 }
2484 else if (cmd === CONTROL_PONG) {
2485 this.log_('got pong on secondary.');
2486 this.secondaryResponsesRequired_--;
2487 this.upgradeIfSecondaryHealthy_();
2488 }
2489 }
2490 };
2491 Connection.prototype.onSecondaryMessageReceived_ = function (parsedData) {
2492 var layer = requireKey('t', parsedData);
2493 var data = requireKey('d', parsedData);
2494 if (layer === 'c') {
2495 this.onSecondaryControl_(data);
2496 }
2497 else if (layer === 'd') {
2498 // got a data message, but we're still second connection. Need to buffer it up
2499 this.pendingDataMessages.push(data);
2500 }
2501 else {
2502 throw new Error('Unknown protocol layer: ' + layer);
2503 }
2504 };
2505 Connection.prototype.upgradeIfSecondaryHealthy_ = function () {
2506 if (this.secondaryResponsesRequired_ <= 0) {
2507 this.log_('Secondary connection is healthy.');
2508 this.isHealthy_ = true;
2509 this.secondaryConn_.markConnectionHealthy();
2510 this.proceedWithUpgrade_();
2511 }
2512 else {
2513 // Send a ping to make sure the connection is healthy.
2514 this.log_('sending ping on secondary.');
2515 this.secondaryConn_.send({ t: 'c', d: { t: PING, d: {} } });
2516 }
2517 };
2518 Connection.prototype.proceedWithUpgrade_ = function () {
2519 // tell this connection to consider itself open
2520 this.secondaryConn_.start();
2521 // send ack
2522 this.log_('sending client ack on secondary');
2523 this.secondaryConn_.send({ t: 'c', d: { t: SWITCH_ACK, d: {} } });
2524 // send end packet on primary transport, switch to sending on this one
2525 // can receive on this one, buffer responses until end received on primary transport
2526 this.log_('Ending transmission on primary');
2527 this.conn_.send({ t: 'c', d: { t: END_TRANSMISSION, d: {} } });
2528 this.tx_ = this.secondaryConn_;
2529 this.tryCleanupConnection();
2530 };
2531 Connection.prototype.onPrimaryMessageReceived_ = function (parsedData) {
2532 // Must refer to parsedData properties in quotes, so closure doesn't touch them.
2533 var layer = requireKey('t', parsedData);
2534 var data = requireKey('d', parsedData);
2535 if (layer === 'c') {
2536 this.onControl_(data);
2537 }
2538 else if (layer === 'd') {
2539 this.onDataMessage_(data);
2540 }
2541 };
2542 Connection.prototype.onDataMessage_ = function (message) {
2543 this.onPrimaryResponse_();
2544 // We don't do anything with data messages, just kick them up a level
2545 this.onMessage_(message);
2546 };
2547 Connection.prototype.onPrimaryResponse_ = function () {
2548 if (!this.isHealthy_) {
2549 this.primaryResponsesRequired_--;
2550 if (this.primaryResponsesRequired_ <= 0) {
2551 this.log_('Primary connection is healthy.');
2552 this.isHealthy_ = true;
2553 this.conn_.markConnectionHealthy();
2554 }
2555 }
2556 };
2557 Connection.prototype.onControl_ = function (controlData) {
2558 var cmd = requireKey(MESSAGE_TYPE, controlData);
2559 if (MESSAGE_DATA in controlData) {
2560 var payload = controlData[MESSAGE_DATA];
2561 if (cmd === SERVER_HELLO) {
2562 this.onHandshake_(payload);
2563 }
2564 else if (cmd === END_TRANSMISSION) {
2565 this.log_('recvd end transmission on primary');
2566 this.rx_ = this.secondaryConn_;
2567 for (var i = 0; i < this.pendingDataMessages.length; ++i) {
2568 this.onDataMessage_(this.pendingDataMessages[i]);
2569 }
2570 this.pendingDataMessages = [];
2571 this.tryCleanupConnection();
2572 }
2573 else if (cmd === CONTROL_SHUTDOWN) {
2574 // This was previously the 'onKill' callback passed to the lower-level connection
2575 // payload in this case is the reason for the shutdown. Generally a human-readable error
2576 this.onConnectionShutdown_(payload);
2577 }
2578 else if (cmd === CONTROL_RESET) {
2579 // payload in this case is the host we should contact
2580 this.onReset_(payload);
2581 }
2582 else if (cmd === CONTROL_ERROR) {
2583 error('Server Error: ' + payload);
2584 }
2585 else if (cmd === CONTROL_PONG) {
2586 this.log_('got pong on primary.');
2587 this.onPrimaryResponse_();
2588 this.sendPingOnPrimaryIfNecessary_();
2589 }
2590 else {
2591 error('Unknown control packet command: ' + cmd);
2592 }
2593 }
2594 };
2595 /**
2596 * @param handshake - The handshake data returned from the server
2597 */
2598 Connection.prototype.onHandshake_ = function (handshake) {
2599 var timestamp = handshake.ts;
2600 var version = handshake.v;
2601 var host = handshake.h;
2602 this.sessionId = handshake.s;
2603 this.repoInfo_.host = host;
2604 // if we've already closed the connection, then don't bother trying to progress further
2605 if (this.state_ === 0 /* CONNECTING */) {
2606 this.conn_.start();
2607 this.onConnectionEstablished_(this.conn_, timestamp);
2608 if (PROTOCOL_VERSION !== version) {
2609 warn('Protocol version mismatch detected');
2610 }
2611 // TODO: do we want to upgrade? when? maybe a delay?
2612 this.tryStartUpgrade_();
2613 }
2614 };
2615 Connection.prototype.tryStartUpgrade_ = function () {
2616 var conn = this.transportManager_.upgradeTransport();
2617 if (conn) {
2618 this.startUpgrade_(conn);
2619 }
2620 };
2621 Connection.prototype.startUpgrade_ = function (conn) {
2622 var _this = this;
2623 this.secondaryConn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, this.sessionId);
2624 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2625 // can consider the transport healthy.
2626 this.secondaryResponsesRequired_ =
2627 conn['responsesRequiredToBeHealthy'] || 0;
2628 var onMessage = this.connReceiver_(this.secondaryConn_);
2629 var onDisconnect = this.disconnReceiver_(this.secondaryConn_);
2630 this.secondaryConn_.open(onMessage, onDisconnect);
2631 // If we haven't successfully upgraded after UPGRADE_TIMEOUT, give up and kill the secondary.
2632 setTimeoutNonBlocking(function () {
2633 if (_this.secondaryConn_) {
2634 _this.log_('Timed out trying to upgrade.');
2635 _this.secondaryConn_.close();
2636 }
2637 }, Math.floor(UPGRADE_TIMEOUT));
2638 };
2639 Connection.prototype.onReset_ = function (host) {
2640 this.log_('Reset packet received. New host: ' + host);
2641 this.repoInfo_.host = host;
2642 // TODO: if we're already "connected", we need to trigger a disconnect at the next layer up.
2643 // We don't currently support resets after the connection has already been established
2644 if (this.state_ === 1 /* CONNECTED */) {
2645 this.close();
2646 }
2647 else {
2648 // Close whatever connections we have open and start again.
2649 this.closeConnections_();
2650 this.start_();
2651 }
2652 };
2653 Connection.prototype.onConnectionEstablished_ = function (conn, timestamp) {
2654 var _this = this;
2655 this.log_('Realtime connection established.');
2656 this.conn_ = conn;
2657 this.state_ = 1 /* CONNECTED */;
2658 if (this.onReady_) {
2659 this.onReady_(timestamp, this.sessionId);
2660 this.onReady_ = null;
2661 }
2662 // If after 5 seconds we haven't sent enough requests to the server to get the connection healthy,
2663 // send some pings.
2664 if (this.primaryResponsesRequired_ === 0) {
2665 this.log_('Primary connection is healthy.');
2666 this.isHealthy_ = true;
2667 }
2668 else {
2669 setTimeoutNonBlocking(function () {
2670 _this.sendPingOnPrimaryIfNecessary_();
2671 }, Math.floor(DELAY_BEFORE_SENDING_EXTRA_REQUESTS));
2672 }
2673 };
2674 Connection.prototype.sendPingOnPrimaryIfNecessary_ = function () {
2675 // If the connection isn't considered healthy yet, we'll send a noop ping packet request.
2676 if (!this.isHealthy_ && this.state_ === 1 /* CONNECTED */) {
2677 this.log_('sending ping on primary.');
2678 this.sendData_({ t: 'c', d: { t: PING, d: {} } });
2679 }
2680 };
2681 Connection.prototype.onSecondaryConnectionLost_ = function () {
2682 var conn = this.secondaryConn_;
2683 this.secondaryConn_ = null;
2684 if (this.tx_ === conn || this.rx_ === conn) {
2685 // we are relying on this connection already in some capacity. Therefore, a failure is real
2686 this.close();
2687 }
2688 };
2689 /**
2690 * @param everConnected - Whether or not the connection ever reached a server. Used to determine if
2691 * we should flush the host cache
2692 */
2693 Connection.prototype.onConnectionLost_ = function (everConnected) {
2694 this.conn_ = null;
2695 // NOTE: IF you're seeing a Firefox error for this line, I think it might be because it's getting
2696 // called on window close and RealtimeState.CONNECTING is no longer defined. Just a guess.
2697 if (!everConnected && this.state_ === 0 /* CONNECTING */) {
2698 this.log_('Realtime connection failed.');
2699 // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
2700 if (this.repoInfo_.isCacheableHost()) {
2701 PersistentStorage.remove('host:' + this.repoInfo_.host);
2702 // reset the internal host to what we would show the user, i.e. <ns>.firebaseio.com
2703 this.repoInfo_.internalHost = this.repoInfo_.host;
2704 }
2705 }
2706 else if (this.state_ === 1 /* CONNECTED */) {
2707 this.log_('Realtime connection lost.');
2708 }
2709 this.close();
2710 };
2711 Connection.prototype.onConnectionShutdown_ = function (reason) {
2712 this.log_('Connection shutdown command received. Shutting down...');
2713 if (this.onKill_) {
2714 this.onKill_(reason);
2715 this.onKill_ = null;
2716 }
2717 // We intentionally don't want to fire onDisconnect (kill is a different case),
2718 // so clear the callback.
2719 this.onDisconnect_ = null;
2720 this.close();
2721 };
2722 Connection.prototype.sendData_ = function (data) {
2723 if (this.state_ !== 1 /* CONNECTED */) {
2724 throw 'Connection is not connected';
2725 }
2726 else {
2727 this.tx_.send(data);
2728 }
2729 };
2730 /**
2731 * Cleans up this connection, calling the appropriate callbacks
2732 */
2733 Connection.prototype.close = function () {
2734 if (this.state_ !== 2 /* DISCONNECTED */) {
2735 this.log_('Closing realtime connection.');
2736 this.state_ = 2 /* DISCONNECTED */;
2737 this.closeConnections_();
2738 if (this.onDisconnect_) {
2739 this.onDisconnect_();
2740 this.onDisconnect_ = null;
2741 }
2742 }
2743 };
2744 Connection.prototype.closeConnections_ = function () {
2745 this.log_('Shutting down all connections');
2746 if (this.conn_) {
2747 this.conn_.close();
2748 this.conn_ = null;
2749 }
2750 if (this.secondaryConn_) {
2751 this.secondaryConn_.close();
2752 this.secondaryConn_ = null;
2753 }
2754 if (this.healthyTimeout_) {
2755 clearTimeout(this.healthyTimeout_);
2756 this.healthyTimeout_ = null;
2757 }
2758 };
2759 return Connection;
2760}());
2761
2762/**
2763 * @license
2764 * Copyright 2017 Google LLC
2765 *
2766 * Licensed under the Apache License, Version 2.0 (the "License");
2767 * you may not use this file except in compliance with the License.
2768 * You may obtain a copy of the License at
2769 *
2770 * http://www.apache.org/licenses/LICENSE-2.0
2771 *
2772 * Unless required by applicable law or agreed to in writing, software
2773 * distributed under the License is distributed on an "AS IS" BASIS,
2774 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2775 * See the License for the specific language governing permissions and
2776 * limitations under the License.
2777 */
2778/**
2779 * Interface defining the set of actions that can be performed against the Firebase server
2780 * (basically corresponds to our wire protocol).
2781 *
2782 * @interface
2783 */
2784var ServerActions = /** @class */ (function () {
2785 function ServerActions() {
2786 }
2787 ServerActions.prototype.put = function (pathString, data, onComplete, hash) { };
2788 ServerActions.prototype.merge = function (pathString, data, onComplete, hash) { };
2789 /**
2790 * Refreshes the auth token for the current connection.
2791 * @param token - The authentication token
2792 */
2793 ServerActions.prototype.refreshAuthToken = function (token) { };
2794 /**
2795 * Refreshes the app check token for the current connection.
2796 * @param token The app check token
2797 */
2798 ServerActions.prototype.refreshAppCheckToken = function (token) { };
2799 ServerActions.prototype.onDisconnectPut = function (pathString, data, onComplete) { };
2800 ServerActions.prototype.onDisconnectMerge = function (pathString, data, onComplete) { };
2801 ServerActions.prototype.onDisconnectCancel = function (pathString, onComplete) { };
2802 ServerActions.prototype.reportStats = function (stats) { };
2803 return ServerActions;
2804}());
2805
2806/**
2807 * @license
2808 * Copyright 2017 Google LLC
2809 *
2810 * Licensed under the Apache License, Version 2.0 (the "License");
2811 * you may not use this file except in compliance with the License.
2812 * You may obtain a copy of the License at
2813 *
2814 * http://www.apache.org/licenses/LICENSE-2.0
2815 *
2816 * Unless required by applicable law or agreed to in writing, software
2817 * distributed under the License is distributed on an "AS IS" BASIS,
2818 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2819 * See the License for the specific language governing permissions and
2820 * limitations under the License.
2821 */
2822/**
2823 * Base class to be used if you want to emit events. Call the constructor with
2824 * the set of allowed event names.
2825 */
2826var EventEmitter = /** @class */ (function () {
2827 function EventEmitter(allowedEvents_) {
2828 this.allowedEvents_ = allowedEvents_;
2829 this.listeners_ = {};
2830 util.assert(Array.isArray(allowedEvents_) && allowedEvents_.length > 0, 'Requires a non-empty array');
2831 }
2832 /**
2833 * To be called by derived classes to trigger events.
2834 */
2835 EventEmitter.prototype.trigger = function (eventType) {
2836 var varArgs = [];
2837 for (var _i = 1; _i < arguments.length; _i++) {
2838 varArgs[_i - 1] = arguments[_i];
2839 }
2840 if (Array.isArray(this.listeners_[eventType])) {
2841 // Clone the list, since callbacks could add/remove listeners.
2842 var listeners = tslib.__spreadArray([], tslib.__read(this.listeners_[eventType]));
2843 for (var i = 0; i < listeners.length; i++) {
2844 listeners[i].callback.apply(listeners[i].context, varArgs);
2845 }
2846 }
2847 };
2848 EventEmitter.prototype.on = function (eventType, callback, context) {
2849 this.validateEventType_(eventType);
2850 this.listeners_[eventType] = this.listeners_[eventType] || [];
2851 this.listeners_[eventType].push({ callback: callback, context: context });
2852 var eventData = this.getInitialEvent(eventType);
2853 if (eventData) {
2854 callback.apply(context, eventData);
2855 }
2856 };
2857 EventEmitter.prototype.off = function (eventType, callback, context) {
2858 this.validateEventType_(eventType);
2859 var listeners = this.listeners_[eventType] || [];
2860 for (var i = 0; i < listeners.length; i++) {
2861 if (listeners[i].callback === callback &&
2862 (!context || context === listeners[i].context)) {
2863 listeners.splice(i, 1);
2864 return;
2865 }
2866 }
2867 };
2868 EventEmitter.prototype.validateEventType_ = function (eventType) {
2869 util.assert(this.allowedEvents_.find(function (et) {
2870 return et === eventType;
2871 }), 'Unknown event: ' + eventType);
2872 };
2873 return EventEmitter;
2874}());
2875
2876/**
2877 * @license
2878 * Copyright 2017 Google LLC
2879 *
2880 * Licensed under the Apache License, Version 2.0 (the "License");
2881 * you may not use this file except in compliance with the License.
2882 * You may obtain a copy of the License at
2883 *
2884 * http://www.apache.org/licenses/LICENSE-2.0
2885 *
2886 * Unless required by applicable law or agreed to in writing, software
2887 * distributed under the License is distributed on an "AS IS" BASIS,
2888 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2889 * See the License for the specific language governing permissions and
2890 * limitations under the License.
2891 */
2892/**
2893 * Monitors online state (as reported by window.online/offline events).
2894 *
2895 * The expectation is that this could have many false positives (thinks we are online
2896 * when we're not), but no false negatives. So we can safely use it to determine when
2897 * we definitely cannot reach the internet.
2898 */
2899var OnlineMonitor = /** @class */ (function (_super) {
2900 tslib.__extends(OnlineMonitor, _super);
2901 function OnlineMonitor() {
2902 var _this = _super.call(this, ['online']) || this;
2903 _this.online_ = true;
2904 // We've had repeated complaints that Cordova apps can get stuck "offline", e.g.
2905 // https://forum.ionicframework.com/t/firebase-connection-is-lost-and-never-come-back/43810
2906 // It would seem that the 'online' event does not always fire consistently. So we disable it
2907 // for Cordova.
2908 if (typeof window !== 'undefined' &&
2909 typeof window.addEventListener !== 'undefined' &&
2910 !util.isMobileCordova()) {
2911 window.addEventListener('online', function () {
2912 if (!_this.online_) {
2913 _this.online_ = true;
2914 _this.trigger('online', true);
2915 }
2916 }, false);
2917 window.addEventListener('offline', function () {
2918 if (_this.online_) {
2919 _this.online_ = false;
2920 _this.trigger('online', false);
2921 }
2922 }, false);
2923 }
2924 return _this;
2925 }
2926 OnlineMonitor.getInstance = function () {
2927 return new OnlineMonitor();
2928 };
2929 OnlineMonitor.prototype.getInitialEvent = function (eventType) {
2930 util.assert(eventType === 'online', 'Unknown event type: ' + eventType);
2931 return [this.online_];
2932 };
2933 OnlineMonitor.prototype.currentlyOnline = function () {
2934 return this.online_;
2935 };
2936 return OnlineMonitor;
2937}(EventEmitter));
2938
2939/**
2940 * @license
2941 * Copyright 2017 Google LLC
2942 *
2943 * Licensed under the Apache License, Version 2.0 (the "License");
2944 * you may not use this file except in compliance with the License.
2945 * You may obtain a copy of the License at
2946 *
2947 * http://www.apache.org/licenses/LICENSE-2.0
2948 *
2949 * Unless required by applicable law or agreed to in writing, software
2950 * distributed under the License is distributed on an "AS IS" BASIS,
2951 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2952 * See the License for the specific language governing permissions and
2953 * limitations under the License.
2954 */
2955/** Maximum key depth. */
2956var MAX_PATH_DEPTH = 32;
2957/** Maximum number of (UTF8) bytes in a Firebase path. */
2958var MAX_PATH_LENGTH_BYTES = 768;
2959/**
2960 * An immutable object representing a parsed path. It's immutable so that you
2961 * can pass them around to other functions without worrying about them changing
2962 * it.
2963 */
2964var Path = /** @class */ (function () {
2965 /**
2966 * @param pathOrString - Path string to parse, or another path, or the raw
2967 * tokens array
2968 */
2969 function Path(pathOrString, pieceNum) {
2970 if (pieceNum === void 0) {
2971 this.pieces_ = pathOrString.split('/');
2972 // Remove empty pieces.
2973 var copyTo = 0;
2974 for (var i = 0; i < this.pieces_.length; i++) {
2975 if (this.pieces_[i].length > 0) {
2976 this.pieces_[copyTo] = this.pieces_[i];
2977 copyTo++;
2978 }
2979 }
2980 this.pieces_.length = copyTo;
2981 this.pieceNum_ = 0;
2982 }
2983 else {
2984 this.pieces_ = pathOrString;
2985 this.pieceNum_ = pieceNum;
2986 }
2987 }
2988 Path.prototype.toString = function () {
2989 var pathString = '';
2990 for (var i = this.pieceNum_; i < this.pieces_.length; i++) {
2991 if (this.pieces_[i] !== '') {
2992 pathString += '/' + this.pieces_[i];
2993 }
2994 }
2995 return pathString || '/';
2996 };
2997 return Path;
2998}());
2999function newEmptyPath() {
3000 return new Path('');
3001}
3002function pathGetFront(path) {
3003 if (path.pieceNum_ >= path.pieces_.length) {
3004 return null;
3005 }
3006 return path.pieces_[path.pieceNum_];
3007}
3008/**
3009 * @returns The number of segments in this path
3010 */
3011function pathGetLength(path) {
3012 return path.pieces_.length - path.pieceNum_;
3013}
3014function pathPopFront(path) {
3015 var pieceNum = path.pieceNum_;
3016 if (pieceNum < path.pieces_.length) {
3017 pieceNum++;
3018 }
3019 return new Path(path.pieces_, pieceNum);
3020}
3021function pathGetBack(path) {
3022 if (path.pieceNum_ < path.pieces_.length) {
3023 return path.pieces_[path.pieces_.length - 1];
3024 }
3025 return null;
3026}
3027function pathToUrlEncodedString(path) {
3028 var pathString = '';
3029 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3030 if (path.pieces_[i] !== '') {
3031 pathString += '/' + encodeURIComponent(String(path.pieces_[i]));
3032 }
3033 }
3034 return pathString || '/';
3035}
3036/**
3037 * Shallow copy of the parts of the path.
3038 *
3039 */
3040function pathSlice(path, begin) {
3041 if (begin === void 0) { begin = 0; }
3042 return path.pieces_.slice(path.pieceNum_ + begin);
3043}
3044function pathParent(path) {
3045 if (path.pieceNum_ >= path.pieces_.length) {
3046 return null;
3047 }
3048 var pieces = [];
3049 for (var i = path.pieceNum_; i < path.pieces_.length - 1; i++) {
3050 pieces.push(path.pieces_[i]);
3051 }
3052 return new Path(pieces, 0);
3053}
3054function pathChild(path, childPathObj) {
3055 var pieces = [];
3056 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3057 pieces.push(path.pieces_[i]);
3058 }
3059 if (childPathObj instanceof Path) {
3060 for (var i = childPathObj.pieceNum_; i < childPathObj.pieces_.length; i++) {
3061 pieces.push(childPathObj.pieces_[i]);
3062 }
3063 }
3064 else {
3065 var childPieces = childPathObj.split('/');
3066 for (var i = 0; i < childPieces.length; i++) {
3067 if (childPieces[i].length > 0) {
3068 pieces.push(childPieces[i]);
3069 }
3070 }
3071 }
3072 return new Path(pieces, 0);
3073}
3074/**
3075 * @returns True if there are no segments in this path
3076 */
3077function pathIsEmpty(path) {
3078 return path.pieceNum_ >= path.pieces_.length;
3079}
3080/**
3081 * @returns The path from outerPath to innerPath
3082 */
3083function newRelativePath(outerPath, innerPath) {
3084 var outer = pathGetFront(outerPath), inner = pathGetFront(innerPath);
3085 if (outer === null) {
3086 return innerPath;
3087 }
3088 else if (outer === inner) {
3089 return newRelativePath(pathPopFront(outerPath), pathPopFront(innerPath));
3090 }
3091 else {
3092 throw new Error('INTERNAL ERROR: innerPath (' +
3093 innerPath +
3094 ') is not within ' +
3095 'outerPath (' +
3096 outerPath +
3097 ')');
3098 }
3099}
3100/**
3101 * @returns -1, 0, 1 if left is less, equal, or greater than the right.
3102 */
3103function pathCompare(left, right) {
3104 var leftKeys = pathSlice(left, 0);
3105 var rightKeys = pathSlice(right, 0);
3106 for (var i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
3107 var cmp = nameCompare(leftKeys[i], rightKeys[i]);
3108 if (cmp !== 0) {
3109 return cmp;
3110 }
3111 }
3112 if (leftKeys.length === rightKeys.length) {
3113 return 0;
3114 }
3115 return leftKeys.length < rightKeys.length ? -1 : 1;
3116}
3117/**
3118 * @returns true if paths are the same.
3119 */
3120function pathEquals(path, other) {
3121 if (pathGetLength(path) !== pathGetLength(other)) {
3122 return false;
3123 }
3124 for (var i = path.pieceNum_, j = other.pieceNum_; i <= path.pieces_.length; i++, j++) {
3125 if (path.pieces_[i] !== other.pieces_[j]) {
3126 return false;
3127 }
3128 }
3129 return true;
3130}
3131/**
3132 * @returns True if this path is a parent of (or the same as) other
3133 */
3134function pathContains(path, other) {
3135 var i = path.pieceNum_;
3136 var j = other.pieceNum_;
3137 if (pathGetLength(path) > pathGetLength(other)) {
3138 return false;
3139 }
3140 while (i < path.pieces_.length) {
3141 if (path.pieces_[i] !== other.pieces_[j]) {
3142 return false;
3143 }
3144 ++i;
3145 ++j;
3146 }
3147 return true;
3148}
3149/**
3150 * Dynamic (mutable) path used to count path lengths.
3151 *
3152 * This class is used to efficiently check paths for valid
3153 * length (in UTF8 bytes) and depth (used in path validation).
3154 *
3155 * Throws Error exception if path is ever invalid.
3156 *
3157 * The definition of a path always begins with '/'.
3158 */
3159var ValidationPath = /** @class */ (function () {
3160 /**
3161 * @param path - Initial Path.
3162 * @param errorPrefix_ - Prefix for any error messages.
3163 */
3164 function ValidationPath(path, errorPrefix_) {
3165 this.errorPrefix_ = errorPrefix_;
3166 this.parts_ = pathSlice(path, 0);
3167 /** Initialize to number of '/' chars needed in path. */
3168 this.byteLength_ = Math.max(1, this.parts_.length);
3169 for (var i = 0; i < this.parts_.length; i++) {
3170 this.byteLength_ += util.stringLength(this.parts_[i]);
3171 }
3172 validationPathCheckValid(this);
3173 }
3174 return ValidationPath;
3175}());
3176function validationPathPush(validationPath, child) {
3177 // Count the needed '/'
3178 if (validationPath.parts_.length > 0) {
3179 validationPath.byteLength_ += 1;
3180 }
3181 validationPath.parts_.push(child);
3182 validationPath.byteLength_ += util.stringLength(child);
3183 validationPathCheckValid(validationPath);
3184}
3185function validationPathPop(validationPath) {
3186 var last = validationPath.parts_.pop();
3187 validationPath.byteLength_ -= util.stringLength(last);
3188 // Un-count the previous '/'
3189 if (validationPath.parts_.length > 0) {
3190 validationPath.byteLength_ -= 1;
3191 }
3192}
3193function validationPathCheckValid(validationPath) {
3194 if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
3195 throw new Error(validationPath.errorPrefix_ +
3196 'has a key path longer than ' +
3197 MAX_PATH_LENGTH_BYTES +
3198 ' bytes (' +
3199 validationPath.byteLength_ +
3200 ').');
3201 }
3202 if (validationPath.parts_.length > MAX_PATH_DEPTH) {
3203 throw new Error(validationPath.errorPrefix_ +
3204 'path specified exceeds the maximum depth that can be written (' +
3205 MAX_PATH_DEPTH +
3206 ') or object contains a cycle ' +
3207 validationPathToErrorString(validationPath));
3208 }
3209}
3210/**
3211 * String for use in error messages - uses '.' notation for path.
3212 */
3213function validationPathToErrorString(validationPath) {
3214 if (validationPath.parts_.length === 0) {
3215 return '';
3216 }
3217 return "in property '" + validationPath.parts_.join('.') + "'";
3218}
3219
3220/**
3221 * @license
3222 * Copyright 2017 Google LLC
3223 *
3224 * Licensed under the Apache License, Version 2.0 (the "License");
3225 * you may not use this file except in compliance with the License.
3226 * You may obtain a copy of the License at
3227 *
3228 * http://www.apache.org/licenses/LICENSE-2.0
3229 *
3230 * Unless required by applicable law or agreed to in writing, software
3231 * distributed under the License is distributed on an "AS IS" BASIS,
3232 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3233 * See the License for the specific language governing permissions and
3234 * limitations under the License.
3235 */
3236var VisibilityMonitor = /** @class */ (function (_super) {
3237 tslib.__extends(VisibilityMonitor, _super);
3238 function VisibilityMonitor() {
3239 var _this = _super.call(this, ['visible']) || this;
3240 var hidden;
3241 var visibilityChange;
3242 if (typeof document !== 'undefined' &&
3243 typeof document.addEventListener !== 'undefined') {
3244 if (typeof document['hidden'] !== 'undefined') {
3245 // Opera 12.10 and Firefox 18 and later support
3246 visibilityChange = 'visibilitychange';
3247 hidden = 'hidden';
3248 }
3249 else if (typeof document['mozHidden'] !== 'undefined') {
3250 visibilityChange = 'mozvisibilitychange';
3251 hidden = 'mozHidden';
3252 }
3253 else if (typeof document['msHidden'] !== 'undefined') {
3254 visibilityChange = 'msvisibilitychange';
3255 hidden = 'msHidden';
3256 }
3257 else if (typeof document['webkitHidden'] !== 'undefined') {
3258 visibilityChange = 'webkitvisibilitychange';
3259 hidden = 'webkitHidden';
3260 }
3261 }
3262 // Initially, we always assume we are visible. This ensures that in browsers
3263 // without page visibility support or in cases where we are never visible
3264 // (e.g. chrome extension), we act as if we are visible, i.e. don't delay
3265 // reconnects
3266 _this.visible_ = true;
3267 if (visibilityChange) {
3268 document.addEventListener(visibilityChange, function () {
3269 var visible = !document[hidden];
3270 if (visible !== _this.visible_) {
3271 _this.visible_ = visible;
3272 _this.trigger('visible', visible);
3273 }
3274 }, false);
3275 }
3276 return _this;
3277 }
3278 VisibilityMonitor.getInstance = function () {
3279 return new VisibilityMonitor();
3280 };
3281 VisibilityMonitor.prototype.getInitialEvent = function (eventType) {
3282 util.assert(eventType === 'visible', 'Unknown event type: ' + eventType);
3283 return [this.visible_];
3284 };
3285 return VisibilityMonitor;
3286}(EventEmitter));
3287
3288/**
3289 * @license
3290 * Copyright 2017 Google LLC
3291 *
3292 * Licensed under the Apache License, Version 2.0 (the "License");
3293 * you may not use this file except in compliance with the License.
3294 * You may obtain a copy of the License at
3295 *
3296 * http://www.apache.org/licenses/LICENSE-2.0
3297 *
3298 * Unless required by applicable law or agreed to in writing, software
3299 * distributed under the License is distributed on an "AS IS" BASIS,
3300 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3301 * See the License for the specific language governing permissions and
3302 * limitations under the License.
3303 */
3304var RECONNECT_MIN_DELAY = 1000;
3305var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
3306var GET_CONNECT_TIMEOUT = 3 * 1000;
3307var RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
3308var RECONNECT_DELAY_MULTIPLIER = 1.3;
3309var RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
3310var SERVER_KILL_INTERRUPT_REASON = 'server_kill';
3311// If auth fails repeatedly, we'll assume something is wrong and log a warning / back off.
3312var INVALID_TOKEN_THRESHOLD = 3;
3313/**
3314 * Firebase connection. Abstracts wire protocol and handles reconnecting.
3315 *
3316 * NOTE: All JSON objects sent to the realtime connection must have property names enclosed
3317 * in quotes to make sure the closure compiler does not minify them.
3318 */
3319var PersistentConnection = /** @class */ (function (_super) {
3320 tslib.__extends(PersistentConnection, _super);
3321 /**
3322 * @param repoInfo_ - Data about the namespace we are connecting to
3323 * @param applicationId_ - The Firebase App ID for this project
3324 * @param onDataUpdate_ - A callback for new data from the server
3325 */
3326 function PersistentConnection(repoInfo_, applicationId_, onDataUpdate_, onConnectStatus_, onServerInfoUpdate_, authTokenProvider_, appCheckTokenProvider_, authOverride_) {
3327 var _this = _super.call(this) || this;
3328 _this.repoInfo_ = repoInfo_;
3329 _this.applicationId_ = applicationId_;
3330 _this.onDataUpdate_ = onDataUpdate_;
3331 _this.onConnectStatus_ = onConnectStatus_;
3332 _this.onServerInfoUpdate_ = onServerInfoUpdate_;
3333 _this.authTokenProvider_ = authTokenProvider_;
3334 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
3335 _this.authOverride_ = authOverride_;
3336 // Used for diagnostic logging.
3337 _this.id = PersistentConnection.nextPersistentConnectionId_++;
3338 _this.log_ = logWrapper('p:' + _this.id + ':');
3339 _this.interruptReasons_ = {};
3340 _this.listens = new Map();
3341 _this.outstandingPuts_ = [];
3342 _this.outstandingGets_ = [];
3343 _this.outstandingPutCount_ = 0;
3344 _this.outstandingGetCount_ = 0;
3345 _this.onDisconnectRequestQueue_ = [];
3346 _this.connected_ = false;
3347 _this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3348 _this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_DEFAULT;
3349 _this.securityDebugCallback_ = null;
3350 _this.lastSessionId = null;
3351 _this.establishConnectionTimer_ = null;
3352 _this.visible_ = false;
3353 // Before we get connected, we keep a queue of pending messages to send.
3354 _this.requestCBHash_ = {};
3355 _this.requestNumber_ = 0;
3356 _this.realtime_ = null;
3357 _this.authToken_ = null;
3358 _this.appCheckToken_ = null;
3359 _this.forceTokenRefresh_ = false;
3360 _this.invalidAuthTokenCount_ = 0;
3361 _this.invalidAppCheckTokenCount_ = 0;
3362 _this.firstConnection_ = true;
3363 _this.lastConnectionAttemptTime_ = null;
3364 _this.lastConnectionEstablishedTime_ = null;
3365 if (authOverride_ && !util.isNodeSdk()) {
3366 throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
3367 }
3368 VisibilityMonitor.getInstance().on('visible', _this.onVisible_, _this);
3369 if (repoInfo_.host.indexOf('fblocal') === -1) {
3370 OnlineMonitor.getInstance().on('online', _this.onOnline_, _this);
3371 }
3372 return _this;
3373 }
3374 PersistentConnection.prototype.sendRequest = function (action, body, onResponse) {
3375 var curReqNum = ++this.requestNumber_;
3376 var msg = { r: curReqNum, a: action, b: body };
3377 this.log_(util.stringify(msg));
3378 util.assert(this.connected_, "sendRequest call when we're not connected not allowed.");
3379 this.realtime_.sendRequest(msg);
3380 if (onResponse) {
3381 this.requestCBHash_[curReqNum] = onResponse;
3382 }
3383 };
3384 PersistentConnection.prototype.get = function (query) {
3385 var _this = this;
3386 this.initConnection_();
3387 var deferred = new util.Deferred();
3388 var request = {
3389 p: query._path.toString(),
3390 q: query._queryObject
3391 };
3392 var outstandingGet = {
3393 action: 'g',
3394 request: request,
3395 onComplete: function (message) {
3396 var payload = message['d'];
3397 if (message['s'] === 'ok') {
3398 deferred.resolve(payload);
3399 }
3400 else {
3401 deferred.reject(payload);
3402 }
3403 }
3404 };
3405 this.outstandingGets_.push(outstandingGet);
3406 this.outstandingGetCount_++;
3407 var index = this.outstandingGets_.length - 1;
3408 if (!this.connected_) {
3409 setTimeout(function () {
3410 var get = _this.outstandingGets_[index];
3411 if (get === undefined || outstandingGet !== get) {
3412 return;
3413 }
3414 delete _this.outstandingGets_[index];
3415 _this.outstandingGetCount_--;
3416 if (_this.outstandingGetCount_ === 0) {
3417 _this.outstandingGets_ = [];
3418 }
3419 _this.log_('get ' + index + ' timed out on connection');
3420 deferred.reject(new Error('Client is offline.'));
3421 }, GET_CONNECT_TIMEOUT);
3422 }
3423 if (this.connected_) {
3424 this.sendGet_(index);
3425 }
3426 return deferred.promise;
3427 };
3428 PersistentConnection.prototype.listen = function (query, currentHashFn, tag, onComplete) {
3429 this.initConnection_();
3430 var queryId = query._queryIdentifier;
3431 var pathString = query._path.toString();
3432 this.log_('Listen called for ' + pathString + ' ' + queryId);
3433 if (!this.listens.has(pathString)) {
3434 this.listens.set(pathString, new Map());
3435 }
3436 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'listen() called for non-default but complete query');
3437 util.assert(!this.listens.get(pathString).has(queryId), "listen() called twice for same path/queryId.");
3438 var listenSpec = {
3439 onComplete: onComplete,
3440 hashFn: currentHashFn,
3441 query: query,
3442 tag: tag
3443 };
3444 this.listens.get(pathString).set(queryId, listenSpec);
3445 if (this.connected_) {
3446 this.sendListen_(listenSpec);
3447 }
3448 };
3449 PersistentConnection.prototype.sendGet_ = function (index) {
3450 var _this = this;
3451 var get = this.outstandingGets_[index];
3452 this.sendRequest('g', get.request, function (message) {
3453 delete _this.outstandingGets_[index];
3454 _this.outstandingGetCount_--;
3455 if (_this.outstandingGetCount_ === 0) {
3456 _this.outstandingGets_ = [];
3457 }
3458 if (get.onComplete) {
3459 get.onComplete(message);
3460 }
3461 });
3462 };
3463 PersistentConnection.prototype.sendListen_ = function (listenSpec) {
3464 var _this = this;
3465 var query = listenSpec.query;
3466 var pathString = query._path.toString();
3467 var queryId = query._queryIdentifier;
3468 this.log_('Listen on ' + pathString + ' for ' + queryId);
3469 var req = { /*path*/ p: pathString };
3470 var action = 'q';
3471 // Only bother to send query if it's non-default.
3472 if (listenSpec.tag) {
3473 req['q'] = query._queryObject;
3474 req['t'] = listenSpec.tag;
3475 }
3476 req[ /*hash*/'h'] = listenSpec.hashFn();
3477 this.sendRequest(action, req, function (message) {
3478 var payload = message[ /*data*/'d'];
3479 var status = message[ /*status*/'s'];
3480 // print warnings in any case...
3481 PersistentConnection.warnOnListenWarnings_(payload, query);
3482 var currentListenSpec = _this.listens.get(pathString) &&
3483 _this.listens.get(pathString).get(queryId);
3484 // only trigger actions if the listen hasn't been removed and readded
3485 if (currentListenSpec === listenSpec) {
3486 _this.log_('listen response', message);
3487 if (status !== 'ok') {
3488 _this.removeListen_(pathString, queryId);
3489 }
3490 if (listenSpec.onComplete) {
3491 listenSpec.onComplete(status, payload);
3492 }
3493 }
3494 });
3495 };
3496 PersistentConnection.warnOnListenWarnings_ = function (payload, query) {
3497 if (payload && typeof payload === 'object' && util.contains(payload, 'w')) {
3498 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3499 var warnings = util.safeGet(payload, 'w');
3500 if (Array.isArray(warnings) && ~warnings.indexOf('no_index')) {
3501 var indexSpec = '".indexOn": "' + query._queryParams.getIndex().toString() + '"';
3502 var indexPath = query._path.toString();
3503 warn("Using an unspecified index. Your data will be downloaded and " +
3504 ("filtered on the client. Consider adding " + indexSpec + " at ") +
3505 (indexPath + " to your security rules for better performance."));
3506 }
3507 }
3508 };
3509 PersistentConnection.prototype.refreshAuthToken = function (token) {
3510 this.authToken_ = token;
3511 this.log_('Auth token refreshed');
3512 if (this.authToken_) {
3513 this.tryAuth();
3514 }
3515 else {
3516 //If we're connected we want to let the server know to unauthenticate us. If we're not connected, simply delete
3517 //the credential so we dont become authenticated next time we connect.
3518 if (this.connected_) {
3519 this.sendRequest('unauth', {}, function () { });
3520 }
3521 }
3522 this.reduceReconnectDelayIfAdminCredential_(token);
3523 };
3524 PersistentConnection.prototype.reduceReconnectDelayIfAdminCredential_ = function (credential) {
3525 // NOTE: This isn't intended to be bulletproof (a malicious developer can always just modify the client).
3526 // Additionally, we don't bother resetting the max delay back to the default if auth fails / expires.
3527 var isFirebaseSecret = credential && credential.length === 40;
3528 if (isFirebaseSecret || util.isAdmin(credential)) {
3529 this.log_('Admin auth credential detected. Reducing max reconnect time.');
3530 this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
3531 }
3532 };
3533 PersistentConnection.prototype.refreshAppCheckToken = function (token) {
3534 this.appCheckToken_ = token;
3535 this.log_('App check token refreshed');
3536 if (this.appCheckToken_) {
3537 this.tryAppCheck();
3538 }
3539 else {
3540 //If we're connected we want to let the server know to unauthenticate us.
3541 //If we're not connected, simply delete the credential so we dont become
3542 // authenticated next time we connect.
3543 if (this.connected_) {
3544 this.sendRequest('unappeck', {}, function () { });
3545 }
3546 }
3547 };
3548 /**
3549 * Attempts to authenticate with the given credentials. If the authentication attempt fails, it's triggered like
3550 * a auth revoked (the connection is closed).
3551 */
3552 PersistentConnection.prototype.tryAuth = function () {
3553 var _this = this;
3554 if (this.connected_ && this.authToken_) {
3555 var token_1 = this.authToken_;
3556 var authMethod = util.isValidFormat(token_1) ? 'auth' : 'gauth';
3557 var requestData = { cred: token_1 };
3558 if (this.authOverride_ === null) {
3559 requestData['noauth'] = true;
3560 }
3561 else if (typeof this.authOverride_ === 'object') {
3562 requestData['authvar'] = this.authOverride_;
3563 }
3564 this.sendRequest(authMethod, requestData, function (res) {
3565 var status = res[ /*status*/'s'];
3566 var data = res[ /*data*/'d'] || 'error';
3567 if (_this.authToken_ === token_1) {
3568 if (status === 'ok') {
3569 _this.invalidAuthTokenCount_ = 0;
3570 }
3571 else {
3572 // Triggers reconnect and force refresh for auth token
3573 _this.onAuthRevoked_(status, data);
3574 }
3575 }
3576 });
3577 }
3578 };
3579 /**
3580 * Attempts to authenticate with the given token. If the authentication
3581 * attempt fails, it's triggered like the token was revoked (the connection is
3582 * closed).
3583 */
3584 PersistentConnection.prototype.tryAppCheck = function () {
3585 var _this = this;
3586 if (this.connected_ && this.appCheckToken_) {
3587 this.sendRequest('appcheck', { 'token': this.appCheckToken_ }, function (res) {
3588 var status = res[ /*status*/'s'];
3589 var data = res[ /*data*/'d'] || 'error';
3590 if (status === 'ok') {
3591 _this.invalidAppCheckTokenCount_ = 0;
3592 }
3593 else {
3594 _this.onAppCheckRevoked_(status, data);
3595 }
3596 });
3597 }
3598 };
3599 /**
3600 * @inheritDoc
3601 */
3602 PersistentConnection.prototype.unlisten = function (query, tag) {
3603 var pathString = query._path.toString();
3604 var queryId = query._queryIdentifier;
3605 this.log_('Unlisten called for ' + pathString + ' ' + queryId);
3606 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'unlisten() called for non-default but complete query');
3607 var listen = this.removeListen_(pathString, queryId);
3608 if (listen && this.connected_) {
3609 this.sendUnlisten_(pathString, queryId, query._queryObject, tag);
3610 }
3611 };
3612 PersistentConnection.prototype.sendUnlisten_ = function (pathString, queryId, queryObj, tag) {
3613 this.log_('Unlisten on ' + pathString + ' for ' + queryId);
3614 var req = { /*path*/ p: pathString };
3615 var action = 'n';
3616 // Only bother sending queryId if it's non-default.
3617 if (tag) {
3618 req['q'] = queryObj;
3619 req['t'] = tag;
3620 }
3621 this.sendRequest(action, req);
3622 };
3623 PersistentConnection.prototype.onDisconnectPut = function (pathString, data, onComplete) {
3624 this.initConnection_();
3625 if (this.connected_) {
3626 this.sendOnDisconnect_('o', pathString, data, onComplete);
3627 }
3628 else {
3629 this.onDisconnectRequestQueue_.push({
3630 pathString: pathString,
3631 action: 'o',
3632 data: data,
3633 onComplete: onComplete
3634 });
3635 }
3636 };
3637 PersistentConnection.prototype.onDisconnectMerge = function (pathString, data, onComplete) {
3638 this.initConnection_();
3639 if (this.connected_) {
3640 this.sendOnDisconnect_('om', pathString, data, onComplete);
3641 }
3642 else {
3643 this.onDisconnectRequestQueue_.push({
3644 pathString: pathString,
3645 action: 'om',
3646 data: data,
3647 onComplete: onComplete
3648 });
3649 }
3650 };
3651 PersistentConnection.prototype.onDisconnectCancel = function (pathString, onComplete) {
3652 this.initConnection_();
3653 if (this.connected_) {
3654 this.sendOnDisconnect_('oc', pathString, null, onComplete);
3655 }
3656 else {
3657 this.onDisconnectRequestQueue_.push({
3658 pathString: pathString,
3659 action: 'oc',
3660 data: null,
3661 onComplete: onComplete
3662 });
3663 }
3664 };
3665 PersistentConnection.prototype.sendOnDisconnect_ = function (action, pathString, data, onComplete) {
3666 var request = { /*path*/ p: pathString, /*data*/ d: data };
3667 this.log_('onDisconnect ' + action, request);
3668 this.sendRequest(action, request, function (response) {
3669 if (onComplete) {
3670 setTimeout(function () {
3671 onComplete(response[ /*status*/'s'], response[ /* data */'d']);
3672 }, Math.floor(0));
3673 }
3674 });
3675 };
3676 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
3677 this.putInternal('p', pathString, data, onComplete, hash);
3678 };
3679 PersistentConnection.prototype.merge = function (pathString, data, onComplete, hash) {
3680 this.putInternal('m', pathString, data, onComplete, hash);
3681 };
3682 PersistentConnection.prototype.putInternal = function (action, pathString, data, onComplete, hash) {
3683 this.initConnection_();
3684 var request = {
3685 /*path*/ p: pathString,
3686 /*data*/ d: data
3687 };
3688 if (hash !== undefined) {
3689 request[ /*hash*/'h'] = hash;
3690 }
3691 // TODO: Only keep track of the most recent put for a given path?
3692 this.outstandingPuts_.push({
3693 action: action,
3694 request: request,
3695 onComplete: onComplete
3696 });
3697 this.outstandingPutCount_++;
3698 var index = this.outstandingPuts_.length - 1;
3699 if (this.connected_) {
3700 this.sendPut_(index);
3701 }
3702 else {
3703 this.log_('Buffering put: ' + pathString);
3704 }
3705 };
3706 PersistentConnection.prototype.sendPut_ = function (index) {
3707 var _this = this;
3708 var action = this.outstandingPuts_[index].action;
3709 var request = this.outstandingPuts_[index].request;
3710 var onComplete = this.outstandingPuts_[index].onComplete;
3711 this.outstandingPuts_[index].queued = this.connected_;
3712 this.sendRequest(action, request, function (message) {
3713 _this.log_(action + ' response', message);
3714 delete _this.outstandingPuts_[index];
3715 _this.outstandingPutCount_--;
3716 // Clean up array occasionally.
3717 if (_this.outstandingPutCount_ === 0) {
3718 _this.outstandingPuts_ = [];
3719 }
3720 if (onComplete) {
3721 onComplete(message[ /*status*/'s'], message[ /* data */'d']);
3722 }
3723 });
3724 };
3725 PersistentConnection.prototype.reportStats = function (stats) {
3726 var _this = this;
3727 // If we're not connected, we just drop the stats.
3728 if (this.connected_) {
3729 var request = { /*counters*/ c: stats };
3730 this.log_('reportStats', request);
3731 this.sendRequest(/*stats*/ 's', request, function (result) {
3732 var status = result[ /*status*/'s'];
3733 if (status !== 'ok') {
3734 var errorReason = result[ /* data */'d'];
3735 _this.log_('reportStats', 'Error sending stats: ' + errorReason);
3736 }
3737 });
3738 }
3739 };
3740 PersistentConnection.prototype.onDataMessage_ = function (message) {
3741 if ('r' in message) {
3742 // this is a response
3743 this.log_('from server: ' + util.stringify(message));
3744 var reqNum = message['r'];
3745 var onResponse = this.requestCBHash_[reqNum];
3746 if (onResponse) {
3747 delete this.requestCBHash_[reqNum];
3748 onResponse(message[ /*body*/'b']);
3749 }
3750 }
3751 else if ('error' in message) {
3752 throw 'A server-side error has occurred: ' + message['error'];
3753 }
3754 else if ('a' in message) {
3755 // a and b are action and body, respectively
3756 this.onDataPush_(message['a'], message['b']);
3757 }
3758 };
3759 PersistentConnection.prototype.onDataPush_ = function (action, body) {
3760 this.log_('handleServerMessage', action, body);
3761 if (action === 'd') {
3762 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3763 /*isMerge*/ false, body['t']);
3764 }
3765 else if (action === 'm') {
3766 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3767 /*isMerge=*/ true, body['t']);
3768 }
3769 else if (action === 'c') {
3770 this.onListenRevoked_(body[ /*path*/'p'], body[ /*query*/'q']);
3771 }
3772 else if (action === 'ac') {
3773 this.onAuthRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3774 }
3775 else if (action === 'apc') {
3776 this.onAppCheckRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3777 }
3778 else if (action === 'sd') {
3779 this.onSecurityDebugPacket_(body);
3780 }
3781 else {
3782 error('Unrecognized action received from server: ' +
3783 util.stringify(action) +
3784 '\nAre you using the latest client?');
3785 }
3786 };
3787 PersistentConnection.prototype.onReady_ = function (timestamp, sessionId) {
3788 this.log_('connection ready');
3789 this.connected_ = true;
3790 this.lastConnectionEstablishedTime_ = new Date().getTime();
3791 this.handleTimestamp_(timestamp);
3792 this.lastSessionId = sessionId;
3793 if (this.firstConnection_) {
3794 this.sendConnectStats_();
3795 }
3796 this.restoreState_();
3797 this.firstConnection_ = false;
3798 this.onConnectStatus_(true);
3799 };
3800 PersistentConnection.prototype.scheduleConnect_ = function (timeout) {
3801 var _this = this;
3802 util.assert(!this.realtime_, "Scheduling a connect when we're already connected/ing?");
3803 if (this.establishConnectionTimer_) {
3804 clearTimeout(this.establishConnectionTimer_);
3805 }
3806 // NOTE: Even when timeout is 0, it's important to do a setTimeout to work around an infuriating "Security Error" in
3807 // Firefox when trying to write to our long-polling iframe in some scenarios (e.g. Forge or our unit tests).
3808 this.establishConnectionTimer_ = setTimeout(function () {
3809 _this.establishConnectionTimer_ = null;
3810 _this.establishConnection_();
3811 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3812 }, Math.floor(timeout));
3813 };
3814 PersistentConnection.prototype.initConnection_ = function () {
3815 if (!this.realtime_ && this.firstConnection_) {
3816 this.scheduleConnect_(0);
3817 }
3818 };
3819 PersistentConnection.prototype.onVisible_ = function (visible) {
3820 // NOTE: Tabbing away and back to a window will defeat our reconnect backoff, but I think that's fine.
3821 if (visible &&
3822 !this.visible_ &&
3823 this.reconnectDelay_ === this.maxReconnectDelay_) {
3824 this.log_('Window became visible. Reducing delay.');
3825 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3826 if (!this.realtime_) {
3827 this.scheduleConnect_(0);
3828 }
3829 }
3830 this.visible_ = visible;
3831 };
3832 PersistentConnection.prototype.onOnline_ = function (online) {
3833 if (online) {
3834 this.log_('Browser went online.');
3835 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3836 if (!this.realtime_) {
3837 this.scheduleConnect_(0);
3838 }
3839 }
3840 else {
3841 this.log_('Browser went offline. Killing connection.');
3842 if (this.realtime_) {
3843 this.realtime_.close();
3844 }
3845 }
3846 };
3847 PersistentConnection.prototype.onRealtimeDisconnect_ = function () {
3848 this.log_('data client disconnected');
3849 this.connected_ = false;
3850 this.realtime_ = null;
3851 // Since we don't know if our sent transactions succeeded or not, we need to cancel them.
3852 this.cancelSentTransactions_();
3853 // Clear out the pending requests.
3854 this.requestCBHash_ = {};
3855 if (this.shouldReconnect_()) {
3856 if (!this.visible_) {
3857 this.log_("Window isn't visible. Delaying reconnect.");
3858 this.reconnectDelay_ = this.maxReconnectDelay_;
3859 this.lastConnectionAttemptTime_ = new Date().getTime();
3860 }
3861 else if (this.lastConnectionEstablishedTime_) {
3862 // If we've been connected long enough, reset reconnect delay to minimum.
3863 var timeSinceLastConnectSucceeded = new Date().getTime() - this.lastConnectionEstablishedTime_;
3864 if (timeSinceLastConnectSucceeded > RECONNECT_DELAY_RESET_TIMEOUT) {
3865 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3866 }
3867 this.lastConnectionEstablishedTime_ = null;
3868 }
3869 var timeSinceLastConnectAttempt = new Date().getTime() - this.lastConnectionAttemptTime_;
3870 var reconnectDelay = Math.max(0, this.reconnectDelay_ - timeSinceLastConnectAttempt);
3871 reconnectDelay = Math.random() * reconnectDelay;
3872 this.log_('Trying to reconnect in ' + reconnectDelay + 'ms');
3873 this.scheduleConnect_(reconnectDelay);
3874 // Adjust reconnect delay for next time.
3875 this.reconnectDelay_ = Math.min(this.maxReconnectDelay_, this.reconnectDelay_ * RECONNECT_DELAY_MULTIPLIER);
3876 }
3877 this.onConnectStatus_(false);
3878 };
3879 PersistentConnection.prototype.establishConnection_ = function () {
3880 return tslib.__awaiter(this, void 0, void 0, function () {
3881 var onDataMessage, onReady, onDisconnect_1, connId, lastSessionId, canceled_1, connection_1, closeFn, sendRequestFn, forceRefresh, _a, authToken, appCheckToken, error_1;
3882 var _this = this;
3883 return tslib.__generator(this, function (_b) {
3884 switch (_b.label) {
3885 case 0:
3886 if (!this.shouldReconnect_()) return [3 /*break*/, 4];
3887 this.log_('Making a connection attempt');
3888 this.lastConnectionAttemptTime_ = new Date().getTime();
3889 this.lastConnectionEstablishedTime_ = null;
3890 onDataMessage = this.onDataMessage_.bind(this);
3891 onReady = this.onReady_.bind(this);
3892 onDisconnect_1 = this.onRealtimeDisconnect_.bind(this);
3893 connId = this.id + ':' + PersistentConnection.nextConnectionId_++;
3894 lastSessionId = this.lastSessionId;
3895 canceled_1 = false;
3896 connection_1 = null;
3897 closeFn = function () {
3898 if (connection_1) {
3899 connection_1.close();
3900 }
3901 else {
3902 canceled_1 = true;
3903 onDisconnect_1();
3904 }
3905 };
3906 sendRequestFn = function (msg) {
3907 util.assert(connection_1, "sendRequest call when we're not connected not allowed.");
3908 connection_1.sendRequest(msg);
3909 };
3910 this.realtime_ = {
3911 close: closeFn,
3912 sendRequest: sendRequestFn
3913 };
3914 forceRefresh = this.forceTokenRefresh_;
3915 this.forceTokenRefresh_ = false;
3916 _b.label = 1;
3917 case 1:
3918 _b.trys.push([1, 3, , 4]);
3919 return [4 /*yield*/, Promise.all([
3920 this.authTokenProvider_.getToken(forceRefresh),
3921 this.appCheckTokenProvider_.getToken(forceRefresh)
3922 ])];
3923 case 2:
3924 _a = tslib.__read.apply(void 0, [_b.sent(), 2]), authToken = _a[0], appCheckToken = _a[1];
3925 if (!canceled_1) {
3926 log('getToken() completed. Creating connection.');
3927 this.authToken_ = authToken && authToken.accessToken;
3928 this.appCheckToken_ = appCheckToken && appCheckToken.token;
3929 connection_1 = new Connection(connId, this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, onDataMessage, onReady, onDisconnect_1,
3930 /* onKill= */ function (reason) {
3931 warn(reason + ' (' + _this.repoInfo_.toString() + ')');
3932 _this.interrupt(SERVER_KILL_INTERRUPT_REASON);
3933 }, lastSessionId);
3934 }
3935 else {
3936 log('getToken() completed but was canceled');
3937 }
3938 return [3 /*break*/, 4];
3939 case 3:
3940 error_1 = _b.sent();
3941 this.log_('Failed to get token: ' + error_1);
3942 if (!canceled_1) {
3943 if (this.repoInfo_.nodeAdmin) {
3944 // This may be a critical error for the Admin Node.js SDK, so log a warning.
3945 // But getToken() may also just have temporarily failed, so we still want to
3946 // continue retrying.
3947 warn(error_1);
3948 }
3949 closeFn();
3950 }
3951 return [3 /*break*/, 4];
3952 case 4: return [2 /*return*/];
3953 }
3954 });
3955 });
3956 };
3957 PersistentConnection.prototype.interrupt = function (reason) {
3958 log('Interrupting connection for reason: ' + reason);
3959 this.interruptReasons_[reason] = true;
3960 if (this.realtime_) {
3961 this.realtime_.close();
3962 }
3963 else {
3964 if (this.establishConnectionTimer_) {
3965 clearTimeout(this.establishConnectionTimer_);
3966 this.establishConnectionTimer_ = null;
3967 }
3968 if (this.connected_) {
3969 this.onRealtimeDisconnect_();
3970 }
3971 }
3972 };
3973 PersistentConnection.prototype.resume = function (reason) {
3974 log('Resuming connection for reason: ' + reason);
3975 delete this.interruptReasons_[reason];
3976 if (util.isEmpty(this.interruptReasons_)) {
3977 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3978 if (!this.realtime_) {
3979 this.scheduleConnect_(0);
3980 }
3981 }
3982 };
3983 PersistentConnection.prototype.handleTimestamp_ = function (timestamp) {
3984 var delta = timestamp - new Date().getTime();
3985 this.onServerInfoUpdate_({ serverTimeOffset: delta });
3986 };
3987 PersistentConnection.prototype.cancelSentTransactions_ = function () {
3988 for (var i = 0; i < this.outstandingPuts_.length; i++) {
3989 var put = this.outstandingPuts_[i];
3990 if (put && /*hash*/ 'h' in put.request && put.queued) {
3991 if (put.onComplete) {
3992 put.onComplete('disconnect');
3993 }
3994 delete this.outstandingPuts_[i];
3995 this.outstandingPutCount_--;
3996 }
3997 }
3998 // Clean up array occasionally.
3999 if (this.outstandingPutCount_ === 0) {
4000 this.outstandingPuts_ = [];
4001 }
4002 };
4003 PersistentConnection.prototype.onListenRevoked_ = function (pathString, query) {
4004 // Remove the listen and manufacture a "permission_denied" error for the failed listen.
4005 var queryId;
4006 if (!query) {
4007 queryId = 'default';
4008 }
4009 else {
4010 queryId = query.map(function (q) { return ObjectToUniqueKey(q); }).join('$');
4011 }
4012 var listen = this.removeListen_(pathString, queryId);
4013 if (listen && listen.onComplete) {
4014 listen.onComplete('permission_denied');
4015 }
4016 };
4017 PersistentConnection.prototype.removeListen_ = function (pathString, queryId) {
4018 var normalizedPathString = new Path(pathString).toString(); // normalize path.
4019 var listen;
4020 if (this.listens.has(normalizedPathString)) {
4021 var map = this.listens.get(normalizedPathString);
4022 listen = map.get(queryId);
4023 map.delete(queryId);
4024 if (map.size === 0) {
4025 this.listens.delete(normalizedPathString);
4026 }
4027 }
4028 else {
4029 // all listens for this path has already been removed
4030 listen = undefined;
4031 }
4032 return listen;
4033 };
4034 PersistentConnection.prototype.onAuthRevoked_ = function (statusCode, explanation) {
4035 log('Auth token revoked: ' + statusCode + '/' + explanation);
4036 this.authToken_ = null;
4037 this.forceTokenRefresh_ = true;
4038 this.realtime_.close();
4039 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4040 // We'll wait a couple times before logging the warning / increasing the
4041 // retry period since oauth tokens will report as "invalid" if they're
4042 // just expired. Plus there may be transient issues that resolve themselves.
4043 this.invalidAuthTokenCount_++;
4044 if (this.invalidAuthTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4045 // Set a long reconnect delay because recovery is unlikely
4046 this.reconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
4047 // Notify the auth token provider that the token is invalid, which will log
4048 // a warning
4049 this.authTokenProvider_.notifyForInvalidToken();
4050 }
4051 }
4052 };
4053 PersistentConnection.prototype.onAppCheckRevoked_ = function (statusCode, explanation) {
4054 log('App check token revoked: ' + statusCode + '/' + explanation);
4055 this.appCheckToken_ = null;
4056 this.forceTokenRefresh_ = true;
4057 // Note: We don't close the connection as the developer may not have
4058 // enforcement enabled. The backend closes connections with enforcements.
4059 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4060 // We'll wait a couple times before logging the warning / increasing the
4061 // retry period since oauth tokens will report as "invalid" if they're
4062 // just expired. Plus there may be transient issues that resolve themselves.
4063 this.invalidAppCheckTokenCount_++;
4064 if (this.invalidAppCheckTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4065 this.appCheckTokenProvider_.notifyForInvalidToken();
4066 }
4067 }
4068 };
4069 PersistentConnection.prototype.onSecurityDebugPacket_ = function (body) {
4070 if (this.securityDebugCallback_) {
4071 this.securityDebugCallback_(body);
4072 }
4073 else {
4074 if ('msg' in body) {
4075 console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
4076 }
4077 }
4078 };
4079 PersistentConnection.prototype.restoreState_ = function () {
4080 var e_1, _a, e_2, _b;
4081 //Re-authenticate ourselves if we have a credential stored.
4082 this.tryAuth();
4083 this.tryAppCheck();
4084 try {
4085 // Puts depend on having received the corresponding data update from the server before they complete, so we must
4086 // make sure to send listens before puts.
4087 for (var _c = tslib.__values(this.listens.values()), _d = _c.next(); !_d.done; _d = _c.next()) {
4088 var queries = _d.value;
4089 try {
4090 for (var _e = (e_2 = void 0, tslib.__values(queries.values())), _f = _e.next(); !_f.done; _f = _e.next()) {
4091 var listenSpec = _f.value;
4092 this.sendListen_(listenSpec);
4093 }
4094 }
4095 catch (e_2_1) { e_2 = { error: e_2_1 }; }
4096 finally {
4097 try {
4098 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
4099 }
4100 finally { if (e_2) throw e_2.error; }
4101 }
4102 }
4103 }
4104 catch (e_1_1) { e_1 = { error: e_1_1 }; }
4105 finally {
4106 try {
4107 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
4108 }
4109 finally { if (e_1) throw e_1.error; }
4110 }
4111 for (var i = 0; i < this.outstandingPuts_.length; i++) {
4112 if (this.outstandingPuts_[i]) {
4113 this.sendPut_(i);
4114 }
4115 }
4116 while (this.onDisconnectRequestQueue_.length) {
4117 var request = this.onDisconnectRequestQueue_.shift();
4118 this.sendOnDisconnect_(request.action, request.pathString, request.data, request.onComplete);
4119 }
4120 for (var i = 0; i < this.outstandingGets_.length; i++) {
4121 if (this.outstandingGets_[i]) {
4122 this.sendGet_(i);
4123 }
4124 }
4125 };
4126 /**
4127 * Sends client stats for first connection
4128 */
4129 PersistentConnection.prototype.sendConnectStats_ = function () {
4130 var stats = {};
4131 var clientName = 'js';
4132 if (util.isNodeSdk()) {
4133 if (this.repoInfo_.nodeAdmin) {
4134 clientName = 'admin_node';
4135 }
4136 else {
4137 clientName = 'node';
4138 }
4139 }
4140 stats['sdk.' + clientName + '.' + SDK_VERSION.replace(/\./g, '-')] = 1;
4141 if (util.isMobileCordova()) {
4142 stats['framework.cordova'] = 1;
4143 }
4144 else if (util.isReactNative()) {
4145 stats['framework.reactnative'] = 1;
4146 }
4147 this.reportStats(stats);
4148 };
4149 PersistentConnection.prototype.shouldReconnect_ = function () {
4150 var online = OnlineMonitor.getInstance().currentlyOnline();
4151 return util.isEmpty(this.interruptReasons_) && online;
4152 };
4153 PersistentConnection.nextPersistentConnectionId_ = 0;
4154 /**
4155 * Counter for number of connections created. Mainly used for tagging in the logs
4156 */
4157 PersistentConnection.nextConnectionId_ = 0;
4158 return PersistentConnection;
4159}(ServerActions));
4160
4161/**
4162 * @license
4163 * Copyright 2017 Google LLC
4164 *
4165 * Licensed under the Apache License, Version 2.0 (the "License");
4166 * you may not use this file except in compliance with the License.
4167 * You may obtain a copy of the License at
4168 *
4169 * http://www.apache.org/licenses/LICENSE-2.0
4170 *
4171 * Unless required by applicable law or agreed to in writing, software
4172 * distributed under the License is distributed on an "AS IS" BASIS,
4173 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4174 * See the License for the specific language governing permissions and
4175 * limitations under the License.
4176 */
4177var NamedNode = /** @class */ (function () {
4178 function NamedNode(name, node) {
4179 this.name = name;
4180 this.node = node;
4181 }
4182 NamedNode.Wrap = function (name, node) {
4183 return new NamedNode(name, node);
4184 };
4185 return NamedNode;
4186}());
4187
4188/**
4189 * @license
4190 * Copyright 2017 Google LLC
4191 *
4192 * Licensed under the Apache License, Version 2.0 (the "License");
4193 * you may not use this file except in compliance with the License.
4194 * You may obtain a copy of the License at
4195 *
4196 * http://www.apache.org/licenses/LICENSE-2.0
4197 *
4198 * Unless required by applicable law or agreed to in writing, software
4199 * distributed under the License is distributed on an "AS IS" BASIS,
4200 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4201 * See the License for the specific language governing permissions and
4202 * limitations under the License.
4203 */
4204var Index = /** @class */ (function () {
4205 function Index() {
4206 }
4207 /**
4208 * @returns A standalone comparison function for
4209 * this index
4210 */
4211 Index.prototype.getCompare = function () {
4212 return this.compare.bind(this);
4213 };
4214 /**
4215 * Given a before and after value for a node, determine if the indexed value has changed. Even if they are different,
4216 * it's possible that the changes are isolated to parts of the snapshot that are not indexed.
4217 *
4218 *
4219 * @returns True if the portion of the snapshot being indexed changed between oldNode and newNode
4220 */
4221 Index.prototype.indexedValueChanged = function (oldNode, newNode) {
4222 var oldWrapped = new NamedNode(MIN_NAME, oldNode);
4223 var newWrapped = new NamedNode(MIN_NAME, newNode);
4224 return this.compare(oldWrapped, newWrapped) !== 0;
4225 };
4226 /**
4227 * @returns a node wrapper that will sort equal to or less than
4228 * any other node wrapper, using this index
4229 */
4230 Index.prototype.minPost = function () {
4231 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4232 return NamedNode.MIN;
4233 };
4234 return Index;
4235}());
4236
4237/**
4238 * @license
4239 * Copyright 2017 Google LLC
4240 *
4241 * Licensed under the Apache License, Version 2.0 (the "License");
4242 * you may not use this file except in compliance with the License.
4243 * You may obtain a copy of the License at
4244 *
4245 * http://www.apache.org/licenses/LICENSE-2.0
4246 *
4247 * Unless required by applicable law or agreed to in writing, software
4248 * distributed under the License is distributed on an "AS IS" BASIS,
4249 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4250 * See the License for the specific language governing permissions and
4251 * limitations under the License.
4252 */
4253var __EMPTY_NODE;
4254var KeyIndex = /** @class */ (function (_super) {
4255 tslib.__extends(KeyIndex, _super);
4256 function KeyIndex() {
4257 return _super !== null && _super.apply(this, arguments) || this;
4258 }
4259 Object.defineProperty(KeyIndex, "__EMPTY_NODE", {
4260 get: function () {
4261 return __EMPTY_NODE;
4262 },
4263 set: function (val) {
4264 __EMPTY_NODE = val;
4265 },
4266 enumerable: false,
4267 configurable: true
4268 });
4269 KeyIndex.prototype.compare = function (a, b) {
4270 return nameCompare(a.name, b.name);
4271 };
4272 KeyIndex.prototype.isDefinedOn = function (node) {
4273 // We could probably return true here (since every node has a key), but it's never called
4274 // so just leaving unimplemented for now.
4275 throw util.assertionError('KeyIndex.isDefinedOn not expected to be called.');
4276 };
4277 KeyIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
4278 return false; // The key for a node never changes.
4279 };
4280 KeyIndex.prototype.minPost = function () {
4281 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4282 return NamedNode.MIN;
4283 };
4284 KeyIndex.prototype.maxPost = function () {
4285 // TODO: This should really be created once and cached in a static property, but
4286 // NamedNode isn't defined yet, so I can't use it in a static. Bleh.
4287 return new NamedNode(MAX_NAME, __EMPTY_NODE);
4288 };
4289 KeyIndex.prototype.makePost = function (indexValue, name) {
4290 util.assert(typeof indexValue === 'string', 'KeyIndex indexValue must always be a string.');
4291 // We just use empty node, but it'll never be compared, since our comparator only looks at name.
4292 return new NamedNode(indexValue, __EMPTY_NODE);
4293 };
4294 /**
4295 * @returns String representation for inclusion in a query spec
4296 */
4297 KeyIndex.prototype.toString = function () {
4298 return '.key';
4299 };
4300 return KeyIndex;
4301}(Index));
4302var KEY_INDEX = new KeyIndex();
4303
4304/**
4305 * @license
4306 * Copyright 2017 Google LLC
4307 *
4308 * Licensed under the Apache License, Version 2.0 (the "License");
4309 * you may not use this file except in compliance with the License.
4310 * You may obtain a copy of the License at
4311 *
4312 * http://www.apache.org/licenses/LICENSE-2.0
4313 *
4314 * Unless required by applicable law or agreed to in writing, software
4315 * distributed under the License is distributed on an "AS IS" BASIS,
4316 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4317 * See the License for the specific language governing permissions and
4318 * limitations under the License.
4319 */
4320/**
4321 * An iterator over an LLRBNode.
4322 */
4323var SortedMapIterator = /** @class */ (function () {
4324 /**
4325 * @param node - Node to iterate.
4326 * @param isReverse_ - Whether or not to iterate in reverse
4327 */
4328 function SortedMapIterator(node, startKey, comparator, isReverse_, resultGenerator_) {
4329 if (resultGenerator_ === void 0) { resultGenerator_ = null; }
4330 this.isReverse_ = isReverse_;
4331 this.resultGenerator_ = resultGenerator_;
4332 this.nodeStack_ = [];
4333 var cmp = 1;
4334 while (!node.isEmpty()) {
4335 node = node;
4336 cmp = startKey ? comparator(node.key, startKey) : 1;
4337 // flip the comparison if we're going in reverse
4338 if (isReverse_) {
4339 cmp *= -1;
4340 }
4341 if (cmp < 0) {
4342 // This node is less than our start key. ignore it
4343 if (this.isReverse_) {
4344 node = node.left;
4345 }
4346 else {
4347 node = node.right;
4348 }
4349 }
4350 else if (cmp === 0) {
4351 // This node is exactly equal to our start key. Push it on the stack, but stop iterating;
4352 this.nodeStack_.push(node);
4353 break;
4354 }
4355 else {
4356 // This node is greater than our start key, add it to the stack and move to the next one
4357 this.nodeStack_.push(node);
4358 if (this.isReverse_) {
4359 node = node.right;
4360 }
4361 else {
4362 node = node.left;
4363 }
4364 }
4365 }
4366 }
4367 SortedMapIterator.prototype.getNext = function () {
4368 if (this.nodeStack_.length === 0) {
4369 return null;
4370 }
4371 var node = this.nodeStack_.pop();
4372 var result;
4373 if (this.resultGenerator_) {
4374 result = this.resultGenerator_(node.key, node.value);
4375 }
4376 else {
4377 result = { key: node.key, value: node.value };
4378 }
4379 if (this.isReverse_) {
4380 node = node.left;
4381 while (!node.isEmpty()) {
4382 this.nodeStack_.push(node);
4383 node = node.right;
4384 }
4385 }
4386 else {
4387 node = node.right;
4388 while (!node.isEmpty()) {
4389 this.nodeStack_.push(node);
4390 node = node.left;
4391 }
4392 }
4393 return result;
4394 };
4395 SortedMapIterator.prototype.hasNext = function () {
4396 return this.nodeStack_.length > 0;
4397 };
4398 SortedMapIterator.prototype.peek = function () {
4399 if (this.nodeStack_.length === 0) {
4400 return null;
4401 }
4402 var node = this.nodeStack_[this.nodeStack_.length - 1];
4403 if (this.resultGenerator_) {
4404 return this.resultGenerator_(node.key, node.value);
4405 }
4406 else {
4407 return { key: node.key, value: node.value };
4408 }
4409 };
4410 return SortedMapIterator;
4411}());
4412/**
4413 * Represents a node in a Left-leaning Red-Black tree.
4414 */
4415var LLRBNode = /** @class */ (function () {
4416 /**
4417 * @param key - Key associated with this node.
4418 * @param value - Value associated with this node.
4419 * @param color - Whether this node is red.
4420 * @param left - Left child.
4421 * @param right - Right child.
4422 */
4423 function LLRBNode(key, value, color, left, right) {
4424 this.key = key;
4425 this.value = value;
4426 this.color = color != null ? color : LLRBNode.RED;
4427 this.left =
4428 left != null ? left : SortedMap.EMPTY_NODE;
4429 this.right =
4430 right != null ? right : SortedMap.EMPTY_NODE;
4431 }
4432 /**
4433 * Returns a copy of the current node, optionally replacing pieces of it.
4434 *
4435 * @param key - New key for the node, or null.
4436 * @param value - New value for the node, or null.
4437 * @param color - New color for the node, or null.
4438 * @param left - New left child for the node, or null.
4439 * @param right - New right child for the node, or null.
4440 * @returns The node copy.
4441 */
4442 LLRBNode.prototype.copy = function (key, value, color, left, right) {
4443 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);
4444 };
4445 /**
4446 * @returns The total number of nodes in the tree.
4447 */
4448 LLRBNode.prototype.count = function () {
4449 return this.left.count() + 1 + this.right.count();
4450 };
4451 /**
4452 * @returns True if the tree is empty.
4453 */
4454 LLRBNode.prototype.isEmpty = function () {
4455 return false;
4456 };
4457 /**
4458 * Traverses the tree in key order and calls the specified action function
4459 * for each node.
4460 *
4461 * @param action - Callback function to be called for each
4462 * node. If it returns true, traversal is aborted.
4463 * @returns The first truthy value returned by action, or the last falsey
4464 * value returned by action
4465 */
4466 LLRBNode.prototype.inorderTraversal = function (action) {
4467 return (this.left.inorderTraversal(action) ||
4468 !!action(this.key, this.value) ||
4469 this.right.inorderTraversal(action));
4470 };
4471 /**
4472 * Traverses the tree in reverse key order and calls the specified action function
4473 * for each node.
4474 *
4475 * @param action - Callback function to be called for each
4476 * node. If it returns true, traversal is aborted.
4477 * @returns True if traversal was aborted.
4478 */
4479 LLRBNode.prototype.reverseTraversal = function (action) {
4480 return (this.right.reverseTraversal(action) ||
4481 action(this.key, this.value) ||
4482 this.left.reverseTraversal(action));
4483 };
4484 /**
4485 * @returns The minimum node in the tree.
4486 */
4487 LLRBNode.prototype.min_ = function () {
4488 if (this.left.isEmpty()) {
4489 return this;
4490 }
4491 else {
4492 return this.left.min_();
4493 }
4494 };
4495 /**
4496 * @returns The maximum key in the tree.
4497 */
4498 LLRBNode.prototype.minKey = function () {
4499 return this.min_().key;
4500 };
4501 /**
4502 * @returns The maximum key in the tree.
4503 */
4504 LLRBNode.prototype.maxKey = function () {
4505 if (this.right.isEmpty()) {
4506 return this.key;
4507 }
4508 else {
4509 return this.right.maxKey();
4510 }
4511 };
4512 /**
4513 * @param key - Key to insert.
4514 * @param value - Value to insert.
4515 * @param comparator - Comparator.
4516 * @returns New tree, with the key/value added.
4517 */
4518 LLRBNode.prototype.insert = function (key, value, comparator) {
4519 var n = this;
4520 var cmp = comparator(key, n.key);
4521 if (cmp < 0) {
4522 n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
4523 }
4524 else if (cmp === 0) {
4525 n = n.copy(null, value, null, null, null);
4526 }
4527 else {
4528 n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
4529 }
4530 return n.fixUp_();
4531 };
4532 /**
4533 * @returns New tree, with the minimum key removed.
4534 */
4535 LLRBNode.prototype.removeMin_ = function () {
4536 if (this.left.isEmpty()) {
4537 return SortedMap.EMPTY_NODE;
4538 }
4539 var n = this;
4540 if (!n.left.isRed_() && !n.left.left.isRed_()) {
4541 n = n.moveRedLeft_();
4542 }
4543 n = n.copy(null, null, null, n.left.removeMin_(), null);
4544 return n.fixUp_();
4545 };
4546 /**
4547 * @param key - The key of the item to remove.
4548 * @param comparator - Comparator.
4549 * @returns New tree, with the specified item removed.
4550 */
4551 LLRBNode.prototype.remove = function (key, comparator) {
4552 var n, smallest;
4553 n = this;
4554 if (comparator(key, n.key) < 0) {
4555 if (!n.left.isEmpty() && !n.left.isRed_() && !n.left.left.isRed_()) {
4556 n = n.moveRedLeft_();
4557 }
4558 n = n.copy(null, null, null, n.left.remove(key, comparator), null);
4559 }
4560 else {
4561 if (n.left.isRed_()) {
4562 n = n.rotateRight_();
4563 }
4564 if (!n.right.isEmpty() && !n.right.isRed_() && !n.right.left.isRed_()) {
4565 n = n.moveRedRight_();
4566 }
4567 if (comparator(key, n.key) === 0) {
4568 if (n.right.isEmpty()) {
4569 return SortedMap.EMPTY_NODE;
4570 }
4571 else {
4572 smallest = n.right.min_();
4573 n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin_());
4574 }
4575 }
4576 n = n.copy(null, null, null, null, n.right.remove(key, comparator));
4577 }
4578 return n.fixUp_();
4579 };
4580 /**
4581 * @returns Whether this is a RED node.
4582 */
4583 LLRBNode.prototype.isRed_ = function () {
4584 return this.color;
4585 };
4586 /**
4587 * @returns New tree after performing any needed rotations.
4588 */
4589 LLRBNode.prototype.fixUp_ = function () {
4590 var n = this;
4591 if (n.right.isRed_() && !n.left.isRed_()) {
4592 n = n.rotateLeft_();
4593 }
4594 if (n.left.isRed_() && n.left.left.isRed_()) {
4595 n = n.rotateRight_();
4596 }
4597 if (n.left.isRed_() && n.right.isRed_()) {
4598 n = n.colorFlip_();
4599 }
4600 return n;
4601 };
4602 /**
4603 * @returns New tree, after moveRedLeft.
4604 */
4605 LLRBNode.prototype.moveRedLeft_ = function () {
4606 var n = this.colorFlip_();
4607 if (n.right.left.isRed_()) {
4608 n = n.copy(null, null, null, null, n.right.rotateRight_());
4609 n = n.rotateLeft_();
4610 n = n.colorFlip_();
4611 }
4612 return n;
4613 };
4614 /**
4615 * @returns New tree, after moveRedRight.
4616 */
4617 LLRBNode.prototype.moveRedRight_ = function () {
4618 var n = this.colorFlip_();
4619 if (n.left.left.isRed_()) {
4620 n = n.rotateRight_();
4621 n = n.colorFlip_();
4622 }
4623 return n;
4624 };
4625 /**
4626 * @returns New tree, after rotateLeft.
4627 */
4628 LLRBNode.prototype.rotateLeft_ = function () {
4629 var nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
4630 return this.right.copy(null, null, this.color, nl, null);
4631 };
4632 /**
4633 * @returns New tree, after rotateRight.
4634 */
4635 LLRBNode.prototype.rotateRight_ = function () {
4636 var nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
4637 return this.left.copy(null, null, this.color, null, nr);
4638 };
4639 /**
4640 * @returns Newt ree, after colorFlip.
4641 */
4642 LLRBNode.prototype.colorFlip_ = function () {
4643 var left = this.left.copy(null, null, !this.left.color, null, null);
4644 var right = this.right.copy(null, null, !this.right.color, null, null);
4645 return this.copy(null, null, !this.color, left, right);
4646 };
4647 /**
4648 * For testing.
4649 *
4650 * @returns True if all is well.
4651 */
4652 LLRBNode.prototype.checkMaxDepth_ = function () {
4653 var blackDepth = this.check_();
4654 return Math.pow(2.0, blackDepth) <= this.count() + 1;
4655 };
4656 LLRBNode.prototype.check_ = function () {
4657 if (this.isRed_() && this.left.isRed_()) {
4658 throw new Error('Red node has red child(' + this.key + ',' + this.value + ')');
4659 }
4660 if (this.right.isRed_()) {
4661 throw new Error('Right child of (' + this.key + ',' + this.value + ') is red');
4662 }
4663 var blackDepth = this.left.check_();
4664 if (blackDepth !== this.right.check_()) {
4665 throw new Error('Black depths differ');
4666 }
4667 else {
4668 return blackDepth + (this.isRed_() ? 0 : 1);
4669 }
4670 };
4671 LLRBNode.RED = true;
4672 LLRBNode.BLACK = false;
4673 return LLRBNode;
4674}());
4675/**
4676 * Represents an empty node (a leaf node in the Red-Black Tree).
4677 */
4678var LLRBEmptyNode = /** @class */ (function () {
4679 function LLRBEmptyNode() {
4680 }
4681 /**
4682 * Returns a copy of the current node.
4683 *
4684 * @returns The node copy.
4685 */
4686 LLRBEmptyNode.prototype.copy = function (key, value, color, left, right) {
4687 return this;
4688 };
4689 /**
4690 * Returns a copy of the tree, with the specified key/value added.
4691 *
4692 * @param key - Key to be added.
4693 * @param value - Value to be added.
4694 * @param comparator - Comparator.
4695 * @returns New tree, with item added.
4696 */
4697 LLRBEmptyNode.prototype.insert = function (key, value, comparator) {
4698 return new LLRBNode(key, value, null);
4699 };
4700 /**
4701 * Returns a copy of the tree, with the specified key removed.
4702 *
4703 * @param key - The key to remove.
4704 * @param comparator - Comparator.
4705 * @returns New tree, with item removed.
4706 */
4707 LLRBEmptyNode.prototype.remove = function (key, comparator) {
4708 return this;
4709 };
4710 /**
4711 * @returns The total number of nodes in the tree.
4712 */
4713 LLRBEmptyNode.prototype.count = function () {
4714 return 0;
4715 };
4716 /**
4717 * @returns True if the tree is empty.
4718 */
4719 LLRBEmptyNode.prototype.isEmpty = function () {
4720 return true;
4721 };
4722 /**
4723 * Traverses the tree in key order and calls the specified action function
4724 * for each node.
4725 *
4726 * @param action - Callback function to be called for each
4727 * node. If it returns true, traversal is aborted.
4728 * @returns True if traversal was aborted.
4729 */
4730 LLRBEmptyNode.prototype.inorderTraversal = function (action) {
4731 return false;
4732 };
4733 /**
4734 * Traverses the tree in reverse key order and calls the specified action function
4735 * for each node.
4736 *
4737 * @param action - Callback function to be called for each
4738 * node. If it returns true, traversal is aborted.
4739 * @returns True if traversal was aborted.
4740 */
4741 LLRBEmptyNode.prototype.reverseTraversal = function (action) {
4742 return false;
4743 };
4744 LLRBEmptyNode.prototype.minKey = function () {
4745 return null;
4746 };
4747 LLRBEmptyNode.prototype.maxKey = function () {
4748 return null;
4749 };
4750 LLRBEmptyNode.prototype.check_ = function () {
4751 return 0;
4752 };
4753 /**
4754 * @returns Whether this node is red.
4755 */
4756 LLRBEmptyNode.prototype.isRed_ = function () {
4757 return false;
4758 };
4759 return LLRBEmptyNode;
4760}());
4761/**
4762 * An immutable sorted map implementation, based on a Left-leaning Red-Black
4763 * tree.
4764 */
4765var SortedMap = /** @class */ (function () {
4766 /**
4767 * @param comparator_ - Key comparator.
4768 * @param root_ - Optional root node for the map.
4769 */
4770 function SortedMap(comparator_, root_) {
4771 if (root_ === void 0) { root_ = SortedMap.EMPTY_NODE; }
4772 this.comparator_ = comparator_;
4773 this.root_ = root_;
4774 }
4775 /**
4776 * Returns a copy of the map, with the specified key/value added or replaced.
4777 * (TODO: We should perhaps rename this method to 'put')
4778 *
4779 * @param key - Key to be added.
4780 * @param value - Value to be added.
4781 * @returns New map, with item added.
4782 */
4783 SortedMap.prototype.insert = function (key, value) {
4784 return new SortedMap(this.comparator_, this.root_
4785 .insert(key, value, this.comparator_)
4786 .copy(null, null, LLRBNode.BLACK, null, null));
4787 };
4788 /**
4789 * Returns a copy of the map, with the specified key removed.
4790 *
4791 * @param key - The key to remove.
4792 * @returns New map, with item removed.
4793 */
4794 SortedMap.prototype.remove = function (key) {
4795 return new SortedMap(this.comparator_, this.root_
4796 .remove(key, this.comparator_)
4797 .copy(null, null, LLRBNode.BLACK, null, null));
4798 };
4799 /**
4800 * Returns the value of the node with the given key, or null.
4801 *
4802 * @param key - The key to look up.
4803 * @returns The value of the node with the given key, or null if the
4804 * key doesn't exist.
4805 */
4806 SortedMap.prototype.get = function (key) {
4807 var cmp;
4808 var node = this.root_;
4809 while (!node.isEmpty()) {
4810 cmp = this.comparator_(key, node.key);
4811 if (cmp === 0) {
4812 return node.value;
4813 }
4814 else if (cmp < 0) {
4815 node = node.left;
4816 }
4817 else if (cmp > 0) {
4818 node = node.right;
4819 }
4820 }
4821 return null;
4822 };
4823 /**
4824 * Returns the key of the item *before* the specified key, or null if key is the first item.
4825 * @param key - The key to find the predecessor of
4826 * @returns The predecessor key.
4827 */
4828 SortedMap.prototype.getPredecessorKey = function (key) {
4829 var cmp, node = this.root_, rightParent = null;
4830 while (!node.isEmpty()) {
4831 cmp = this.comparator_(key, node.key);
4832 if (cmp === 0) {
4833 if (!node.left.isEmpty()) {
4834 node = node.left;
4835 while (!node.right.isEmpty()) {
4836 node = node.right;
4837 }
4838 return node.key;
4839 }
4840 else if (rightParent) {
4841 return rightParent.key;
4842 }
4843 else {
4844 return null; // first item.
4845 }
4846 }
4847 else if (cmp < 0) {
4848 node = node.left;
4849 }
4850 else if (cmp > 0) {
4851 rightParent = node;
4852 node = node.right;
4853 }
4854 }
4855 throw new Error('Attempted to find predecessor key for a nonexistent key. What gives?');
4856 };
4857 /**
4858 * @returns True if the map is empty.
4859 */
4860 SortedMap.prototype.isEmpty = function () {
4861 return this.root_.isEmpty();
4862 };
4863 /**
4864 * @returns The total number of nodes in the map.
4865 */
4866 SortedMap.prototype.count = function () {
4867 return this.root_.count();
4868 };
4869 /**
4870 * @returns The minimum key in the map.
4871 */
4872 SortedMap.prototype.minKey = function () {
4873 return this.root_.minKey();
4874 };
4875 /**
4876 * @returns The maximum key in the map.
4877 */
4878 SortedMap.prototype.maxKey = function () {
4879 return this.root_.maxKey();
4880 };
4881 /**
4882 * Traverses the map in key order and calls the specified action function
4883 * for each key/value pair.
4884 *
4885 * @param action - Callback function to be called
4886 * for each key/value pair. If action returns true, traversal is aborted.
4887 * @returns The first truthy value returned by action, or the last falsey
4888 * value returned by action
4889 */
4890 SortedMap.prototype.inorderTraversal = function (action) {
4891 return this.root_.inorderTraversal(action);
4892 };
4893 /**
4894 * Traverses the map in reverse key order and calls the specified action function
4895 * for each key/value pair.
4896 *
4897 * @param action - Callback function to be called
4898 * for each key/value pair. If action returns true, traversal is aborted.
4899 * @returns True if the traversal was aborted.
4900 */
4901 SortedMap.prototype.reverseTraversal = function (action) {
4902 return this.root_.reverseTraversal(action);
4903 };
4904 /**
4905 * Returns an iterator over the SortedMap.
4906 * @returns The iterator.
4907 */
4908 SortedMap.prototype.getIterator = function (resultGenerator) {
4909 return new SortedMapIterator(this.root_, null, this.comparator_, false, resultGenerator);
4910 };
4911 SortedMap.prototype.getIteratorFrom = function (key, resultGenerator) {
4912 return new SortedMapIterator(this.root_, key, this.comparator_, false, resultGenerator);
4913 };
4914 SortedMap.prototype.getReverseIteratorFrom = function (key, resultGenerator) {
4915 return new SortedMapIterator(this.root_, key, this.comparator_, true, resultGenerator);
4916 };
4917 SortedMap.prototype.getReverseIterator = function (resultGenerator) {
4918 return new SortedMapIterator(this.root_, null, this.comparator_, true, resultGenerator);
4919 };
4920 /**
4921 * Always use the same empty node, to reduce memory.
4922 */
4923 SortedMap.EMPTY_NODE = new LLRBEmptyNode();
4924 return SortedMap;
4925}());
4926
4927/**
4928 * @license
4929 * Copyright 2017 Google LLC
4930 *
4931 * Licensed under the Apache License, Version 2.0 (the "License");
4932 * you may not use this file except in compliance with the License.
4933 * You may obtain a copy of the License at
4934 *
4935 * http://www.apache.org/licenses/LICENSE-2.0
4936 *
4937 * Unless required by applicable law or agreed to in writing, software
4938 * distributed under the License is distributed on an "AS IS" BASIS,
4939 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4940 * See the License for the specific language governing permissions and
4941 * limitations under the License.
4942 */
4943function NAME_ONLY_COMPARATOR(left, right) {
4944 return nameCompare(left.name, right.name);
4945}
4946function NAME_COMPARATOR(left, right) {
4947 return nameCompare(left, right);
4948}
4949
4950/**
4951 * @license
4952 * Copyright 2017 Google LLC
4953 *
4954 * Licensed under the Apache License, Version 2.0 (the "License");
4955 * you may not use this file except in compliance with the License.
4956 * You may obtain a copy of the License at
4957 *
4958 * http://www.apache.org/licenses/LICENSE-2.0
4959 *
4960 * Unless required by applicable law or agreed to in writing, software
4961 * distributed under the License is distributed on an "AS IS" BASIS,
4962 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4963 * See the License for the specific language governing permissions and
4964 * limitations under the License.
4965 */
4966var MAX_NODE$2;
4967function setMaxNode$1(val) {
4968 MAX_NODE$2 = val;
4969}
4970var priorityHashText = function (priority) {
4971 if (typeof priority === 'number') {
4972 return 'number:' + doubleToIEEE754String(priority);
4973 }
4974 else {
4975 return 'string:' + priority;
4976 }
4977};
4978/**
4979 * Validates that a priority snapshot Node is valid.
4980 */
4981var validatePriorityNode = function (priorityNode) {
4982 if (priorityNode.isLeafNode()) {
4983 var val = priorityNode.val();
4984 util.assert(typeof val === 'string' ||
4985 typeof val === 'number' ||
4986 (typeof val === 'object' && util.contains(val, '.sv')), 'Priority must be a string or number.');
4987 }
4988 else {
4989 util.assert(priorityNode === MAX_NODE$2 || priorityNode.isEmpty(), 'priority of unexpected type.');
4990 }
4991 // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
4992 util.assert(priorityNode === MAX_NODE$2 || priorityNode.getPriority().isEmpty(), "Priority nodes can't have a priority of their own.");
4993};
4994
4995/**
4996 * @license
4997 * Copyright 2017 Google LLC
4998 *
4999 * Licensed under the Apache License, Version 2.0 (the "License");
5000 * you may not use this file except in compliance with the License.
5001 * You may obtain a copy of the License at
5002 *
5003 * http://www.apache.org/licenses/LICENSE-2.0
5004 *
5005 * Unless required by applicable law or agreed to in writing, software
5006 * distributed under the License is distributed on an "AS IS" BASIS,
5007 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5008 * See the License for the specific language governing permissions and
5009 * limitations under the License.
5010 */
5011var __childrenNodeConstructor;
5012/**
5013 * LeafNode is a class for storing leaf nodes in a DataSnapshot. It
5014 * implements Node and stores the value of the node (a string,
5015 * number, or boolean) accessible via getValue().
5016 */
5017var LeafNode = /** @class */ (function () {
5018 /**
5019 * @param value_ - The value to store in this leaf node. The object type is
5020 * possible in the event of a deferred value
5021 * @param priorityNode_ - The priority of this node.
5022 */
5023 function LeafNode(value_, priorityNode_) {
5024 if (priorityNode_ === void 0) { priorityNode_ = LeafNode.__childrenNodeConstructor.EMPTY_NODE; }
5025 this.value_ = value_;
5026 this.priorityNode_ = priorityNode_;
5027 this.lazyHash_ = null;
5028 util.assert(this.value_ !== undefined && this.value_ !== null, "LeafNode shouldn't be created with null/undefined value.");
5029 validatePriorityNode(this.priorityNode_);
5030 }
5031 Object.defineProperty(LeafNode, "__childrenNodeConstructor", {
5032 get: function () {
5033 return __childrenNodeConstructor;
5034 },
5035 set: function (val) {
5036 __childrenNodeConstructor = val;
5037 },
5038 enumerable: false,
5039 configurable: true
5040 });
5041 /** @inheritDoc */
5042 LeafNode.prototype.isLeafNode = function () {
5043 return true;
5044 };
5045 /** @inheritDoc */
5046 LeafNode.prototype.getPriority = function () {
5047 return this.priorityNode_;
5048 };
5049 /** @inheritDoc */
5050 LeafNode.prototype.updatePriority = function (newPriorityNode) {
5051 return new LeafNode(this.value_, newPriorityNode);
5052 };
5053 /** @inheritDoc */
5054 LeafNode.prototype.getImmediateChild = function (childName) {
5055 // Hack to treat priority as a regular child
5056 if (childName === '.priority') {
5057 return this.priorityNode_;
5058 }
5059 else {
5060 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5061 }
5062 };
5063 /** @inheritDoc */
5064 LeafNode.prototype.getChild = function (path) {
5065 if (pathIsEmpty(path)) {
5066 return this;
5067 }
5068 else if (pathGetFront(path) === '.priority') {
5069 return this.priorityNode_;
5070 }
5071 else {
5072 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5073 }
5074 };
5075 LeafNode.prototype.hasChild = function () {
5076 return false;
5077 };
5078 /** @inheritDoc */
5079 LeafNode.prototype.getPredecessorChildName = function (childName, childNode) {
5080 return null;
5081 };
5082 /** @inheritDoc */
5083 LeafNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5084 if (childName === '.priority') {
5085 return this.updatePriority(newChildNode);
5086 }
5087 else if (newChildNode.isEmpty() && childName !== '.priority') {
5088 return this;
5089 }
5090 else {
5091 return LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateImmediateChild(childName, newChildNode).updatePriority(this.priorityNode_);
5092 }
5093 };
5094 /** @inheritDoc */
5095 LeafNode.prototype.updateChild = function (path, newChildNode) {
5096 var front = pathGetFront(path);
5097 if (front === null) {
5098 return newChildNode;
5099 }
5100 else if (newChildNode.isEmpty() && front !== '.priority') {
5101 return this;
5102 }
5103 else {
5104 util.assert(front !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5105 return this.updateImmediateChild(front, LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateChild(pathPopFront(path), newChildNode));
5106 }
5107 };
5108 /** @inheritDoc */
5109 LeafNode.prototype.isEmpty = function () {
5110 return false;
5111 };
5112 /** @inheritDoc */
5113 LeafNode.prototype.numChildren = function () {
5114 return 0;
5115 };
5116 /** @inheritDoc */
5117 LeafNode.prototype.forEachChild = function (index, action) {
5118 return false;
5119 };
5120 LeafNode.prototype.val = function (exportFormat) {
5121 if (exportFormat && !this.getPriority().isEmpty()) {
5122 return {
5123 '.value': this.getValue(),
5124 '.priority': this.getPriority().val()
5125 };
5126 }
5127 else {
5128 return this.getValue();
5129 }
5130 };
5131 /** @inheritDoc */
5132 LeafNode.prototype.hash = function () {
5133 if (this.lazyHash_ === null) {
5134 var toHash = '';
5135 if (!this.priorityNode_.isEmpty()) {
5136 toHash +=
5137 'priority:' +
5138 priorityHashText(this.priorityNode_.val()) +
5139 ':';
5140 }
5141 var type = typeof this.value_;
5142 toHash += type + ':';
5143 if (type === 'number') {
5144 toHash += doubleToIEEE754String(this.value_);
5145 }
5146 else {
5147 toHash += this.value_;
5148 }
5149 this.lazyHash_ = sha1(toHash);
5150 }
5151 return this.lazyHash_;
5152 };
5153 /**
5154 * Returns the value of the leaf node.
5155 * @returns The value of the node.
5156 */
5157 LeafNode.prototype.getValue = function () {
5158 return this.value_;
5159 };
5160 LeafNode.prototype.compareTo = function (other) {
5161 if (other === LeafNode.__childrenNodeConstructor.EMPTY_NODE) {
5162 return 1;
5163 }
5164 else if (other instanceof LeafNode.__childrenNodeConstructor) {
5165 return -1;
5166 }
5167 else {
5168 util.assert(other.isLeafNode(), 'Unknown node type');
5169 return this.compareToLeafNode_(other);
5170 }
5171 };
5172 /**
5173 * Comparison specifically for two leaf nodes
5174 */
5175 LeafNode.prototype.compareToLeafNode_ = function (otherLeaf) {
5176 var otherLeafType = typeof otherLeaf.value_;
5177 var thisLeafType = typeof this.value_;
5178 var otherIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(otherLeafType);
5179 var thisIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(thisLeafType);
5180 util.assert(otherIndex >= 0, 'Unknown leaf type: ' + otherLeafType);
5181 util.assert(thisIndex >= 0, 'Unknown leaf type: ' + thisLeafType);
5182 if (otherIndex === thisIndex) {
5183 // Same type, compare values
5184 if (thisLeafType === 'object') {
5185 // Deferred value nodes are all equal, but we should also never get to this point...
5186 return 0;
5187 }
5188 else {
5189 // Note that this works because true > false, all others are number or string comparisons
5190 if (this.value_ < otherLeaf.value_) {
5191 return -1;
5192 }
5193 else if (this.value_ === otherLeaf.value_) {
5194 return 0;
5195 }
5196 else {
5197 return 1;
5198 }
5199 }
5200 }
5201 else {
5202 return thisIndex - otherIndex;
5203 }
5204 };
5205 LeafNode.prototype.withIndex = function () {
5206 return this;
5207 };
5208 LeafNode.prototype.isIndexed = function () {
5209 return true;
5210 };
5211 LeafNode.prototype.equals = function (other) {
5212 if (other === this) {
5213 return true;
5214 }
5215 else if (other.isLeafNode()) {
5216 var otherLeaf = other;
5217 return (this.value_ === otherLeaf.value_ &&
5218 this.priorityNode_.equals(otherLeaf.priorityNode_));
5219 }
5220 else {
5221 return false;
5222 }
5223 };
5224 /**
5225 * The sort order for comparing leaf nodes of different types. If two leaf nodes have
5226 * the same type, the comparison falls back to their value
5227 */
5228 LeafNode.VALUE_TYPE_ORDER = ['object', 'boolean', 'number', 'string'];
5229 return LeafNode;
5230}());
5231
5232/**
5233 * @license
5234 * Copyright 2017 Google LLC
5235 *
5236 * Licensed under the Apache License, Version 2.0 (the "License");
5237 * you may not use this file except in compliance with the License.
5238 * You may obtain a copy of the License at
5239 *
5240 * http://www.apache.org/licenses/LICENSE-2.0
5241 *
5242 * Unless required by applicable law or agreed to in writing, software
5243 * distributed under the License is distributed on an "AS IS" BASIS,
5244 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5245 * See the License for the specific language governing permissions and
5246 * limitations under the License.
5247 */
5248var nodeFromJSON$1;
5249var MAX_NODE$1;
5250function setNodeFromJSON(val) {
5251 nodeFromJSON$1 = val;
5252}
5253function setMaxNode(val) {
5254 MAX_NODE$1 = val;
5255}
5256var PriorityIndex = /** @class */ (function (_super) {
5257 tslib.__extends(PriorityIndex, _super);
5258 function PriorityIndex() {
5259 return _super !== null && _super.apply(this, arguments) || this;
5260 }
5261 PriorityIndex.prototype.compare = function (a, b) {
5262 var aPriority = a.node.getPriority();
5263 var bPriority = b.node.getPriority();
5264 var indexCmp = aPriority.compareTo(bPriority);
5265 if (indexCmp === 0) {
5266 return nameCompare(a.name, b.name);
5267 }
5268 else {
5269 return indexCmp;
5270 }
5271 };
5272 PriorityIndex.prototype.isDefinedOn = function (node) {
5273 return !node.getPriority().isEmpty();
5274 };
5275 PriorityIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
5276 return !oldNode.getPriority().equals(newNode.getPriority());
5277 };
5278 PriorityIndex.prototype.minPost = function () {
5279 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5280 return NamedNode.MIN;
5281 };
5282 PriorityIndex.prototype.maxPost = function () {
5283 return new NamedNode(MAX_NAME, new LeafNode('[PRIORITY-POST]', MAX_NODE$1));
5284 };
5285 PriorityIndex.prototype.makePost = function (indexValue, name) {
5286 var priorityNode = nodeFromJSON$1(indexValue);
5287 return new NamedNode(name, new LeafNode('[PRIORITY-POST]', priorityNode));
5288 };
5289 /**
5290 * @returns String representation for inclusion in a query spec
5291 */
5292 PriorityIndex.prototype.toString = function () {
5293 return '.priority';
5294 };
5295 return PriorityIndex;
5296}(Index));
5297var PRIORITY_INDEX = new PriorityIndex();
5298
5299/**
5300 * @license
5301 * Copyright 2017 Google LLC
5302 *
5303 * Licensed under the Apache License, Version 2.0 (the "License");
5304 * you may not use this file except in compliance with the License.
5305 * You may obtain a copy of the License at
5306 *
5307 * http://www.apache.org/licenses/LICENSE-2.0
5308 *
5309 * Unless required by applicable law or agreed to in writing, software
5310 * distributed under the License is distributed on an "AS IS" BASIS,
5311 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5312 * See the License for the specific language governing permissions and
5313 * limitations under the License.
5314 */
5315var LOG_2 = Math.log(2);
5316var Base12Num = /** @class */ (function () {
5317 function Base12Num(length) {
5318 var logBase2 = function (num) {
5319 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5320 return parseInt((Math.log(num) / LOG_2), 10);
5321 };
5322 var bitMask = function (bits) { return parseInt(Array(bits + 1).join('1'), 2); };
5323 this.count = logBase2(length + 1);
5324 this.current_ = this.count - 1;
5325 var mask = bitMask(this.count);
5326 this.bits_ = (length + 1) & mask;
5327 }
5328 Base12Num.prototype.nextBitIsOne = function () {
5329 //noinspection JSBitwiseOperatorUsage
5330 var result = !(this.bits_ & (0x1 << this.current_));
5331 this.current_--;
5332 return result;
5333 };
5334 return Base12Num;
5335}());
5336/**
5337 * Takes a list of child nodes and constructs a SortedSet using the given comparison
5338 * function
5339 *
5340 * Uses the algorithm described in the paper linked here:
5341 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458
5342 *
5343 * @param childList - Unsorted list of children
5344 * @param cmp - The comparison method to be used
5345 * @param keyFn - An optional function to extract K from a node wrapper, if K's
5346 * type is not NamedNode
5347 * @param mapSortFn - An optional override for comparator used by the generated sorted map
5348 */
5349var buildChildSet = function (childList, cmp, keyFn, mapSortFn) {
5350 childList.sort(cmp);
5351 var buildBalancedTree = function (low, high) {
5352 var length = high - low;
5353 var namedNode;
5354 var key;
5355 if (length === 0) {
5356 return null;
5357 }
5358 else if (length === 1) {
5359 namedNode = childList[low];
5360 key = keyFn ? keyFn(namedNode) : namedNode;
5361 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, null, null);
5362 }
5363 else {
5364 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5365 var middle = parseInt((length / 2), 10) + low;
5366 var left = buildBalancedTree(low, middle);
5367 var right = buildBalancedTree(middle + 1, high);
5368 namedNode = childList[middle];
5369 key = keyFn ? keyFn(namedNode) : namedNode;
5370 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, left, right);
5371 }
5372 };
5373 var buildFrom12Array = function (base12) {
5374 var node = null;
5375 var root = null;
5376 var index = childList.length;
5377 var buildPennant = function (chunkSize, color) {
5378 var low = index - chunkSize;
5379 var high = index;
5380 index -= chunkSize;
5381 var childTree = buildBalancedTree(low + 1, high);
5382 var namedNode = childList[low];
5383 var key = keyFn ? keyFn(namedNode) : namedNode;
5384 attachPennant(new LLRBNode(key, namedNode.node, color, null, childTree));
5385 };
5386 var attachPennant = function (pennant) {
5387 if (node) {
5388 node.left = pennant;
5389 node = pennant;
5390 }
5391 else {
5392 root = pennant;
5393 node = pennant;
5394 }
5395 };
5396 for (var i = 0; i < base12.count; ++i) {
5397 var isOne = base12.nextBitIsOne();
5398 // The number of nodes taken in each slice is 2^(arr.length - (i + 1))
5399 var chunkSize = Math.pow(2, base12.count - (i + 1));
5400 if (isOne) {
5401 buildPennant(chunkSize, LLRBNode.BLACK);
5402 }
5403 else {
5404 // current == 2
5405 buildPennant(chunkSize, LLRBNode.BLACK);
5406 buildPennant(chunkSize, LLRBNode.RED);
5407 }
5408 }
5409 return root;
5410 };
5411 var base12 = new Base12Num(childList.length);
5412 var root = buildFrom12Array(base12);
5413 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5414 return new SortedMap(mapSortFn || cmp, root);
5415};
5416
5417/**
5418 * @license
5419 * Copyright 2017 Google LLC
5420 *
5421 * Licensed under the Apache License, Version 2.0 (the "License");
5422 * you may not use this file except in compliance with the License.
5423 * You may obtain a copy of the License at
5424 *
5425 * http://www.apache.org/licenses/LICENSE-2.0
5426 *
5427 * Unless required by applicable law or agreed to in writing, software
5428 * distributed under the License is distributed on an "AS IS" BASIS,
5429 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5430 * See the License for the specific language governing permissions and
5431 * limitations under the License.
5432 */
5433var _defaultIndexMap;
5434var fallbackObject = {};
5435var IndexMap = /** @class */ (function () {
5436 function IndexMap(indexes_, indexSet_) {
5437 this.indexes_ = indexes_;
5438 this.indexSet_ = indexSet_;
5439 }
5440 Object.defineProperty(IndexMap, "Default", {
5441 /**
5442 * The default IndexMap for nodes without a priority
5443 */
5444 get: function () {
5445 util.assert(fallbackObject && PRIORITY_INDEX, 'ChildrenNode.ts has not been loaded');
5446 _defaultIndexMap =
5447 _defaultIndexMap ||
5448 new IndexMap({ '.priority': fallbackObject }, { '.priority': PRIORITY_INDEX });
5449 return _defaultIndexMap;
5450 },
5451 enumerable: false,
5452 configurable: true
5453 });
5454 IndexMap.prototype.get = function (indexKey) {
5455 var sortedMap = util.safeGet(this.indexes_, indexKey);
5456 if (!sortedMap) {
5457 throw new Error('No index defined for ' + indexKey);
5458 }
5459 if (sortedMap instanceof SortedMap) {
5460 return sortedMap;
5461 }
5462 else {
5463 // The index exists, but it falls back to just name comparison. Return null so that the calling code uses the
5464 // regular child map
5465 return null;
5466 }
5467 };
5468 IndexMap.prototype.hasIndex = function (indexDefinition) {
5469 return util.contains(this.indexSet_, indexDefinition.toString());
5470 };
5471 IndexMap.prototype.addIndex = function (indexDefinition, existingChildren) {
5472 util.assert(indexDefinition !== KEY_INDEX, "KeyIndex always exists and isn't meant to be added to the IndexMap.");
5473 var childList = [];
5474 var sawIndexedValue = false;
5475 var iter = existingChildren.getIterator(NamedNode.Wrap);
5476 var next = iter.getNext();
5477 while (next) {
5478 sawIndexedValue =
5479 sawIndexedValue || indexDefinition.isDefinedOn(next.node);
5480 childList.push(next);
5481 next = iter.getNext();
5482 }
5483 var newIndex;
5484 if (sawIndexedValue) {
5485 newIndex = buildChildSet(childList, indexDefinition.getCompare());
5486 }
5487 else {
5488 newIndex = fallbackObject;
5489 }
5490 var indexName = indexDefinition.toString();
5491 var newIndexSet = tslib.__assign({}, this.indexSet_);
5492 newIndexSet[indexName] = indexDefinition;
5493 var newIndexes = tslib.__assign({}, this.indexes_);
5494 newIndexes[indexName] = newIndex;
5495 return new IndexMap(newIndexes, newIndexSet);
5496 };
5497 /**
5498 * Ensure that this node is properly tracked in any indexes that we're maintaining
5499 */
5500 IndexMap.prototype.addToIndexes = function (namedNode, existingChildren) {
5501 var _this = this;
5502 var newIndexes = util.map(this.indexes_, function (indexedChildren, indexName) {
5503 var index = util.safeGet(_this.indexSet_, indexName);
5504 util.assert(index, 'Missing index implementation for ' + indexName);
5505 if (indexedChildren === fallbackObject) {
5506 // Check to see if we need to index everything
5507 if (index.isDefinedOn(namedNode.node)) {
5508 // We need to build this index
5509 var childList = [];
5510 var iter = existingChildren.getIterator(NamedNode.Wrap);
5511 var next = iter.getNext();
5512 while (next) {
5513 if (next.name !== namedNode.name) {
5514 childList.push(next);
5515 }
5516 next = iter.getNext();
5517 }
5518 childList.push(namedNode);
5519 return buildChildSet(childList, index.getCompare());
5520 }
5521 else {
5522 // No change, this remains a fallback
5523 return fallbackObject;
5524 }
5525 }
5526 else {
5527 var existingSnap = existingChildren.get(namedNode.name);
5528 var newChildren = indexedChildren;
5529 if (existingSnap) {
5530 newChildren = newChildren.remove(new NamedNode(namedNode.name, existingSnap));
5531 }
5532 return newChildren.insert(namedNode, namedNode.node);
5533 }
5534 });
5535 return new IndexMap(newIndexes, this.indexSet_);
5536 };
5537 /**
5538 * Create a new IndexMap instance with the given value removed
5539 */
5540 IndexMap.prototype.removeFromIndexes = function (namedNode, existingChildren) {
5541 var newIndexes = util.map(this.indexes_, function (indexedChildren) {
5542 if (indexedChildren === fallbackObject) {
5543 // This is the fallback. Just return it, nothing to do in this case
5544 return indexedChildren;
5545 }
5546 else {
5547 var existingSnap = existingChildren.get(namedNode.name);
5548 if (existingSnap) {
5549 return indexedChildren.remove(new NamedNode(namedNode.name, existingSnap));
5550 }
5551 else {
5552 // No record of this child
5553 return indexedChildren;
5554 }
5555 }
5556 });
5557 return new IndexMap(newIndexes, this.indexSet_);
5558 };
5559 return IndexMap;
5560}());
5561
5562/**
5563 * @license
5564 * Copyright 2017 Google LLC
5565 *
5566 * Licensed under the Apache License, Version 2.0 (the "License");
5567 * you may not use this file except in compliance with the License.
5568 * You may obtain a copy of the License at
5569 *
5570 * http://www.apache.org/licenses/LICENSE-2.0
5571 *
5572 * Unless required by applicable law or agreed to in writing, software
5573 * distributed under the License is distributed on an "AS IS" BASIS,
5574 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5575 * See the License for the specific language governing permissions and
5576 * limitations under the License.
5577 */
5578// TODO: For memory savings, don't store priorityNode_ if it's empty.
5579var EMPTY_NODE;
5580/**
5581 * ChildrenNode is a class for storing internal nodes in a DataSnapshot
5582 * (i.e. nodes with children). It implements Node and stores the
5583 * list of children in the children property, sorted by child name.
5584 */
5585var ChildrenNode = /** @class */ (function () {
5586 /**
5587 * @param children_ - List of children of this node..
5588 * @param priorityNode_ - The priority of this node (as a snapshot node).
5589 */
5590 function ChildrenNode(children_, priorityNode_, indexMap_) {
5591 this.children_ = children_;
5592 this.priorityNode_ = priorityNode_;
5593 this.indexMap_ = indexMap_;
5594 this.lazyHash_ = null;
5595 /**
5596 * Note: The only reason we allow null priority is for EMPTY_NODE, since we can't use
5597 * EMPTY_NODE as the priority of EMPTY_NODE. We might want to consider making EMPTY_NODE its own
5598 * class instead of an empty ChildrenNode.
5599 */
5600 if (this.priorityNode_) {
5601 validatePriorityNode(this.priorityNode_);
5602 }
5603 if (this.children_.isEmpty()) {
5604 util.assert(!this.priorityNode_ || this.priorityNode_.isEmpty(), 'An empty node cannot have a priority');
5605 }
5606 }
5607 Object.defineProperty(ChildrenNode, "EMPTY_NODE", {
5608 get: function () {
5609 return (EMPTY_NODE ||
5610 (EMPTY_NODE = new ChildrenNode(new SortedMap(NAME_COMPARATOR), null, IndexMap.Default)));
5611 },
5612 enumerable: false,
5613 configurable: true
5614 });
5615 /** @inheritDoc */
5616 ChildrenNode.prototype.isLeafNode = function () {
5617 return false;
5618 };
5619 /** @inheritDoc */
5620 ChildrenNode.prototype.getPriority = function () {
5621 return this.priorityNode_ || EMPTY_NODE;
5622 };
5623 /** @inheritDoc */
5624 ChildrenNode.prototype.updatePriority = function (newPriorityNode) {
5625 if (this.children_.isEmpty()) {
5626 // Don't allow priorities on empty nodes
5627 return this;
5628 }
5629 else {
5630 return new ChildrenNode(this.children_, newPriorityNode, this.indexMap_);
5631 }
5632 };
5633 /** @inheritDoc */
5634 ChildrenNode.prototype.getImmediateChild = function (childName) {
5635 // Hack to treat priority as a regular child
5636 if (childName === '.priority') {
5637 return this.getPriority();
5638 }
5639 else {
5640 var child = this.children_.get(childName);
5641 return child === null ? EMPTY_NODE : child;
5642 }
5643 };
5644 /** @inheritDoc */
5645 ChildrenNode.prototype.getChild = function (path) {
5646 var front = pathGetFront(path);
5647 if (front === null) {
5648 return this;
5649 }
5650 return this.getImmediateChild(front).getChild(pathPopFront(path));
5651 };
5652 /** @inheritDoc */
5653 ChildrenNode.prototype.hasChild = function (childName) {
5654 return this.children_.get(childName) !== null;
5655 };
5656 /** @inheritDoc */
5657 ChildrenNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5658 util.assert(newChildNode, 'We should always be passing snapshot nodes');
5659 if (childName === '.priority') {
5660 return this.updatePriority(newChildNode);
5661 }
5662 else {
5663 var namedNode = new NamedNode(childName, newChildNode);
5664 var newChildren = void 0, newIndexMap = void 0;
5665 if (newChildNode.isEmpty()) {
5666 newChildren = this.children_.remove(childName);
5667 newIndexMap = this.indexMap_.removeFromIndexes(namedNode, this.children_);
5668 }
5669 else {
5670 newChildren = this.children_.insert(childName, newChildNode);
5671 newIndexMap = this.indexMap_.addToIndexes(namedNode, this.children_);
5672 }
5673 var newPriority = newChildren.isEmpty()
5674 ? EMPTY_NODE
5675 : this.priorityNode_;
5676 return new ChildrenNode(newChildren, newPriority, newIndexMap);
5677 }
5678 };
5679 /** @inheritDoc */
5680 ChildrenNode.prototype.updateChild = function (path, newChildNode) {
5681 var front = pathGetFront(path);
5682 if (front === null) {
5683 return newChildNode;
5684 }
5685 else {
5686 util.assert(pathGetFront(path) !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5687 var newImmediateChild = this.getImmediateChild(front).updateChild(pathPopFront(path), newChildNode);
5688 return this.updateImmediateChild(front, newImmediateChild);
5689 }
5690 };
5691 /** @inheritDoc */
5692 ChildrenNode.prototype.isEmpty = function () {
5693 return this.children_.isEmpty();
5694 };
5695 /** @inheritDoc */
5696 ChildrenNode.prototype.numChildren = function () {
5697 return this.children_.count();
5698 };
5699 /** @inheritDoc */
5700 ChildrenNode.prototype.val = function (exportFormat) {
5701 if (this.isEmpty()) {
5702 return null;
5703 }
5704 var obj = {};
5705 var numKeys = 0, maxKey = 0, allIntegerKeys = true;
5706 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5707 obj[key] = childNode.val(exportFormat);
5708 numKeys++;
5709 if (allIntegerKeys && ChildrenNode.INTEGER_REGEXP_.test(key)) {
5710 maxKey = Math.max(maxKey, Number(key));
5711 }
5712 else {
5713 allIntegerKeys = false;
5714 }
5715 });
5716 if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
5717 // convert to array.
5718 var array = [];
5719 // eslint-disable-next-line guard-for-in
5720 for (var key in obj) {
5721 array[key] = obj[key];
5722 }
5723 return array;
5724 }
5725 else {
5726 if (exportFormat && !this.getPriority().isEmpty()) {
5727 obj['.priority'] = this.getPriority().val();
5728 }
5729 return obj;
5730 }
5731 };
5732 /** @inheritDoc */
5733 ChildrenNode.prototype.hash = function () {
5734 if (this.lazyHash_ === null) {
5735 var toHash_1 = '';
5736 if (!this.getPriority().isEmpty()) {
5737 toHash_1 +=
5738 'priority:' +
5739 priorityHashText(this.getPriority().val()) +
5740 ':';
5741 }
5742 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5743 var childHash = childNode.hash();
5744 if (childHash !== '') {
5745 toHash_1 += ':' + key + ':' + childHash;
5746 }
5747 });
5748 this.lazyHash_ = toHash_1 === '' ? '' : sha1(toHash_1);
5749 }
5750 return this.lazyHash_;
5751 };
5752 /** @inheritDoc */
5753 ChildrenNode.prototype.getPredecessorChildName = function (childName, childNode, index) {
5754 var idx = this.resolveIndex_(index);
5755 if (idx) {
5756 var predecessor = idx.getPredecessorKey(new NamedNode(childName, childNode));
5757 return predecessor ? predecessor.name : null;
5758 }
5759 else {
5760 return this.children_.getPredecessorKey(childName);
5761 }
5762 };
5763 ChildrenNode.prototype.getFirstChildName = function (indexDefinition) {
5764 var idx = this.resolveIndex_(indexDefinition);
5765 if (idx) {
5766 var minKey = idx.minKey();
5767 return minKey && minKey.name;
5768 }
5769 else {
5770 return this.children_.minKey();
5771 }
5772 };
5773 ChildrenNode.prototype.getFirstChild = function (indexDefinition) {
5774 var minKey = this.getFirstChildName(indexDefinition);
5775 if (minKey) {
5776 return new NamedNode(minKey, this.children_.get(minKey));
5777 }
5778 else {
5779 return null;
5780 }
5781 };
5782 /**
5783 * Given an index, return the key name of the largest value we have, according to that index
5784 */
5785 ChildrenNode.prototype.getLastChildName = function (indexDefinition) {
5786 var idx = this.resolveIndex_(indexDefinition);
5787 if (idx) {
5788 var maxKey = idx.maxKey();
5789 return maxKey && maxKey.name;
5790 }
5791 else {
5792 return this.children_.maxKey();
5793 }
5794 };
5795 ChildrenNode.prototype.getLastChild = function (indexDefinition) {
5796 var maxKey = this.getLastChildName(indexDefinition);
5797 if (maxKey) {
5798 return new NamedNode(maxKey, this.children_.get(maxKey));
5799 }
5800 else {
5801 return null;
5802 }
5803 };
5804 ChildrenNode.prototype.forEachChild = function (index, action) {
5805 var idx = this.resolveIndex_(index);
5806 if (idx) {
5807 return idx.inorderTraversal(function (wrappedNode) {
5808 return action(wrappedNode.name, wrappedNode.node);
5809 });
5810 }
5811 else {
5812 return this.children_.inorderTraversal(action);
5813 }
5814 };
5815 ChildrenNode.prototype.getIterator = function (indexDefinition) {
5816 return this.getIteratorFrom(indexDefinition.minPost(), indexDefinition);
5817 };
5818 ChildrenNode.prototype.getIteratorFrom = function (startPost, indexDefinition) {
5819 var idx = this.resolveIndex_(indexDefinition);
5820 if (idx) {
5821 return idx.getIteratorFrom(startPost, function (key) { return key; });
5822 }
5823 else {
5824 var iterator = this.children_.getIteratorFrom(startPost.name, NamedNode.Wrap);
5825 var next = iterator.peek();
5826 while (next != null && indexDefinition.compare(next, startPost) < 0) {
5827 iterator.getNext();
5828 next = iterator.peek();
5829 }
5830 return iterator;
5831 }
5832 };
5833 ChildrenNode.prototype.getReverseIterator = function (indexDefinition) {
5834 return this.getReverseIteratorFrom(indexDefinition.maxPost(), indexDefinition);
5835 };
5836 ChildrenNode.prototype.getReverseIteratorFrom = function (endPost, indexDefinition) {
5837 var idx = this.resolveIndex_(indexDefinition);
5838 if (idx) {
5839 return idx.getReverseIteratorFrom(endPost, function (key) {
5840 return key;
5841 });
5842 }
5843 else {
5844 var iterator = this.children_.getReverseIteratorFrom(endPost.name, NamedNode.Wrap);
5845 var next = iterator.peek();
5846 while (next != null && indexDefinition.compare(next, endPost) > 0) {
5847 iterator.getNext();
5848 next = iterator.peek();
5849 }
5850 return iterator;
5851 }
5852 };
5853 ChildrenNode.prototype.compareTo = function (other) {
5854 if (this.isEmpty()) {
5855 if (other.isEmpty()) {
5856 return 0;
5857 }
5858 else {
5859 return -1;
5860 }
5861 }
5862 else if (other.isLeafNode() || other.isEmpty()) {
5863 return 1;
5864 }
5865 else if (other === MAX_NODE) {
5866 return -1;
5867 }
5868 else {
5869 // Must be another node with children.
5870 return 0;
5871 }
5872 };
5873 ChildrenNode.prototype.withIndex = function (indexDefinition) {
5874 if (indexDefinition === KEY_INDEX ||
5875 this.indexMap_.hasIndex(indexDefinition)) {
5876 return this;
5877 }
5878 else {
5879 var newIndexMap = this.indexMap_.addIndex(indexDefinition, this.children_);
5880 return new ChildrenNode(this.children_, this.priorityNode_, newIndexMap);
5881 }
5882 };
5883 ChildrenNode.prototype.isIndexed = function (index) {
5884 return index === KEY_INDEX || this.indexMap_.hasIndex(index);
5885 };
5886 ChildrenNode.prototype.equals = function (other) {
5887 if (other === this) {
5888 return true;
5889 }
5890 else if (other.isLeafNode()) {
5891 return false;
5892 }
5893 else {
5894 var otherChildrenNode = other;
5895 if (!this.getPriority().equals(otherChildrenNode.getPriority())) {
5896 return false;
5897 }
5898 else if (this.children_.count() === otherChildrenNode.children_.count()) {
5899 var thisIter = this.getIterator(PRIORITY_INDEX);
5900 var otherIter = otherChildrenNode.getIterator(PRIORITY_INDEX);
5901 var thisCurrent = thisIter.getNext();
5902 var otherCurrent = otherIter.getNext();
5903 while (thisCurrent && otherCurrent) {
5904 if (thisCurrent.name !== otherCurrent.name ||
5905 !thisCurrent.node.equals(otherCurrent.node)) {
5906 return false;
5907 }
5908 thisCurrent = thisIter.getNext();
5909 otherCurrent = otherIter.getNext();
5910 }
5911 return thisCurrent === null && otherCurrent === null;
5912 }
5913 else {
5914 return false;
5915 }
5916 }
5917 };
5918 /**
5919 * Returns a SortedMap ordered by index, or null if the default (by-key) ordering can be used
5920 * instead.
5921 *
5922 */
5923 ChildrenNode.prototype.resolveIndex_ = function (indexDefinition) {
5924 if (indexDefinition === KEY_INDEX) {
5925 return null;
5926 }
5927 else {
5928 return this.indexMap_.get(indexDefinition.toString());
5929 }
5930 };
5931 ChildrenNode.INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;
5932 return ChildrenNode;
5933}());
5934var MaxNode = /** @class */ (function (_super) {
5935 tslib.__extends(MaxNode, _super);
5936 function MaxNode() {
5937 return _super.call(this, new SortedMap(NAME_COMPARATOR), ChildrenNode.EMPTY_NODE, IndexMap.Default) || this;
5938 }
5939 MaxNode.prototype.compareTo = function (other) {
5940 if (other === this) {
5941 return 0;
5942 }
5943 else {
5944 return 1;
5945 }
5946 };
5947 MaxNode.prototype.equals = function (other) {
5948 // Not that we every compare it, but MAX_NODE is only ever equal to itself
5949 return other === this;
5950 };
5951 MaxNode.prototype.getPriority = function () {
5952 return this;
5953 };
5954 MaxNode.prototype.getImmediateChild = function (childName) {
5955 return ChildrenNode.EMPTY_NODE;
5956 };
5957 MaxNode.prototype.isEmpty = function () {
5958 return false;
5959 };
5960 return MaxNode;
5961}(ChildrenNode));
5962/**
5963 * Marker that will sort higher than any other snapshot.
5964 */
5965var MAX_NODE = new MaxNode();
5966Object.defineProperties(NamedNode, {
5967 MIN: {
5968 value: new NamedNode(MIN_NAME, ChildrenNode.EMPTY_NODE)
5969 },
5970 MAX: {
5971 value: new NamedNode(MAX_NAME, MAX_NODE)
5972 }
5973});
5974/**
5975 * Reference Extensions
5976 */
5977KeyIndex.__EMPTY_NODE = ChildrenNode.EMPTY_NODE;
5978LeafNode.__childrenNodeConstructor = ChildrenNode;
5979setMaxNode$1(MAX_NODE);
5980setMaxNode(MAX_NODE);
5981
5982/**
5983 * @license
5984 * Copyright 2017 Google LLC
5985 *
5986 * Licensed under the Apache License, Version 2.0 (the "License");
5987 * you may not use this file except in compliance with the License.
5988 * You may obtain a copy of the License at
5989 *
5990 * http://www.apache.org/licenses/LICENSE-2.0
5991 *
5992 * Unless required by applicable law or agreed to in writing, software
5993 * distributed under the License is distributed on an "AS IS" BASIS,
5994 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5995 * See the License for the specific language governing permissions and
5996 * limitations under the License.
5997 */
5998var USE_HINZE = true;
5999/**
6000 * Constructs a snapshot node representing the passed JSON and returns it.
6001 * @param json - JSON to create a node for.
6002 * @param priority - Optional priority to use. This will be ignored if the
6003 * passed JSON contains a .priority property.
6004 */
6005function nodeFromJSON(json, priority) {
6006 if (priority === void 0) { priority = null; }
6007 if (json === null) {
6008 return ChildrenNode.EMPTY_NODE;
6009 }
6010 if (typeof json === 'object' && '.priority' in json) {
6011 priority = json['.priority'];
6012 }
6013 util.assert(priority === null ||
6014 typeof priority === 'string' ||
6015 typeof priority === 'number' ||
6016 (typeof priority === 'object' && '.sv' in priority), 'Invalid priority type found: ' + typeof priority);
6017 if (typeof json === 'object' && '.value' in json && json['.value'] !== null) {
6018 json = json['.value'];
6019 }
6020 // Valid leaf nodes include non-objects or server-value wrapper objects
6021 if (typeof json !== 'object' || '.sv' in json) {
6022 var jsonLeaf = json;
6023 return new LeafNode(jsonLeaf, nodeFromJSON(priority));
6024 }
6025 if (!(json instanceof Array) && USE_HINZE) {
6026 var children_1 = [];
6027 var childrenHavePriority_1 = false;
6028 var hinzeJsonObj = json;
6029 each(hinzeJsonObj, function (key, child) {
6030 if (key.substring(0, 1) !== '.') {
6031 // Ignore metadata nodes
6032 var childNode = nodeFromJSON(child);
6033 if (!childNode.isEmpty()) {
6034 childrenHavePriority_1 =
6035 childrenHavePriority_1 || !childNode.getPriority().isEmpty();
6036 children_1.push(new NamedNode(key, childNode));
6037 }
6038 }
6039 });
6040 if (children_1.length === 0) {
6041 return ChildrenNode.EMPTY_NODE;
6042 }
6043 var childSet = buildChildSet(children_1, NAME_ONLY_COMPARATOR, function (namedNode) { return namedNode.name; }, NAME_COMPARATOR);
6044 if (childrenHavePriority_1) {
6045 var sortedChildSet = buildChildSet(children_1, PRIORITY_INDEX.getCompare());
6046 return new ChildrenNode(childSet, nodeFromJSON(priority), new IndexMap({ '.priority': sortedChildSet }, { '.priority': PRIORITY_INDEX }));
6047 }
6048 else {
6049 return new ChildrenNode(childSet, nodeFromJSON(priority), IndexMap.Default);
6050 }
6051 }
6052 else {
6053 var node_1 = ChildrenNode.EMPTY_NODE;
6054 each(json, function (key, childData) {
6055 if (util.contains(json, key)) {
6056 if (key.substring(0, 1) !== '.') {
6057 // ignore metadata nodes.
6058 var childNode = nodeFromJSON(childData);
6059 if (childNode.isLeafNode() || !childNode.isEmpty()) {
6060 node_1 = node_1.updateImmediateChild(key, childNode);
6061 }
6062 }
6063 }
6064 });
6065 return node_1.updatePriority(nodeFromJSON(priority));
6066 }
6067}
6068setNodeFromJSON(nodeFromJSON);
6069
6070/**
6071 * @license
6072 * Copyright 2017 Google LLC
6073 *
6074 * Licensed under the Apache License, Version 2.0 (the "License");
6075 * you may not use this file except in compliance with the License.
6076 * You may obtain a copy of the License at
6077 *
6078 * http://www.apache.org/licenses/LICENSE-2.0
6079 *
6080 * Unless required by applicable law or agreed to in writing, software
6081 * distributed under the License is distributed on an "AS IS" BASIS,
6082 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6083 * See the License for the specific language governing permissions and
6084 * limitations under the License.
6085 */
6086var PathIndex = /** @class */ (function (_super) {
6087 tslib.__extends(PathIndex, _super);
6088 function PathIndex(indexPath_) {
6089 var _this = _super.call(this) || this;
6090 _this.indexPath_ = indexPath_;
6091 util.assert(!pathIsEmpty(indexPath_) && pathGetFront(indexPath_) !== '.priority', "Can't create PathIndex with empty path or .priority key");
6092 return _this;
6093 }
6094 PathIndex.prototype.extractChild = function (snap) {
6095 return snap.getChild(this.indexPath_);
6096 };
6097 PathIndex.prototype.isDefinedOn = function (node) {
6098 return !node.getChild(this.indexPath_).isEmpty();
6099 };
6100 PathIndex.prototype.compare = function (a, b) {
6101 var aChild = this.extractChild(a.node);
6102 var bChild = this.extractChild(b.node);
6103 var indexCmp = aChild.compareTo(bChild);
6104 if (indexCmp === 0) {
6105 return nameCompare(a.name, b.name);
6106 }
6107 else {
6108 return indexCmp;
6109 }
6110 };
6111 PathIndex.prototype.makePost = function (indexValue, name) {
6112 var valueNode = nodeFromJSON(indexValue);
6113 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, valueNode);
6114 return new NamedNode(name, node);
6115 };
6116 PathIndex.prototype.maxPost = function () {
6117 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, MAX_NODE);
6118 return new NamedNode(MAX_NAME, node);
6119 };
6120 PathIndex.prototype.toString = function () {
6121 return pathSlice(this.indexPath_, 0).join('/');
6122 };
6123 return PathIndex;
6124}(Index));
6125
6126/**
6127 * @license
6128 * Copyright 2017 Google LLC
6129 *
6130 * Licensed under the Apache License, Version 2.0 (the "License");
6131 * you may not use this file except in compliance with the License.
6132 * You may obtain a copy of the License at
6133 *
6134 * http://www.apache.org/licenses/LICENSE-2.0
6135 *
6136 * Unless required by applicable law or agreed to in writing, software
6137 * distributed under the License is distributed on an "AS IS" BASIS,
6138 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6139 * See the License for the specific language governing permissions and
6140 * limitations under the License.
6141 */
6142var ValueIndex = /** @class */ (function (_super) {
6143 tslib.__extends(ValueIndex, _super);
6144 function ValueIndex() {
6145 return _super !== null && _super.apply(this, arguments) || this;
6146 }
6147 ValueIndex.prototype.compare = function (a, b) {
6148 var indexCmp = a.node.compareTo(b.node);
6149 if (indexCmp === 0) {
6150 return nameCompare(a.name, b.name);
6151 }
6152 else {
6153 return indexCmp;
6154 }
6155 };
6156 ValueIndex.prototype.isDefinedOn = function (node) {
6157 return true;
6158 };
6159 ValueIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
6160 return !oldNode.equals(newNode);
6161 };
6162 ValueIndex.prototype.minPost = function () {
6163 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6164 return NamedNode.MIN;
6165 };
6166 ValueIndex.prototype.maxPost = function () {
6167 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6168 return NamedNode.MAX;
6169 };
6170 ValueIndex.prototype.makePost = function (indexValue, name) {
6171 var valueNode = nodeFromJSON(indexValue);
6172 return new NamedNode(name, valueNode);
6173 };
6174 /**
6175 * @returns String representation for inclusion in a query spec
6176 */
6177 ValueIndex.prototype.toString = function () {
6178 return '.value';
6179 };
6180 return ValueIndex;
6181}(Index));
6182var VALUE_INDEX = new ValueIndex();
6183
6184/**
6185 * @license
6186 * Copyright 2017 Google LLC
6187 *
6188 * Licensed under the Apache License, Version 2.0 (the "License");
6189 * you may not use this file except in compliance with the License.
6190 * You may obtain a copy of the License at
6191 *
6192 * http://www.apache.org/licenses/LICENSE-2.0
6193 *
6194 * Unless required by applicable law or agreed to in writing, software
6195 * distributed under the License is distributed on an "AS IS" BASIS,
6196 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6197 * See the License for the specific language governing permissions and
6198 * limitations under the License.
6199 */
6200// Modeled after base64 web-safe chars, but ordered by ASCII.
6201var PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
6202var MIN_PUSH_CHAR = '-';
6203var MAX_PUSH_CHAR = 'z';
6204var MAX_KEY_LEN = 786;
6205/**
6206 * Fancy ID generator that creates 20-character string identifiers with the
6207 * following properties:
6208 *
6209 * 1. They're based on timestamp so that they sort *after* any existing ids.
6210 * 2. They contain 72-bits of random data after the timestamp so that IDs won't
6211 * collide with other clients' IDs.
6212 * 3. They sort *lexicographically* (so the timestamp is converted to characters
6213 * that will sort properly).
6214 * 4. They're monotonically increasing. Even if you generate more than one in
6215 * the same timestamp, the latter ones will sort after the former ones. We do
6216 * this by using the previous random bits but "incrementing" them by 1 (only
6217 * in the case of a timestamp collision).
6218 */
6219var nextPushId = (function () {
6220 // Timestamp of last push, used to prevent local collisions if you push twice
6221 // in one ms.
6222 var lastPushTime = 0;
6223 // We generate 72-bits of randomness which get turned into 12 characters and
6224 // appended to the timestamp to prevent collisions with other clients. We
6225 // store the last characters we generated because in the event of a collision,
6226 // we'll use those same characters except "incremented" by one.
6227 var lastRandChars = [];
6228 return function (now) {
6229 var duplicateTime = now === lastPushTime;
6230 lastPushTime = now;
6231 var i;
6232 var timeStampChars = new Array(8);
6233 for (i = 7; i >= 0; i--) {
6234 timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
6235 // NOTE: Can't use << here because javascript will convert to int and lose
6236 // the upper bits.
6237 now = Math.floor(now / 64);
6238 }
6239 util.assert(now === 0, 'Cannot push at time == 0');
6240 var id = timeStampChars.join('');
6241 if (!duplicateTime) {
6242 for (i = 0; i < 12; i++) {
6243 lastRandChars[i] = Math.floor(Math.random() * 64);
6244 }
6245 }
6246 else {
6247 // If the timestamp hasn't changed since last push, use the same random
6248 // number, except incremented by 1.
6249 for (i = 11; i >= 0 && lastRandChars[i] === 63; i--) {
6250 lastRandChars[i] = 0;
6251 }
6252 lastRandChars[i]++;
6253 }
6254 for (i = 0; i < 12; i++) {
6255 id += PUSH_CHARS.charAt(lastRandChars[i]);
6256 }
6257 util.assert(id.length === 20, 'nextPushId: Length should be 20.');
6258 return id;
6259 };
6260})();
6261var successor = function (key) {
6262 if (key === '' + INTEGER_32_MAX) {
6263 // See https://firebase.google.com/docs/database/web/lists-of-data#data-order
6264 return MIN_PUSH_CHAR;
6265 }
6266 var keyAsInt = tryParseInt(key);
6267 if (keyAsInt != null) {
6268 return '' + (keyAsInt + 1);
6269 }
6270 var next = new Array(key.length);
6271 for (var i_1 = 0; i_1 < next.length; i_1++) {
6272 next[i_1] = key.charAt(i_1);
6273 }
6274 if (next.length < MAX_KEY_LEN) {
6275 next.push(MIN_PUSH_CHAR);
6276 return next.join('');
6277 }
6278 var i = next.length - 1;
6279 while (i >= 0 && next[i] === MAX_PUSH_CHAR) {
6280 i--;
6281 }
6282 // `successor` was called on the largest possible key, so return the
6283 // MAX_NAME, which sorts larger than all keys.
6284 if (i === -1) {
6285 return MAX_NAME;
6286 }
6287 var source = next[i];
6288 var sourcePlusOne = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(source) + 1);
6289 next[i] = sourcePlusOne;
6290 return next.slice(0, i + 1).join('');
6291};
6292// `key` is assumed to be non-empty.
6293var predecessor = function (key) {
6294 if (key === '' + INTEGER_32_MIN) {
6295 return MIN_NAME;
6296 }
6297 var keyAsInt = tryParseInt(key);
6298 if (keyAsInt != null) {
6299 return '' + (keyAsInt - 1);
6300 }
6301 var next = new Array(key.length);
6302 for (var i = 0; i < next.length; i++) {
6303 next[i] = key.charAt(i);
6304 }
6305 // If `key` ends in `MIN_PUSH_CHAR`, the largest key lexicographically
6306 // smaller than `key`, is `key[0:key.length - 1]`. The next key smaller
6307 // than that, `predecessor(predecessor(key))`, is
6308 //
6309 // `key[0:key.length - 2] + (key[key.length - 1] - 1) + \
6310 // { MAX_PUSH_CHAR repeated MAX_KEY_LEN - (key.length - 1) times }
6311 //
6312 // analogous to increment/decrement for base-10 integers.
6313 //
6314 // This works because lexigographic comparison works character-by-character,
6315 // using length as a tie-breaker if one key is a prefix of the other.
6316 if (next[next.length - 1] === MIN_PUSH_CHAR) {
6317 if (next.length === 1) {
6318 // See https://firebase.google.com/docs/database/web/lists-of-data#orderbykey
6319 return '' + INTEGER_32_MAX;
6320 }
6321 delete next[next.length - 1];
6322 return next.join('');
6323 }
6324 // Replace the last character with it's immediate predecessor, and
6325 // fill the suffix of the key with MAX_PUSH_CHAR. This is the
6326 // lexicographically largest possible key smaller than `key`.
6327 next[next.length - 1] = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(next[next.length - 1]) - 1);
6328 return next.join('') + MAX_PUSH_CHAR.repeat(MAX_KEY_LEN - next.length);
6329};
6330
6331/**
6332 * @license
6333 * Copyright 2017 Google LLC
6334 *
6335 * Licensed under the Apache License, Version 2.0 (the "License");
6336 * you may not use this file except in compliance with the License.
6337 * You may obtain a copy of the License at
6338 *
6339 * http://www.apache.org/licenses/LICENSE-2.0
6340 *
6341 * Unless required by applicable law or agreed to in writing, software
6342 * distributed under the License is distributed on an "AS IS" BASIS,
6343 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6344 * See the License for the specific language governing permissions and
6345 * limitations under the License.
6346 */
6347function changeValue(snapshotNode) {
6348 return { type: "value" /* VALUE */, snapshotNode: snapshotNode };
6349}
6350function changeChildAdded(childName, snapshotNode) {
6351 return { type: "child_added" /* CHILD_ADDED */, snapshotNode: snapshotNode, childName: childName };
6352}
6353function changeChildRemoved(childName, snapshotNode) {
6354 return { type: "child_removed" /* CHILD_REMOVED */, snapshotNode: snapshotNode, childName: childName };
6355}
6356function changeChildChanged(childName, snapshotNode, oldSnap) {
6357 return {
6358 type: "child_changed" /* CHILD_CHANGED */,
6359 snapshotNode: snapshotNode,
6360 childName: childName,
6361 oldSnap: oldSnap
6362 };
6363}
6364function changeChildMoved(childName, snapshotNode) {
6365 return { type: "child_moved" /* CHILD_MOVED */, snapshotNode: snapshotNode, childName: childName };
6366}
6367
6368/**
6369 * @license
6370 * Copyright 2017 Google LLC
6371 *
6372 * Licensed under the Apache License, Version 2.0 (the "License");
6373 * you may not use this file except in compliance with the License.
6374 * You may obtain a copy of the License at
6375 *
6376 * http://www.apache.org/licenses/LICENSE-2.0
6377 *
6378 * Unless required by applicable law or agreed to in writing, software
6379 * distributed under the License is distributed on an "AS IS" BASIS,
6380 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6381 * See the License for the specific language governing permissions and
6382 * limitations under the License.
6383 */
6384/**
6385 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
6386 */
6387var IndexedFilter = /** @class */ (function () {
6388 function IndexedFilter(index_) {
6389 this.index_ = index_;
6390 }
6391 IndexedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6392 util.assert(snap.isIndexed(this.index_), 'A node must be indexed if only a child is updated');
6393 var oldChild = snap.getImmediateChild(key);
6394 // Check if anything actually changed.
6395 if (oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))) {
6396 // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
6397 // In this case, affectedPath will appear null in both the old and new snapshots. So we need
6398 // to avoid treating these cases as "nothing changed."
6399 if (oldChild.isEmpty() === newChild.isEmpty()) {
6400 // Nothing changed.
6401 // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
6402 //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
6403 return snap;
6404 }
6405 }
6406 if (optChangeAccumulator != null) {
6407 if (newChild.isEmpty()) {
6408 if (snap.hasChild(key)) {
6409 optChangeAccumulator.trackChildChange(changeChildRemoved(key, oldChild));
6410 }
6411 else {
6412 util.assert(snap.isLeafNode(), 'A child remove without an old child only makes sense on a leaf node');
6413 }
6414 }
6415 else if (oldChild.isEmpty()) {
6416 optChangeAccumulator.trackChildChange(changeChildAdded(key, newChild));
6417 }
6418 else {
6419 optChangeAccumulator.trackChildChange(changeChildChanged(key, newChild, oldChild));
6420 }
6421 }
6422 if (snap.isLeafNode() && newChild.isEmpty()) {
6423 return snap;
6424 }
6425 else {
6426 // Make sure the node is indexed
6427 return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
6428 }
6429 };
6430 IndexedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6431 if (optChangeAccumulator != null) {
6432 if (!oldSnap.isLeafNode()) {
6433 oldSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6434 if (!newSnap.hasChild(key)) {
6435 optChangeAccumulator.trackChildChange(changeChildRemoved(key, childNode));
6436 }
6437 });
6438 }
6439 if (!newSnap.isLeafNode()) {
6440 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6441 if (oldSnap.hasChild(key)) {
6442 var oldChild = oldSnap.getImmediateChild(key);
6443 if (!oldChild.equals(childNode)) {
6444 optChangeAccumulator.trackChildChange(changeChildChanged(key, childNode, oldChild));
6445 }
6446 }
6447 else {
6448 optChangeAccumulator.trackChildChange(changeChildAdded(key, childNode));
6449 }
6450 });
6451 }
6452 }
6453 return newSnap.withIndex(this.index_);
6454 };
6455 IndexedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6456 if (oldSnap.isEmpty()) {
6457 return ChildrenNode.EMPTY_NODE;
6458 }
6459 else {
6460 return oldSnap.updatePriority(newPriority);
6461 }
6462 };
6463 IndexedFilter.prototype.filtersNodes = function () {
6464 return false;
6465 };
6466 IndexedFilter.prototype.getIndexedFilter = function () {
6467 return this;
6468 };
6469 IndexedFilter.prototype.getIndex = function () {
6470 return this.index_;
6471 };
6472 return IndexedFilter;
6473}());
6474
6475/**
6476 * @license
6477 * Copyright 2017 Google LLC
6478 *
6479 * Licensed under the Apache License, Version 2.0 (the "License");
6480 * you may not use this file except in compliance with the License.
6481 * You may obtain a copy of the License at
6482 *
6483 * http://www.apache.org/licenses/LICENSE-2.0
6484 *
6485 * Unless required by applicable law or agreed to in writing, software
6486 * distributed under the License is distributed on an "AS IS" BASIS,
6487 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6488 * See the License for the specific language governing permissions and
6489 * limitations under the License.
6490 */
6491/**
6492 * Filters nodes by range and uses an IndexFilter to track any changes after filtering the node
6493 */
6494var RangedFilter = /** @class */ (function () {
6495 function RangedFilter(params) {
6496 this.indexedFilter_ = new IndexedFilter(params.getIndex());
6497 this.index_ = params.getIndex();
6498 this.startPost_ = RangedFilter.getStartPost_(params);
6499 this.endPost_ = RangedFilter.getEndPost_(params);
6500 }
6501 RangedFilter.prototype.getStartPost = function () {
6502 return this.startPost_;
6503 };
6504 RangedFilter.prototype.getEndPost = function () {
6505 return this.endPost_;
6506 };
6507 RangedFilter.prototype.matches = function (node) {
6508 return (this.index_.compare(this.getStartPost(), node) <= 0 &&
6509 this.index_.compare(node, this.getEndPost()) <= 0);
6510 };
6511 RangedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6512 if (!this.matches(new NamedNode(key, newChild))) {
6513 newChild = ChildrenNode.EMPTY_NODE;
6514 }
6515 return this.indexedFilter_.updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6516 };
6517 RangedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6518 if (newSnap.isLeafNode()) {
6519 // Make sure we have a children node with the correct index, not a leaf node;
6520 newSnap = ChildrenNode.EMPTY_NODE;
6521 }
6522 var filtered = newSnap.withIndex(this.index_);
6523 // Don't support priorities on queries
6524 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6525 var self = this;
6526 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6527 if (!self.matches(new NamedNode(key, childNode))) {
6528 filtered = filtered.updateImmediateChild(key, ChildrenNode.EMPTY_NODE);
6529 }
6530 });
6531 return this.indexedFilter_.updateFullNode(oldSnap, filtered, optChangeAccumulator);
6532 };
6533 RangedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6534 // Don't support priorities on queries
6535 return oldSnap;
6536 };
6537 RangedFilter.prototype.filtersNodes = function () {
6538 return true;
6539 };
6540 RangedFilter.prototype.getIndexedFilter = function () {
6541 return this.indexedFilter_;
6542 };
6543 RangedFilter.prototype.getIndex = function () {
6544 return this.index_;
6545 };
6546 RangedFilter.getStartPost_ = function (params) {
6547 if (params.hasStart()) {
6548 var startName = params.getIndexStartName();
6549 return params.getIndex().makePost(params.getIndexStartValue(), startName);
6550 }
6551 else {
6552 return params.getIndex().minPost();
6553 }
6554 };
6555 RangedFilter.getEndPost_ = function (params) {
6556 if (params.hasEnd()) {
6557 var endName = params.getIndexEndName();
6558 return params.getIndex().makePost(params.getIndexEndValue(), endName);
6559 }
6560 else {
6561 return params.getIndex().maxPost();
6562 }
6563 };
6564 return RangedFilter;
6565}());
6566
6567/**
6568 * @license
6569 * Copyright 2017 Google LLC
6570 *
6571 * Licensed under the Apache License, Version 2.0 (the "License");
6572 * you may not use this file except in compliance with the License.
6573 * You may obtain a copy of the License at
6574 *
6575 * http://www.apache.org/licenses/LICENSE-2.0
6576 *
6577 * Unless required by applicable law or agreed to in writing, software
6578 * distributed under the License is distributed on an "AS IS" BASIS,
6579 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6580 * See the License for the specific language governing permissions and
6581 * limitations under the License.
6582 */
6583/**
6584 * Applies a limit and a range to a node and uses RangedFilter to do the heavy lifting where possible
6585 */
6586var LimitedFilter = /** @class */ (function () {
6587 function LimitedFilter(params) {
6588 this.rangedFilter_ = new RangedFilter(params);
6589 this.index_ = params.getIndex();
6590 this.limit_ = params.getLimit();
6591 this.reverse_ = !params.isViewFromLeft();
6592 }
6593 LimitedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6594 if (!this.rangedFilter_.matches(new NamedNode(key, newChild))) {
6595 newChild = ChildrenNode.EMPTY_NODE;
6596 }
6597 if (snap.getImmediateChild(key).equals(newChild)) {
6598 // No change
6599 return snap;
6600 }
6601 else if (snap.numChildren() < this.limit_) {
6602 return this.rangedFilter_
6603 .getIndexedFilter()
6604 .updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6605 }
6606 else {
6607 return this.fullLimitUpdateChild_(snap, key, newChild, source, optChangeAccumulator);
6608 }
6609 };
6610 LimitedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6611 var filtered;
6612 if (newSnap.isLeafNode() || newSnap.isEmpty()) {
6613 // Make sure we have a children node with the correct index, not a leaf node;
6614 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6615 }
6616 else {
6617 if (this.limit_ * 2 < newSnap.numChildren() &&
6618 newSnap.isIndexed(this.index_)) {
6619 // Easier to build up a snapshot, since what we're given has more than twice the elements we want
6620 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6621 // anchor to the startPost, endPost, or last element as appropriate
6622 var iterator = void 0;
6623 if (this.reverse_) {
6624 iterator = newSnap.getReverseIteratorFrom(this.rangedFilter_.getEndPost(), this.index_);
6625 }
6626 else {
6627 iterator = newSnap.getIteratorFrom(this.rangedFilter_.getStartPost(), this.index_);
6628 }
6629 var count = 0;
6630 while (iterator.hasNext() && count < this.limit_) {
6631 var next = iterator.getNext();
6632 var inRange = void 0;
6633 if (this.reverse_) {
6634 inRange =
6635 this.index_.compare(this.rangedFilter_.getStartPost(), next) <= 0;
6636 }
6637 else {
6638 inRange =
6639 this.index_.compare(next, this.rangedFilter_.getEndPost()) <= 0;
6640 }
6641 if (inRange) {
6642 filtered = filtered.updateImmediateChild(next.name, next.node);
6643 count++;
6644 }
6645 else {
6646 // if we have reached the end post, we cannot keep adding elemments
6647 break;
6648 }
6649 }
6650 }
6651 else {
6652 // The snap contains less than twice the limit. Faster to delete from the snap than build up a new one
6653 filtered = newSnap.withIndex(this.index_);
6654 // Don't support priorities on queries
6655 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6656 var startPost = void 0;
6657 var endPost = void 0;
6658 var cmp = void 0;
6659 var iterator = void 0;
6660 if (this.reverse_) {
6661 iterator = filtered.getReverseIterator(this.index_);
6662 startPost = this.rangedFilter_.getEndPost();
6663 endPost = this.rangedFilter_.getStartPost();
6664 var indexCompare_1 = this.index_.getCompare();
6665 cmp = function (a, b) { return indexCompare_1(b, a); };
6666 }
6667 else {
6668 iterator = filtered.getIterator(this.index_);
6669 startPost = this.rangedFilter_.getStartPost();
6670 endPost = this.rangedFilter_.getEndPost();
6671 cmp = this.index_.getCompare();
6672 }
6673 var count = 0;
6674 var foundStartPost = false;
6675 while (iterator.hasNext()) {
6676 var next = iterator.getNext();
6677 if (!foundStartPost && cmp(startPost, next) <= 0) {
6678 // start adding
6679 foundStartPost = true;
6680 }
6681 var inRange = foundStartPost && count < this.limit_ && cmp(next, endPost) <= 0;
6682 if (inRange) {
6683 count++;
6684 }
6685 else {
6686 filtered = filtered.updateImmediateChild(next.name, ChildrenNode.EMPTY_NODE);
6687 }
6688 }
6689 }
6690 }
6691 return this.rangedFilter_
6692 .getIndexedFilter()
6693 .updateFullNode(oldSnap, filtered, optChangeAccumulator);
6694 };
6695 LimitedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6696 // Don't support priorities on queries
6697 return oldSnap;
6698 };
6699 LimitedFilter.prototype.filtersNodes = function () {
6700 return true;
6701 };
6702 LimitedFilter.prototype.getIndexedFilter = function () {
6703 return this.rangedFilter_.getIndexedFilter();
6704 };
6705 LimitedFilter.prototype.getIndex = function () {
6706 return this.index_;
6707 };
6708 LimitedFilter.prototype.fullLimitUpdateChild_ = function (snap, childKey, childSnap, source, changeAccumulator) {
6709 // TODO: rename all cache stuff etc to general snap terminology
6710 var cmp;
6711 if (this.reverse_) {
6712 var indexCmp_1 = this.index_.getCompare();
6713 cmp = function (a, b) { return indexCmp_1(b, a); };
6714 }
6715 else {
6716 cmp = this.index_.getCompare();
6717 }
6718 var oldEventCache = snap;
6719 util.assert(oldEventCache.numChildren() === this.limit_, '');
6720 var newChildNamedNode = new NamedNode(childKey, childSnap);
6721 var windowBoundary = this.reverse_
6722 ? oldEventCache.getFirstChild(this.index_)
6723 : oldEventCache.getLastChild(this.index_);
6724 var inRange = this.rangedFilter_.matches(newChildNamedNode);
6725 if (oldEventCache.hasChild(childKey)) {
6726 var oldChildSnap = oldEventCache.getImmediateChild(childKey);
6727 var nextChild = source.getChildAfterChild(this.index_, windowBoundary, this.reverse_);
6728 while (nextChild != null &&
6729 (nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))) {
6730 // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
6731 // been applied to the limited filter yet. Ignore this next child which will be updated later in
6732 // the limited filter...
6733 nextChild = source.getChildAfterChild(this.index_, nextChild, this.reverse_);
6734 }
6735 var compareNext = nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
6736 var remainsInWindow = inRange && !childSnap.isEmpty() && compareNext >= 0;
6737 if (remainsInWindow) {
6738 if (changeAccumulator != null) {
6739 changeAccumulator.trackChildChange(changeChildChanged(childKey, childSnap, oldChildSnap));
6740 }
6741 return oldEventCache.updateImmediateChild(childKey, childSnap);
6742 }
6743 else {
6744 if (changeAccumulator != null) {
6745 changeAccumulator.trackChildChange(changeChildRemoved(childKey, oldChildSnap));
6746 }
6747 var newEventCache = oldEventCache.updateImmediateChild(childKey, ChildrenNode.EMPTY_NODE);
6748 var nextChildInRange = nextChild != null && this.rangedFilter_.matches(nextChild);
6749 if (nextChildInRange) {
6750 if (changeAccumulator != null) {
6751 changeAccumulator.trackChildChange(changeChildAdded(nextChild.name, nextChild.node));
6752 }
6753 return newEventCache.updateImmediateChild(nextChild.name, nextChild.node);
6754 }
6755 else {
6756 return newEventCache;
6757 }
6758 }
6759 }
6760 else if (childSnap.isEmpty()) {
6761 // we're deleting a node, but it was not in the window, so ignore it
6762 return snap;
6763 }
6764 else if (inRange) {
6765 if (cmp(windowBoundary, newChildNamedNode) >= 0) {
6766 if (changeAccumulator != null) {
6767 changeAccumulator.trackChildChange(changeChildRemoved(windowBoundary.name, windowBoundary.node));
6768 changeAccumulator.trackChildChange(changeChildAdded(childKey, childSnap));
6769 }
6770 return oldEventCache
6771 .updateImmediateChild(childKey, childSnap)
6772 .updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
6773 }
6774 else {
6775 return snap;
6776 }
6777 }
6778 else {
6779 return snap;
6780 }
6781 };
6782 return LimitedFilter;
6783}());
6784
6785/**
6786 * @license
6787 * Copyright 2017 Google LLC
6788 *
6789 * Licensed under the Apache License, Version 2.0 (the "License");
6790 * you may not use this file except in compliance with the License.
6791 * You may obtain a copy of the License at
6792 *
6793 * http://www.apache.org/licenses/LICENSE-2.0
6794 *
6795 * Unless required by applicable law or agreed to in writing, software
6796 * distributed under the License is distributed on an "AS IS" BASIS,
6797 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6798 * See the License for the specific language governing permissions and
6799 * limitations under the License.
6800 */
6801/**
6802 * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a
6803 * range to be returned for a particular location. It is assumed that validation of parameters is done at the
6804 * user-facing API level, so it is not done here.
6805 *
6806 * @internal
6807 */
6808var QueryParams = /** @class */ (function () {
6809 function QueryParams() {
6810 this.limitSet_ = false;
6811 this.startSet_ = false;
6812 this.startNameSet_ = false;
6813 this.startAfterSet_ = false;
6814 this.endSet_ = false;
6815 this.endNameSet_ = false;
6816 this.endBeforeSet_ = false;
6817 this.limit_ = 0;
6818 this.viewFrom_ = '';
6819 this.indexStartValue_ = null;
6820 this.indexStartName_ = '';
6821 this.indexEndValue_ = null;
6822 this.indexEndName_ = '';
6823 this.index_ = PRIORITY_INDEX;
6824 }
6825 QueryParams.prototype.hasStart = function () {
6826 return this.startSet_;
6827 };
6828 QueryParams.prototype.hasStartAfter = function () {
6829 return this.startAfterSet_;
6830 };
6831 QueryParams.prototype.hasEndBefore = function () {
6832 return this.endBeforeSet_;
6833 };
6834 /**
6835 * @returns True if it would return from left.
6836 */
6837 QueryParams.prototype.isViewFromLeft = function () {
6838 if (this.viewFrom_ === '') {
6839 // limit(), rather than limitToFirst or limitToLast was called.
6840 // This means that only one of startSet_ and endSet_ is true. Use them
6841 // to calculate which side of the view to anchor to. If neither is set,
6842 // anchor to the end.
6843 return this.startSet_;
6844 }
6845 else {
6846 return this.viewFrom_ === "l" /* VIEW_FROM_LEFT */;
6847 }
6848 };
6849 /**
6850 * Only valid to call if hasStart() returns true
6851 */
6852 QueryParams.prototype.getIndexStartValue = function () {
6853 util.assert(this.startSet_, 'Only valid if start has been set');
6854 return this.indexStartValue_;
6855 };
6856 /**
6857 * Only valid to call if hasStart() returns true.
6858 * Returns the starting key name for the range defined by these query parameters
6859 */
6860 QueryParams.prototype.getIndexStartName = function () {
6861 util.assert(this.startSet_, 'Only valid if start has been set');
6862 if (this.startNameSet_) {
6863 return this.indexStartName_;
6864 }
6865 else {
6866 return MIN_NAME;
6867 }
6868 };
6869 QueryParams.prototype.hasEnd = function () {
6870 return this.endSet_;
6871 };
6872 /**
6873 * Only valid to call if hasEnd() returns true.
6874 */
6875 QueryParams.prototype.getIndexEndValue = function () {
6876 util.assert(this.endSet_, 'Only valid if end has been set');
6877 return this.indexEndValue_;
6878 };
6879 /**
6880 * Only valid to call if hasEnd() returns true.
6881 * Returns the end key name for the range defined by these query parameters
6882 */
6883 QueryParams.prototype.getIndexEndName = function () {
6884 util.assert(this.endSet_, 'Only valid if end has been set');
6885 if (this.endNameSet_) {
6886 return this.indexEndName_;
6887 }
6888 else {
6889 return MAX_NAME;
6890 }
6891 };
6892 QueryParams.prototype.hasLimit = function () {
6893 return this.limitSet_;
6894 };
6895 /**
6896 * @returns True if a limit has been set and it has been explicitly anchored
6897 */
6898 QueryParams.prototype.hasAnchoredLimit = function () {
6899 return this.limitSet_ && this.viewFrom_ !== '';
6900 };
6901 /**
6902 * Only valid to call if hasLimit() returns true
6903 */
6904 QueryParams.prototype.getLimit = function () {
6905 util.assert(this.limitSet_, 'Only valid if limit has been set');
6906 return this.limit_;
6907 };
6908 QueryParams.prototype.getIndex = function () {
6909 return this.index_;
6910 };
6911 QueryParams.prototype.loadsAllData = function () {
6912 return !(this.startSet_ || this.endSet_ || this.limitSet_);
6913 };
6914 QueryParams.prototype.isDefault = function () {
6915 return this.loadsAllData() && this.index_ === PRIORITY_INDEX;
6916 };
6917 QueryParams.prototype.copy = function () {
6918 var copy = new QueryParams();
6919 copy.limitSet_ = this.limitSet_;
6920 copy.limit_ = this.limit_;
6921 copy.startSet_ = this.startSet_;
6922 copy.indexStartValue_ = this.indexStartValue_;
6923 copy.startNameSet_ = this.startNameSet_;
6924 copy.indexStartName_ = this.indexStartName_;
6925 copy.endSet_ = this.endSet_;
6926 copy.indexEndValue_ = this.indexEndValue_;
6927 copy.endNameSet_ = this.endNameSet_;
6928 copy.indexEndName_ = this.indexEndName_;
6929 copy.index_ = this.index_;
6930 copy.viewFrom_ = this.viewFrom_;
6931 return copy;
6932 };
6933 return QueryParams;
6934}());
6935function queryParamsGetNodeFilter(queryParams) {
6936 if (queryParams.loadsAllData()) {
6937 return new IndexedFilter(queryParams.getIndex());
6938 }
6939 else if (queryParams.hasLimit()) {
6940 return new LimitedFilter(queryParams);
6941 }
6942 else {
6943 return new RangedFilter(queryParams);
6944 }
6945}
6946function queryParamsLimitToFirst(queryParams, newLimit) {
6947 var newParams = queryParams.copy();
6948 newParams.limitSet_ = true;
6949 newParams.limit_ = newLimit;
6950 newParams.viewFrom_ = "l" /* VIEW_FROM_LEFT */;
6951 return newParams;
6952}
6953function queryParamsLimitToLast(queryParams, newLimit) {
6954 var newParams = queryParams.copy();
6955 newParams.limitSet_ = true;
6956 newParams.limit_ = newLimit;
6957 newParams.viewFrom_ = "r" /* VIEW_FROM_RIGHT */;
6958 return newParams;
6959}
6960function queryParamsStartAt(queryParams, indexValue, key) {
6961 var newParams = queryParams.copy();
6962 newParams.startSet_ = true;
6963 if (indexValue === undefined) {
6964 indexValue = null;
6965 }
6966 newParams.indexStartValue_ = indexValue;
6967 if (key != null) {
6968 newParams.startNameSet_ = true;
6969 newParams.indexStartName_ = key;
6970 }
6971 else {
6972 newParams.startNameSet_ = false;
6973 newParams.indexStartName_ = '';
6974 }
6975 return newParams;
6976}
6977function queryParamsStartAfter(queryParams, indexValue, key) {
6978 var params;
6979 if (queryParams.index_ === KEY_INDEX) {
6980 if (typeof indexValue === 'string') {
6981 indexValue = successor(indexValue);
6982 }
6983 params = queryParamsStartAt(queryParams, indexValue, key);
6984 }
6985 else {
6986 var childKey = void 0;
6987 if (key == null) {
6988 childKey = MAX_NAME;
6989 }
6990 else {
6991 childKey = successor(key);
6992 }
6993 params = queryParamsStartAt(queryParams, indexValue, childKey);
6994 }
6995 params.startAfterSet_ = true;
6996 return params;
6997}
6998function queryParamsEndAt(queryParams, indexValue, key) {
6999 var newParams = queryParams.copy();
7000 newParams.endSet_ = true;
7001 if (indexValue === undefined) {
7002 indexValue = null;
7003 }
7004 newParams.indexEndValue_ = indexValue;
7005 if (key !== undefined) {
7006 newParams.endNameSet_ = true;
7007 newParams.indexEndName_ = key;
7008 }
7009 else {
7010 newParams.endNameSet_ = false;
7011 newParams.indexEndName_ = '';
7012 }
7013 return newParams;
7014}
7015function queryParamsEndBefore(queryParams, indexValue, key) {
7016 var childKey;
7017 var params;
7018 if (queryParams.index_ === KEY_INDEX) {
7019 if (typeof indexValue === 'string') {
7020 indexValue = predecessor(indexValue);
7021 }
7022 params = queryParamsEndAt(queryParams, indexValue, key);
7023 }
7024 else {
7025 if (key == null) {
7026 childKey = MIN_NAME;
7027 }
7028 else {
7029 childKey = predecessor(key);
7030 }
7031 params = queryParamsEndAt(queryParams, indexValue, childKey);
7032 }
7033 params.endBeforeSet_ = true;
7034 return params;
7035}
7036function queryParamsOrderBy(queryParams, index) {
7037 var newParams = queryParams.copy();
7038 newParams.index_ = index;
7039 return newParams;
7040}
7041/**
7042 * Returns a set of REST query string parameters representing this query.
7043 *
7044 * @returns query string parameters
7045 */
7046function queryParamsToRestQueryStringParameters(queryParams) {
7047 var qs = {};
7048 if (queryParams.isDefault()) {
7049 return qs;
7050 }
7051 var orderBy;
7052 if (queryParams.index_ === PRIORITY_INDEX) {
7053 orderBy = "$priority" /* PRIORITY_INDEX */;
7054 }
7055 else if (queryParams.index_ === VALUE_INDEX) {
7056 orderBy = "$value" /* VALUE_INDEX */;
7057 }
7058 else if (queryParams.index_ === KEY_INDEX) {
7059 orderBy = "$key" /* KEY_INDEX */;
7060 }
7061 else {
7062 util.assert(queryParams.index_ instanceof PathIndex, 'Unrecognized index type!');
7063 orderBy = queryParams.index_.toString();
7064 }
7065 qs["orderBy" /* ORDER_BY */] = util.stringify(orderBy);
7066 if (queryParams.startSet_) {
7067 qs["startAt" /* START_AT */] = util.stringify(queryParams.indexStartValue_);
7068 if (queryParams.startNameSet_) {
7069 qs["startAt" /* START_AT */] +=
7070 ',' + util.stringify(queryParams.indexStartName_);
7071 }
7072 }
7073 if (queryParams.endSet_) {
7074 qs["endAt" /* END_AT */] = util.stringify(queryParams.indexEndValue_);
7075 if (queryParams.endNameSet_) {
7076 qs["endAt" /* END_AT */] +=
7077 ',' + util.stringify(queryParams.indexEndName_);
7078 }
7079 }
7080 if (queryParams.limitSet_) {
7081 if (queryParams.isViewFromLeft()) {
7082 qs["limitToFirst" /* LIMIT_TO_FIRST */] = queryParams.limit_;
7083 }
7084 else {
7085 qs["limitToLast" /* LIMIT_TO_LAST */] = queryParams.limit_;
7086 }
7087 }
7088 return qs;
7089}
7090function queryParamsGetQueryObject(queryParams) {
7091 var obj = {};
7092 if (queryParams.startSet_) {
7093 obj["sp" /* INDEX_START_VALUE */] =
7094 queryParams.indexStartValue_;
7095 if (queryParams.startNameSet_) {
7096 obj["sn" /* INDEX_START_NAME */] =
7097 queryParams.indexStartName_;
7098 }
7099 }
7100 if (queryParams.endSet_) {
7101 obj["ep" /* INDEX_END_VALUE */] = queryParams.indexEndValue_;
7102 if (queryParams.endNameSet_) {
7103 obj["en" /* INDEX_END_NAME */] = queryParams.indexEndName_;
7104 }
7105 }
7106 if (queryParams.limitSet_) {
7107 obj["l" /* LIMIT */] = queryParams.limit_;
7108 var viewFrom = queryParams.viewFrom_;
7109 if (viewFrom === '') {
7110 if (queryParams.isViewFromLeft()) {
7111 viewFrom = "l" /* VIEW_FROM_LEFT */;
7112 }
7113 else {
7114 viewFrom = "r" /* VIEW_FROM_RIGHT */;
7115 }
7116 }
7117 obj["vf" /* VIEW_FROM */] = viewFrom;
7118 }
7119 // For now, priority index is the default, so we only specify if it's some other index
7120 if (queryParams.index_ !== PRIORITY_INDEX) {
7121 obj["i" /* INDEX */] = queryParams.index_.toString();
7122 }
7123 return obj;
7124}
7125
7126/**
7127 * @license
7128 * Copyright 2017 Google LLC
7129 *
7130 * Licensed under the Apache License, Version 2.0 (the "License");
7131 * you may not use this file except in compliance with the License.
7132 * You may obtain a copy of the License at
7133 *
7134 * http://www.apache.org/licenses/LICENSE-2.0
7135 *
7136 * Unless required by applicable law or agreed to in writing, software
7137 * distributed under the License is distributed on an "AS IS" BASIS,
7138 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7139 * See the License for the specific language governing permissions and
7140 * limitations under the License.
7141 */
7142/**
7143 * An implementation of ServerActions that communicates with the server via REST requests.
7144 * This is mostly useful for compatibility with crawlers, where we don't want to spin up a full
7145 * persistent connection (using WebSockets or long-polling)
7146 */
7147var ReadonlyRestClient = /** @class */ (function (_super) {
7148 tslib.__extends(ReadonlyRestClient, _super);
7149 /**
7150 * @param repoInfo_ - Data about the namespace we are connecting to
7151 * @param onDataUpdate_ - A callback for new data from the server
7152 */
7153 function ReadonlyRestClient(repoInfo_, onDataUpdate_, authTokenProvider_, appCheckTokenProvider_) {
7154 var _this = _super.call(this) || this;
7155 _this.repoInfo_ = repoInfo_;
7156 _this.onDataUpdate_ = onDataUpdate_;
7157 _this.authTokenProvider_ = authTokenProvider_;
7158 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
7159 /** @private {function(...[*])} */
7160 _this.log_ = logWrapper('p:rest:');
7161 /**
7162 * We don't actually need to track listens, except to prevent us calling an onComplete for a listen
7163 * that's been removed. :-/
7164 */
7165 _this.listens_ = {};
7166 return _this;
7167 }
7168 ReadonlyRestClient.prototype.reportStats = function (stats) {
7169 throw new Error('Method not implemented.');
7170 };
7171 ReadonlyRestClient.getListenId_ = function (query, tag) {
7172 if (tag !== undefined) {
7173 return 'tag$' + tag;
7174 }
7175 else {
7176 util.assert(query._queryParams.isDefault(), "should have a tag if it's not a default query.");
7177 return query._path.toString();
7178 }
7179 };
7180 /** @inheritDoc */
7181 ReadonlyRestClient.prototype.listen = function (query, currentHashFn, tag, onComplete) {
7182 var _this = this;
7183 var pathString = query._path.toString();
7184 this.log_('Listen called for ' + pathString + ' ' + query._queryIdentifier);
7185 // Mark this listener so we can tell if it's removed.
7186 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7187 var thisListen = {};
7188 this.listens_[listenId] = thisListen;
7189 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7190 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7191 var data = result;
7192 if (error === 404) {
7193 data = null;
7194 error = null;
7195 }
7196 if (error === null) {
7197 _this.onDataUpdate_(pathString, data, /*isMerge=*/ false, tag);
7198 }
7199 if (util.safeGet(_this.listens_, listenId) === thisListen) {
7200 var status_1;
7201 if (!error) {
7202 status_1 = 'ok';
7203 }
7204 else if (error === 401) {
7205 status_1 = 'permission_denied';
7206 }
7207 else {
7208 status_1 = 'rest_error:' + error;
7209 }
7210 onComplete(status_1, null);
7211 }
7212 });
7213 };
7214 /** @inheritDoc */
7215 ReadonlyRestClient.prototype.unlisten = function (query, tag) {
7216 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7217 delete this.listens_[listenId];
7218 };
7219 ReadonlyRestClient.prototype.get = function (query) {
7220 var _this = this;
7221 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7222 var pathString = query._path.toString();
7223 var deferred = new util.Deferred();
7224 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7225 var data = result;
7226 if (error === 404) {
7227 data = null;
7228 error = null;
7229 }
7230 if (error === null) {
7231 _this.onDataUpdate_(pathString, data,
7232 /*isMerge=*/ false,
7233 /*tag=*/ null);
7234 deferred.resolve(data);
7235 }
7236 else {
7237 deferred.reject(new Error(data));
7238 }
7239 });
7240 return deferred.promise;
7241 };
7242 /** @inheritDoc */
7243 ReadonlyRestClient.prototype.refreshAuthToken = function (token) {
7244 // no-op since we just always call getToken.
7245 };
7246 /**
7247 * Performs a REST request to the given path, with the provided query string parameters,
7248 * and any auth credentials we have.
7249 */
7250 ReadonlyRestClient.prototype.restRequest_ = function (pathString, queryStringParameters, callback) {
7251 var _this = this;
7252 if (queryStringParameters === void 0) { queryStringParameters = {}; }
7253 queryStringParameters['format'] = 'export';
7254 return Promise.all([
7255 this.authTokenProvider_.getToken(/*forceRefresh=*/ false),
7256 this.appCheckTokenProvider_.getToken(/*forceRefresh=*/ false)
7257 ]).then(function (_a) {
7258 var _b = tslib.__read(_a, 2), authToken = _b[0], appCheckToken = _b[1];
7259 if (authToken && authToken.accessToken) {
7260 queryStringParameters['auth'] = authToken.accessToken;
7261 }
7262 if (appCheckToken && appCheckToken.token) {
7263 queryStringParameters['ac'] = appCheckToken.token;
7264 }
7265 var url = (_this.repoInfo_.secure ? 'https://' : 'http://') +
7266 _this.repoInfo_.host +
7267 pathString +
7268 '?' +
7269 'ns=' +
7270 _this.repoInfo_.namespace +
7271 util.querystring(queryStringParameters);
7272 _this.log_('Sending REST request for ' + url);
7273 var xhr = new XMLHttpRequest();
7274 xhr.onreadystatechange = function () {
7275 if (callback && xhr.readyState === 4) {
7276 _this.log_('REST Response for ' + url + ' received. status:', xhr.status, 'response:', xhr.responseText);
7277 var res = null;
7278 if (xhr.status >= 200 && xhr.status < 300) {
7279 try {
7280 res = util.jsonEval(xhr.responseText);
7281 }
7282 catch (e) {
7283 warn('Failed to parse JSON response for ' +
7284 url +
7285 ': ' +
7286 xhr.responseText);
7287 }
7288 callback(null, res);
7289 }
7290 else {
7291 // 401 and 404 are expected.
7292 if (xhr.status !== 401 && xhr.status !== 404) {
7293 warn('Got unsuccessful REST response for ' +
7294 url +
7295 ' Status: ' +
7296 xhr.status);
7297 }
7298 callback(xhr.status);
7299 }
7300 callback = null;
7301 }
7302 };
7303 xhr.open('GET', url, /*asynchronous=*/ true);
7304 xhr.send();
7305 });
7306 };
7307 return ReadonlyRestClient;
7308}(ServerActions));
7309
7310/**
7311 * @license
7312 * Copyright 2017 Google LLC
7313 *
7314 * Licensed under the Apache License, Version 2.0 (the "License");
7315 * you may not use this file except in compliance with the License.
7316 * You may obtain a copy of the License at
7317 *
7318 * http://www.apache.org/licenses/LICENSE-2.0
7319 *
7320 * Unless required by applicable law or agreed to in writing, software
7321 * distributed under the License is distributed on an "AS IS" BASIS,
7322 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7323 * See the License for the specific language governing permissions and
7324 * limitations under the License.
7325 */
7326/**
7327 * Mutable object which basically just stores a reference to the "latest" immutable snapshot.
7328 */
7329var SnapshotHolder = /** @class */ (function () {
7330 function SnapshotHolder() {
7331 this.rootNode_ = ChildrenNode.EMPTY_NODE;
7332 }
7333 SnapshotHolder.prototype.getNode = function (path) {
7334 return this.rootNode_.getChild(path);
7335 };
7336 SnapshotHolder.prototype.updateSnapshot = function (path, newSnapshotNode) {
7337 this.rootNode_ = this.rootNode_.updateChild(path, newSnapshotNode);
7338 };
7339 return SnapshotHolder;
7340}());
7341
7342/**
7343 * @license
7344 * Copyright 2017 Google LLC
7345 *
7346 * Licensed under the Apache License, Version 2.0 (the "License");
7347 * you may not use this file except in compliance with the License.
7348 * You may obtain a copy of the License at
7349 *
7350 * http://www.apache.org/licenses/LICENSE-2.0
7351 *
7352 * Unless required by applicable law or agreed to in writing, software
7353 * distributed under the License is distributed on an "AS IS" BASIS,
7354 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7355 * See the License for the specific language governing permissions and
7356 * limitations under the License.
7357 */
7358function newSparseSnapshotTree() {
7359 return {
7360 value: null,
7361 children: new Map()
7362 };
7363}
7364/**
7365 * Stores the given node at the specified path. If there is already a node
7366 * at a shallower path, it merges the new data into that snapshot node.
7367 *
7368 * @param path - Path to look up snapshot for.
7369 * @param data - The new data, or null.
7370 */
7371function sparseSnapshotTreeRemember(sparseSnapshotTree, path, data) {
7372 if (pathIsEmpty(path)) {
7373 sparseSnapshotTree.value = data;
7374 sparseSnapshotTree.children.clear();
7375 }
7376 else if (sparseSnapshotTree.value !== null) {
7377 sparseSnapshotTree.value = sparseSnapshotTree.value.updateChild(path, data);
7378 }
7379 else {
7380 var childKey = pathGetFront(path);
7381 if (!sparseSnapshotTree.children.has(childKey)) {
7382 sparseSnapshotTree.children.set(childKey, newSparseSnapshotTree());
7383 }
7384 var child = sparseSnapshotTree.children.get(childKey);
7385 path = pathPopFront(path);
7386 sparseSnapshotTreeRemember(child, path, data);
7387 }
7388}
7389/**
7390 * Purge the data at path from the cache.
7391 *
7392 * @param path - Path to look up snapshot for.
7393 * @returns True if this node should now be removed.
7394 */
7395function sparseSnapshotTreeForget(sparseSnapshotTree, path) {
7396 if (pathIsEmpty(path)) {
7397 sparseSnapshotTree.value = null;
7398 sparseSnapshotTree.children.clear();
7399 return true;
7400 }
7401 else {
7402 if (sparseSnapshotTree.value !== null) {
7403 if (sparseSnapshotTree.value.isLeafNode()) {
7404 // We're trying to forget a node that doesn't exist
7405 return false;
7406 }
7407 else {
7408 var value = sparseSnapshotTree.value;
7409 sparseSnapshotTree.value = null;
7410 value.forEachChild(PRIORITY_INDEX, function (key, tree) {
7411 sparseSnapshotTreeRemember(sparseSnapshotTree, new Path(key), tree);
7412 });
7413 return sparseSnapshotTreeForget(sparseSnapshotTree, path);
7414 }
7415 }
7416 else if (sparseSnapshotTree.children.size > 0) {
7417 var childKey = pathGetFront(path);
7418 path = pathPopFront(path);
7419 if (sparseSnapshotTree.children.has(childKey)) {
7420 var safeToRemove = sparseSnapshotTreeForget(sparseSnapshotTree.children.get(childKey), path);
7421 if (safeToRemove) {
7422 sparseSnapshotTree.children.delete(childKey);
7423 }
7424 }
7425 return sparseSnapshotTree.children.size === 0;
7426 }
7427 else {
7428 return true;
7429 }
7430 }
7431}
7432/**
7433 * Recursively iterates through all of the stored tree and calls the
7434 * callback on each one.
7435 *
7436 * @param prefixPath - Path to look up node for.
7437 * @param func - The function to invoke for each tree.
7438 */
7439function sparseSnapshotTreeForEachTree(sparseSnapshotTree, prefixPath, func) {
7440 if (sparseSnapshotTree.value !== null) {
7441 func(prefixPath, sparseSnapshotTree.value);
7442 }
7443 else {
7444 sparseSnapshotTreeForEachChild(sparseSnapshotTree, function (key, tree) {
7445 var path = new Path(prefixPath.toString() + '/' + key);
7446 sparseSnapshotTreeForEachTree(tree, path, func);
7447 });
7448 }
7449}
7450/**
7451 * Iterates through each immediate child and triggers the callback.
7452 * Only seems to be used in tests.
7453 *
7454 * @param func - The function to invoke for each child.
7455 */
7456function sparseSnapshotTreeForEachChild(sparseSnapshotTree, func) {
7457 sparseSnapshotTree.children.forEach(function (tree, key) {
7458 func(key, tree);
7459 });
7460}
7461
7462/**
7463 * @license
7464 * Copyright 2017 Google LLC
7465 *
7466 * Licensed under the Apache License, Version 2.0 (the "License");
7467 * you may not use this file except in compliance with the License.
7468 * You may obtain a copy of the License at
7469 *
7470 * http://www.apache.org/licenses/LICENSE-2.0
7471 *
7472 * Unless required by applicable law or agreed to in writing, software
7473 * distributed under the License is distributed on an "AS IS" BASIS,
7474 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7475 * See the License for the specific language governing permissions and
7476 * limitations under the License.
7477 */
7478/**
7479 * Returns the delta from the previous call to get stats.
7480 *
7481 * @param collection_ - The collection to "listen" to.
7482 */
7483var StatsListener = /** @class */ (function () {
7484 function StatsListener(collection_) {
7485 this.collection_ = collection_;
7486 this.last_ = null;
7487 }
7488 StatsListener.prototype.get = function () {
7489 var newStats = this.collection_.get();
7490 var delta = tslib.__assign({}, newStats);
7491 if (this.last_) {
7492 each(this.last_, function (stat, value) {
7493 delta[stat] = delta[stat] - value;
7494 });
7495 }
7496 this.last_ = newStats;
7497 return delta;
7498 };
7499 return StatsListener;
7500}());
7501
7502/**
7503 * @license
7504 * Copyright 2017 Google LLC
7505 *
7506 * Licensed under the Apache License, Version 2.0 (the "License");
7507 * you may not use this file except in compliance with the License.
7508 * You may obtain a copy of the License at
7509 *
7510 * http://www.apache.org/licenses/LICENSE-2.0
7511 *
7512 * Unless required by applicable law or agreed to in writing, software
7513 * distributed under the License is distributed on an "AS IS" BASIS,
7514 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7515 * See the License for the specific language governing permissions and
7516 * limitations under the License.
7517 */
7518// Assuming some apps may have a short amount of time on page, and a bulk of firebase operations probably
7519// happen on page load, we try to report our first set of stats pretty quickly, but we wait at least 10
7520// seconds to try to ensure the Firebase connection is established / settled.
7521var FIRST_STATS_MIN_TIME = 10 * 1000;
7522var FIRST_STATS_MAX_TIME = 30 * 1000;
7523// We'll continue to report stats on average every 5 minutes.
7524var REPORT_STATS_INTERVAL = 5 * 60 * 1000;
7525var StatsReporter = /** @class */ (function () {
7526 function StatsReporter(collection, server_) {
7527 this.server_ = server_;
7528 this.statsToReport_ = {};
7529 this.statsListener_ = new StatsListener(collection);
7530 var timeout = FIRST_STATS_MIN_TIME +
7531 (FIRST_STATS_MAX_TIME - FIRST_STATS_MIN_TIME) * Math.random();
7532 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(timeout));
7533 }
7534 StatsReporter.prototype.reportStats_ = function () {
7535 var _this = this;
7536 var stats = this.statsListener_.get();
7537 var reportedStats = {};
7538 var haveStatsToReport = false;
7539 each(stats, function (stat, value) {
7540 if (value > 0 && util.contains(_this.statsToReport_, stat)) {
7541 reportedStats[stat] = value;
7542 haveStatsToReport = true;
7543 }
7544 });
7545 if (haveStatsToReport) {
7546 this.server_.reportStats(reportedStats);
7547 }
7548 // queue our next run.
7549 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(Math.random() * 2 * REPORT_STATS_INTERVAL));
7550 };
7551 return StatsReporter;
7552}());
7553
7554/**
7555 * @license
7556 * Copyright 2017 Google LLC
7557 *
7558 * Licensed under the Apache License, Version 2.0 (the "License");
7559 * you may not use this file except in compliance with the License.
7560 * You may obtain a copy of the License at
7561 *
7562 * http://www.apache.org/licenses/LICENSE-2.0
7563 *
7564 * Unless required by applicable law or agreed to in writing, software
7565 * distributed under the License is distributed on an "AS IS" BASIS,
7566 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7567 * See the License for the specific language governing permissions and
7568 * limitations under the License.
7569 */
7570/**
7571 *
7572 * @enum
7573 */
7574var OperationType;
7575(function (OperationType) {
7576 OperationType[OperationType["OVERWRITE"] = 0] = "OVERWRITE";
7577 OperationType[OperationType["MERGE"] = 1] = "MERGE";
7578 OperationType[OperationType["ACK_USER_WRITE"] = 2] = "ACK_USER_WRITE";
7579 OperationType[OperationType["LISTEN_COMPLETE"] = 3] = "LISTEN_COMPLETE";
7580})(OperationType || (OperationType = {}));
7581function newOperationSourceUser() {
7582 return {
7583 fromUser: true,
7584 fromServer: false,
7585 queryId: null,
7586 tagged: false
7587 };
7588}
7589function newOperationSourceServer() {
7590 return {
7591 fromUser: false,
7592 fromServer: true,
7593 queryId: null,
7594 tagged: false
7595 };
7596}
7597function newOperationSourceServerTaggedQuery(queryId) {
7598 return {
7599 fromUser: false,
7600 fromServer: true,
7601 queryId: queryId,
7602 tagged: true
7603 };
7604}
7605
7606/**
7607 * @license
7608 * Copyright 2017 Google LLC
7609 *
7610 * Licensed under the Apache License, Version 2.0 (the "License");
7611 * you may not use this file except in compliance with the License.
7612 * You may obtain a copy of the License at
7613 *
7614 * http://www.apache.org/licenses/LICENSE-2.0
7615 *
7616 * Unless required by applicable law or agreed to in writing, software
7617 * distributed under the License is distributed on an "AS IS" BASIS,
7618 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7619 * See the License for the specific language governing permissions and
7620 * limitations under the License.
7621 */
7622var AckUserWrite = /** @class */ (function () {
7623 /**
7624 * @param affectedTree - A tree containing true for each affected path. Affected paths can't overlap.
7625 */
7626 function AckUserWrite(
7627 /** @inheritDoc */ path,
7628 /** @inheritDoc */ affectedTree,
7629 /** @inheritDoc */ revert) {
7630 this.path = path;
7631 this.affectedTree = affectedTree;
7632 this.revert = revert;
7633 /** @inheritDoc */
7634 this.type = OperationType.ACK_USER_WRITE;
7635 /** @inheritDoc */
7636 this.source = newOperationSourceUser();
7637 }
7638 AckUserWrite.prototype.operationForChild = function (childName) {
7639 if (!pathIsEmpty(this.path)) {
7640 util.assert(pathGetFront(this.path) === childName, 'operationForChild called for unrelated child.');
7641 return new AckUserWrite(pathPopFront(this.path), this.affectedTree, this.revert);
7642 }
7643 else if (this.affectedTree.value != null) {
7644 util.assert(this.affectedTree.children.isEmpty(), 'affectedTree should not have overlapping affected paths.');
7645 // All child locations are affected as well; just return same operation.
7646 return this;
7647 }
7648 else {
7649 var childTree = this.affectedTree.subtree(new Path(childName));
7650 return new AckUserWrite(newEmptyPath(), childTree, this.revert);
7651 }
7652 };
7653 return AckUserWrite;
7654}());
7655
7656/**
7657 * @license
7658 * Copyright 2017 Google LLC
7659 *
7660 * Licensed under the Apache License, Version 2.0 (the "License");
7661 * you may not use this file except in compliance with the License.
7662 * You may obtain a copy of the License at
7663 *
7664 * http://www.apache.org/licenses/LICENSE-2.0
7665 *
7666 * Unless required by applicable law or agreed to in writing, software
7667 * distributed under the License is distributed on an "AS IS" BASIS,
7668 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7669 * See the License for the specific language governing permissions and
7670 * limitations under the License.
7671 */
7672var ListenComplete = /** @class */ (function () {
7673 function ListenComplete(source, path) {
7674 this.source = source;
7675 this.path = path;
7676 /** @inheritDoc */
7677 this.type = OperationType.LISTEN_COMPLETE;
7678 }
7679 ListenComplete.prototype.operationForChild = function (childName) {
7680 if (pathIsEmpty(this.path)) {
7681 return new ListenComplete(this.source, newEmptyPath());
7682 }
7683 else {
7684 return new ListenComplete(this.source, pathPopFront(this.path));
7685 }
7686 };
7687 return ListenComplete;
7688}());
7689
7690/**
7691 * @license
7692 * Copyright 2017 Google LLC
7693 *
7694 * Licensed under the Apache License, Version 2.0 (the "License");
7695 * you may not use this file except in compliance with the License.
7696 * You may obtain a copy of the License at
7697 *
7698 * http://www.apache.org/licenses/LICENSE-2.0
7699 *
7700 * Unless required by applicable law or agreed to in writing, software
7701 * distributed under the License is distributed on an "AS IS" BASIS,
7702 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7703 * See the License for the specific language governing permissions and
7704 * limitations under the License.
7705 */
7706var Overwrite = /** @class */ (function () {
7707 function Overwrite(source, path, snap) {
7708 this.source = source;
7709 this.path = path;
7710 this.snap = snap;
7711 /** @inheritDoc */
7712 this.type = OperationType.OVERWRITE;
7713 }
7714 Overwrite.prototype.operationForChild = function (childName) {
7715 if (pathIsEmpty(this.path)) {
7716 return new Overwrite(this.source, newEmptyPath(), this.snap.getImmediateChild(childName));
7717 }
7718 else {
7719 return new Overwrite(this.source, pathPopFront(this.path), this.snap);
7720 }
7721 };
7722 return Overwrite;
7723}());
7724
7725/**
7726 * @license
7727 * Copyright 2017 Google LLC
7728 *
7729 * Licensed under the Apache License, Version 2.0 (the "License");
7730 * you may not use this file except in compliance with the License.
7731 * You may obtain a copy of the License at
7732 *
7733 * http://www.apache.org/licenses/LICENSE-2.0
7734 *
7735 * Unless required by applicable law or agreed to in writing, software
7736 * distributed under the License is distributed on an "AS IS" BASIS,
7737 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7738 * See the License for the specific language governing permissions and
7739 * limitations under the License.
7740 */
7741var Merge = /** @class */ (function () {
7742 function Merge(
7743 /** @inheritDoc */ source,
7744 /** @inheritDoc */ path,
7745 /** @inheritDoc */ children) {
7746 this.source = source;
7747 this.path = path;
7748 this.children = children;
7749 /** @inheritDoc */
7750 this.type = OperationType.MERGE;
7751 }
7752 Merge.prototype.operationForChild = function (childName) {
7753 if (pathIsEmpty(this.path)) {
7754 var childTree = this.children.subtree(new Path(childName));
7755 if (childTree.isEmpty()) {
7756 // This child is unaffected
7757 return null;
7758 }
7759 else if (childTree.value) {
7760 // We have a snapshot for the child in question. This becomes an overwrite of the child.
7761 return new Overwrite(this.source, newEmptyPath(), childTree.value);
7762 }
7763 else {
7764 // This is a merge at a deeper level
7765 return new Merge(this.source, newEmptyPath(), childTree);
7766 }
7767 }
7768 else {
7769 util.assert(pathGetFront(this.path) === childName, "Can't get a merge for a child not on the path of the operation");
7770 return new Merge(this.source, pathPopFront(this.path), this.children);
7771 }
7772 };
7773 Merge.prototype.toString = function () {
7774 return ('Operation(' +
7775 this.path +
7776 ': ' +
7777 this.source.toString() +
7778 ' merge: ' +
7779 this.children.toString() +
7780 ')');
7781 };
7782 return Merge;
7783}());
7784
7785/**
7786 * @license
7787 * Copyright 2017 Google LLC
7788 *
7789 * Licensed under the Apache License, Version 2.0 (the "License");
7790 * you may not use this file except in compliance with the License.
7791 * You may obtain a copy of the License at
7792 *
7793 * http://www.apache.org/licenses/LICENSE-2.0
7794 *
7795 * Unless required by applicable law or agreed to in writing, software
7796 * distributed under the License is distributed on an "AS IS" BASIS,
7797 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7798 * See the License for the specific language governing permissions and
7799 * limitations under the License.
7800 */
7801/**
7802 * A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
7803 * initialized in the sense that we know at one point in time this represented a valid state of the world, e.g.
7804 * initialized with data from the server, or a complete overwrite by the client. The filtered flag also tracks
7805 * whether a node potentially had children removed due to a filter.
7806 */
7807var CacheNode = /** @class */ (function () {
7808 function CacheNode(node_, fullyInitialized_, filtered_) {
7809 this.node_ = node_;
7810 this.fullyInitialized_ = fullyInitialized_;
7811 this.filtered_ = filtered_;
7812 }
7813 /**
7814 * Returns whether this node was fully initialized with either server data or a complete overwrite by the client
7815 */
7816 CacheNode.prototype.isFullyInitialized = function () {
7817 return this.fullyInitialized_;
7818 };
7819 /**
7820 * Returns whether this node is potentially missing children due to a filter applied to the node
7821 */
7822 CacheNode.prototype.isFiltered = function () {
7823 return this.filtered_;
7824 };
7825 CacheNode.prototype.isCompleteForPath = function (path) {
7826 if (pathIsEmpty(path)) {
7827 return this.isFullyInitialized() && !this.filtered_;
7828 }
7829 var childKey = pathGetFront(path);
7830 return this.isCompleteForChild(childKey);
7831 };
7832 CacheNode.prototype.isCompleteForChild = function (key) {
7833 return ((this.isFullyInitialized() && !this.filtered_) || this.node_.hasChild(key));
7834 };
7835 CacheNode.prototype.getNode = function () {
7836 return this.node_;
7837 };
7838 return CacheNode;
7839}());
7840
7841/**
7842 * @license
7843 * Copyright 2017 Google LLC
7844 *
7845 * Licensed under the Apache License, Version 2.0 (the "License");
7846 * you may not use this file except in compliance with the License.
7847 * You may obtain a copy of the License at
7848 *
7849 * http://www.apache.org/licenses/LICENSE-2.0
7850 *
7851 * Unless required by applicable law or agreed to in writing, software
7852 * distributed under the License is distributed on an "AS IS" BASIS,
7853 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7854 * See the License for the specific language governing permissions and
7855 * limitations under the License.
7856 */
7857/**
7858 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
7859 * CacheDiffer into actual events (Event) that can be raised. See generateEventsForChanges()
7860 * for details.
7861 *
7862 */
7863var EventGenerator = /** @class */ (function () {
7864 function EventGenerator(query_) {
7865 this.query_ = query_;
7866 this.index_ = this.query_._queryParams.getIndex();
7867 }
7868 return EventGenerator;
7869}());
7870/**
7871 * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
7872 * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
7873 *
7874 * Notes:
7875 * - child_moved events will be synthesized at this time for any child_changed events that affect
7876 * our index.
7877 * - prevName will be calculated based on the index ordering.
7878 */
7879function eventGeneratorGenerateEventsForChanges(eventGenerator, changes, eventCache, eventRegistrations) {
7880 var events = [];
7881 var moves = [];
7882 changes.forEach(function (change) {
7883 if (change.type === "child_changed" /* CHILD_CHANGED */ &&
7884 eventGenerator.index_.indexedValueChanged(change.oldSnap, change.snapshotNode)) {
7885 moves.push(changeChildMoved(change.childName, change.snapshotNode));
7886 }
7887 });
7888 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_removed" /* CHILD_REMOVED */, changes, eventRegistrations, eventCache);
7889 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_added" /* CHILD_ADDED */, changes, eventRegistrations, eventCache);
7890 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_moved" /* CHILD_MOVED */, moves, eventRegistrations, eventCache);
7891 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_changed" /* CHILD_CHANGED */, changes, eventRegistrations, eventCache);
7892 eventGeneratorGenerateEventsForType(eventGenerator, events, "value" /* VALUE */, changes, eventRegistrations, eventCache);
7893 return events;
7894}
7895/**
7896 * Given changes of a single change type, generate the corresponding events.
7897 */
7898function eventGeneratorGenerateEventsForType(eventGenerator, events, eventType, changes, registrations, eventCache) {
7899 var filteredChanges = changes.filter(function (change) { return change.type === eventType; });
7900 filteredChanges.sort(function (a, b) {
7901 return eventGeneratorCompareChanges(eventGenerator, a, b);
7902 });
7903 filteredChanges.forEach(function (change) {
7904 var materializedChange = eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache);
7905 registrations.forEach(function (registration) {
7906 if (registration.respondsTo(change.type)) {
7907 events.push(registration.createEvent(materializedChange, eventGenerator.query_));
7908 }
7909 });
7910 });
7911}
7912function eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache) {
7913 if (change.type === 'value' || change.type === 'child_removed') {
7914 return change;
7915 }
7916 else {
7917 change.prevName = eventCache.getPredecessorChildName(change.childName, change.snapshotNode, eventGenerator.index_);
7918 return change;
7919 }
7920}
7921function eventGeneratorCompareChanges(eventGenerator, a, b) {
7922 if (a.childName == null || b.childName == null) {
7923 throw util.assertionError('Should only compare child_ events.');
7924 }
7925 var aWrapped = new NamedNode(a.childName, a.snapshotNode);
7926 var bWrapped = new NamedNode(b.childName, b.snapshotNode);
7927 return eventGenerator.index_.compare(aWrapped, bWrapped);
7928}
7929
7930/**
7931 * @license
7932 * Copyright 2017 Google LLC
7933 *
7934 * Licensed under the Apache License, Version 2.0 (the "License");
7935 * you may not use this file except in compliance with the License.
7936 * You may obtain a copy of the License at
7937 *
7938 * http://www.apache.org/licenses/LICENSE-2.0
7939 *
7940 * Unless required by applicable law or agreed to in writing, software
7941 * distributed under the License is distributed on an "AS IS" BASIS,
7942 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7943 * See the License for the specific language governing permissions and
7944 * limitations under the License.
7945 */
7946function newViewCache(eventCache, serverCache) {
7947 return { eventCache: eventCache, serverCache: serverCache };
7948}
7949function viewCacheUpdateEventSnap(viewCache, eventSnap, complete, filtered) {
7950 return newViewCache(new CacheNode(eventSnap, complete, filtered), viewCache.serverCache);
7951}
7952function viewCacheUpdateServerSnap(viewCache, serverSnap, complete, filtered) {
7953 return newViewCache(viewCache.eventCache, new CacheNode(serverSnap, complete, filtered));
7954}
7955function viewCacheGetCompleteEventSnap(viewCache) {
7956 return viewCache.eventCache.isFullyInitialized()
7957 ? viewCache.eventCache.getNode()
7958 : null;
7959}
7960function viewCacheGetCompleteServerSnap(viewCache) {
7961 return viewCache.serverCache.isFullyInitialized()
7962 ? viewCache.serverCache.getNode()
7963 : null;
7964}
7965
7966/**
7967 * @license
7968 * Copyright 2017 Google LLC
7969 *
7970 * Licensed under the Apache License, Version 2.0 (the "License");
7971 * you may not use this file except in compliance with the License.
7972 * You may obtain a copy of the License at
7973 *
7974 * http://www.apache.org/licenses/LICENSE-2.0
7975 *
7976 * Unless required by applicable law or agreed to in writing, software
7977 * distributed under the License is distributed on an "AS IS" BASIS,
7978 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7979 * See the License for the specific language governing permissions and
7980 * limitations under the License.
7981 */
7982var emptyChildrenSingleton;
7983/**
7984 * Singleton empty children collection.
7985 *
7986 */
7987var EmptyChildren = function () {
7988 if (!emptyChildrenSingleton) {
7989 emptyChildrenSingleton = new SortedMap(stringCompare);
7990 }
7991 return emptyChildrenSingleton;
7992};
7993/**
7994 * A tree with immutable elements.
7995 */
7996var ImmutableTree = /** @class */ (function () {
7997 function ImmutableTree(value, children) {
7998 if (children === void 0) { children = EmptyChildren(); }
7999 this.value = value;
8000 this.children = children;
8001 }
8002 ImmutableTree.fromObject = function (obj) {
8003 var tree = new ImmutableTree(null);
8004 each(obj, function (childPath, childSnap) {
8005 tree = tree.set(new Path(childPath), childSnap);
8006 });
8007 return tree;
8008 };
8009 /**
8010 * True if the value is empty and there are no children
8011 */
8012 ImmutableTree.prototype.isEmpty = function () {
8013 return this.value === null && this.children.isEmpty();
8014 };
8015 /**
8016 * Given a path and predicate, return the first node and the path to that node
8017 * where the predicate returns true.
8018 *
8019 * TODO Do a perf test -- If we're creating a bunch of `{path: value:}`
8020 * objects on the way back out, it may be better to pass down a pathSoFar obj.
8021 *
8022 * @param relativePath - The remainder of the path
8023 * @param predicate - The predicate to satisfy to return a node
8024 */
8025 ImmutableTree.prototype.findRootMostMatchingPathAndValue = function (relativePath, predicate) {
8026 if (this.value != null && predicate(this.value)) {
8027 return { path: newEmptyPath(), value: this.value };
8028 }
8029 else {
8030 if (pathIsEmpty(relativePath)) {
8031 return null;
8032 }
8033 else {
8034 var front = pathGetFront(relativePath);
8035 var child = this.children.get(front);
8036 if (child !== null) {
8037 var childExistingPathAndValue = child.findRootMostMatchingPathAndValue(pathPopFront(relativePath), predicate);
8038 if (childExistingPathAndValue != null) {
8039 var fullPath = pathChild(new Path(front), childExistingPathAndValue.path);
8040 return { path: fullPath, value: childExistingPathAndValue.value };
8041 }
8042 else {
8043 return null;
8044 }
8045 }
8046 else {
8047 return null;
8048 }
8049 }
8050 }
8051 };
8052 /**
8053 * Find, if it exists, the shortest subpath of the given path that points a defined
8054 * value in the tree
8055 */
8056 ImmutableTree.prototype.findRootMostValueAndPath = function (relativePath) {
8057 return this.findRootMostMatchingPathAndValue(relativePath, function () { return true; });
8058 };
8059 /**
8060 * @returns The subtree at the given path
8061 */
8062 ImmutableTree.prototype.subtree = function (relativePath) {
8063 if (pathIsEmpty(relativePath)) {
8064 return this;
8065 }
8066 else {
8067 var front = pathGetFront(relativePath);
8068 var childTree = this.children.get(front);
8069 if (childTree !== null) {
8070 return childTree.subtree(pathPopFront(relativePath));
8071 }
8072 else {
8073 return new ImmutableTree(null);
8074 }
8075 }
8076 };
8077 /**
8078 * Sets a value at the specified path.
8079 *
8080 * @param relativePath - Path to set value at.
8081 * @param toSet - Value to set.
8082 * @returns Resulting tree.
8083 */
8084 ImmutableTree.prototype.set = function (relativePath, toSet) {
8085 if (pathIsEmpty(relativePath)) {
8086 return new ImmutableTree(toSet, this.children);
8087 }
8088 else {
8089 var front = pathGetFront(relativePath);
8090 var child = this.children.get(front) || new ImmutableTree(null);
8091 var newChild = child.set(pathPopFront(relativePath), toSet);
8092 var newChildren = this.children.insert(front, newChild);
8093 return new ImmutableTree(this.value, newChildren);
8094 }
8095 };
8096 /**
8097 * Removes the value at the specified path.
8098 *
8099 * @param relativePath - Path to value to remove.
8100 * @returns Resulting tree.
8101 */
8102 ImmutableTree.prototype.remove = function (relativePath) {
8103 if (pathIsEmpty(relativePath)) {
8104 if (this.children.isEmpty()) {
8105 return new ImmutableTree(null);
8106 }
8107 else {
8108 return new ImmutableTree(null, this.children);
8109 }
8110 }
8111 else {
8112 var front = pathGetFront(relativePath);
8113 var child = this.children.get(front);
8114 if (child) {
8115 var newChild = child.remove(pathPopFront(relativePath));
8116 var newChildren = void 0;
8117 if (newChild.isEmpty()) {
8118 newChildren = this.children.remove(front);
8119 }
8120 else {
8121 newChildren = this.children.insert(front, newChild);
8122 }
8123 if (this.value === null && newChildren.isEmpty()) {
8124 return new ImmutableTree(null);
8125 }
8126 else {
8127 return new ImmutableTree(this.value, newChildren);
8128 }
8129 }
8130 else {
8131 return this;
8132 }
8133 }
8134 };
8135 /**
8136 * Gets a value from the tree.
8137 *
8138 * @param relativePath - Path to get value for.
8139 * @returns Value at path, or null.
8140 */
8141 ImmutableTree.prototype.get = function (relativePath) {
8142 if (pathIsEmpty(relativePath)) {
8143 return this.value;
8144 }
8145 else {
8146 var front = pathGetFront(relativePath);
8147 var child = this.children.get(front);
8148 if (child) {
8149 return child.get(pathPopFront(relativePath));
8150 }
8151 else {
8152 return null;
8153 }
8154 }
8155 };
8156 /**
8157 * Replace the subtree at the specified path with the given new tree.
8158 *
8159 * @param relativePath - Path to replace subtree for.
8160 * @param newTree - New tree.
8161 * @returns Resulting tree.
8162 */
8163 ImmutableTree.prototype.setTree = function (relativePath, newTree) {
8164 if (pathIsEmpty(relativePath)) {
8165 return newTree;
8166 }
8167 else {
8168 var front = pathGetFront(relativePath);
8169 var child = this.children.get(front) || new ImmutableTree(null);
8170 var newChild = child.setTree(pathPopFront(relativePath), newTree);
8171 var newChildren = void 0;
8172 if (newChild.isEmpty()) {
8173 newChildren = this.children.remove(front);
8174 }
8175 else {
8176 newChildren = this.children.insert(front, newChild);
8177 }
8178 return new ImmutableTree(this.value, newChildren);
8179 }
8180 };
8181 /**
8182 * Performs a depth first fold on this tree. Transforms a tree into a single
8183 * value, given a function that operates on the path to a node, an optional
8184 * current value, and a map of child names to folded subtrees
8185 */
8186 ImmutableTree.prototype.fold = function (fn) {
8187 return this.fold_(newEmptyPath(), fn);
8188 };
8189 /**
8190 * Recursive helper for public-facing fold() method
8191 */
8192 ImmutableTree.prototype.fold_ = function (pathSoFar, fn) {
8193 var accum = {};
8194 this.children.inorderTraversal(function (childKey, childTree) {
8195 accum[childKey] = childTree.fold_(pathChild(pathSoFar, childKey), fn);
8196 });
8197 return fn(pathSoFar, this.value, accum);
8198 };
8199 /**
8200 * Find the first matching value on the given path. Return the result of applying f to it.
8201 */
8202 ImmutableTree.prototype.findOnPath = function (path, f) {
8203 return this.findOnPath_(path, newEmptyPath(), f);
8204 };
8205 ImmutableTree.prototype.findOnPath_ = function (pathToFollow, pathSoFar, f) {
8206 var result = this.value ? f(pathSoFar, this.value) : false;
8207 if (result) {
8208 return result;
8209 }
8210 else {
8211 if (pathIsEmpty(pathToFollow)) {
8212 return null;
8213 }
8214 else {
8215 var front = pathGetFront(pathToFollow);
8216 var nextChild = this.children.get(front);
8217 if (nextChild) {
8218 return nextChild.findOnPath_(pathPopFront(pathToFollow), pathChild(pathSoFar, front), f);
8219 }
8220 else {
8221 return null;
8222 }
8223 }
8224 }
8225 };
8226 ImmutableTree.prototype.foreachOnPath = function (path, f) {
8227 return this.foreachOnPath_(path, newEmptyPath(), f);
8228 };
8229 ImmutableTree.prototype.foreachOnPath_ = function (pathToFollow, currentRelativePath, f) {
8230 if (pathIsEmpty(pathToFollow)) {
8231 return this;
8232 }
8233 else {
8234 if (this.value) {
8235 f(currentRelativePath, this.value);
8236 }
8237 var front = pathGetFront(pathToFollow);
8238 var nextChild = this.children.get(front);
8239 if (nextChild) {
8240 return nextChild.foreachOnPath_(pathPopFront(pathToFollow), pathChild(currentRelativePath, front), f);
8241 }
8242 else {
8243 return new ImmutableTree(null);
8244 }
8245 }
8246 };
8247 /**
8248 * Calls the given function for each node in the tree that has a value.
8249 *
8250 * @param f - A function to be called with the path from the root of the tree to
8251 * a node, and the value at that node. Called in depth-first order.
8252 */
8253 ImmutableTree.prototype.foreach = function (f) {
8254 this.foreach_(newEmptyPath(), f);
8255 };
8256 ImmutableTree.prototype.foreach_ = function (currentRelativePath, f) {
8257 this.children.inorderTraversal(function (childName, childTree) {
8258 childTree.foreach_(pathChild(currentRelativePath, childName), f);
8259 });
8260 if (this.value) {
8261 f(currentRelativePath, this.value);
8262 }
8263 };
8264 ImmutableTree.prototype.foreachChild = function (f) {
8265 this.children.inorderTraversal(function (childName, childTree) {
8266 if (childTree.value) {
8267 f(childName, childTree.value);
8268 }
8269 });
8270 };
8271 return ImmutableTree;
8272}());
8273
8274/**
8275 * @license
8276 * Copyright 2017 Google LLC
8277 *
8278 * Licensed under the Apache License, Version 2.0 (the "License");
8279 * you may not use this file except in compliance with the License.
8280 * You may obtain a copy of the License at
8281 *
8282 * http://www.apache.org/licenses/LICENSE-2.0
8283 *
8284 * Unless required by applicable law or agreed to in writing, software
8285 * distributed under the License is distributed on an "AS IS" BASIS,
8286 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8287 * See the License for the specific language governing permissions and
8288 * limitations under the License.
8289 */
8290/**
8291 * This class holds a collection of writes that can be applied to nodes in unison. It abstracts away the logic with
8292 * dealing with priority writes and multiple nested writes. At any given path there is only allowed to be one write
8293 * modifying that path. Any write to an existing path or shadowing an existing path will modify that existing write
8294 * to reflect the write added.
8295 */
8296var CompoundWrite = /** @class */ (function () {
8297 function CompoundWrite(writeTree_) {
8298 this.writeTree_ = writeTree_;
8299 }
8300 CompoundWrite.empty = function () {
8301 return new CompoundWrite(new ImmutableTree(null));
8302 };
8303 return CompoundWrite;
8304}());
8305function compoundWriteAddWrite(compoundWrite, path, node) {
8306 if (pathIsEmpty(path)) {
8307 return new CompoundWrite(new ImmutableTree(node));
8308 }
8309 else {
8310 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8311 if (rootmost != null) {
8312 var rootMostPath = rootmost.path;
8313 var value = rootmost.value;
8314 var relativePath = newRelativePath(rootMostPath, path);
8315 value = value.updateChild(relativePath, node);
8316 return new CompoundWrite(compoundWrite.writeTree_.set(rootMostPath, value));
8317 }
8318 else {
8319 var subtree = new ImmutableTree(node);
8320 var newWriteTree = compoundWrite.writeTree_.setTree(path, subtree);
8321 return new CompoundWrite(newWriteTree);
8322 }
8323 }
8324}
8325function compoundWriteAddWrites(compoundWrite, path, updates) {
8326 var newWrite = compoundWrite;
8327 each(updates, function (childKey, node) {
8328 newWrite = compoundWriteAddWrite(newWrite, pathChild(path, childKey), node);
8329 });
8330 return newWrite;
8331}
8332/**
8333 * Will remove a write at the given path and deeper paths. This will <em>not</em> modify a write at a higher
8334 * location, which must be removed by calling this method with that path.
8335 *
8336 * @param compoundWrite - The CompoundWrite to remove.
8337 * @param path - The path at which a write and all deeper writes should be removed
8338 * @returns The new CompoundWrite with the removed path
8339 */
8340function compoundWriteRemoveWrite(compoundWrite, path) {
8341 if (pathIsEmpty(path)) {
8342 return CompoundWrite.empty();
8343 }
8344 else {
8345 var newWriteTree = compoundWrite.writeTree_.setTree(path, new ImmutableTree(null));
8346 return new CompoundWrite(newWriteTree);
8347 }
8348}
8349/**
8350 * Returns whether this CompoundWrite will fully overwrite a node at a given location and can therefore be
8351 * considered "complete".
8352 *
8353 * @param compoundWrite - The CompoundWrite to check.
8354 * @param path - The path to check for
8355 * @returns Whether there is a complete write at that path
8356 */
8357function compoundWriteHasCompleteWrite(compoundWrite, path) {
8358 return compoundWriteGetCompleteNode(compoundWrite, path) != null;
8359}
8360/**
8361 * Returns a node for a path if and only if the node is a "complete" overwrite at that path. This will not aggregate
8362 * writes from deeper paths, but will return child nodes from a more shallow path.
8363 *
8364 * @param compoundWrite - The CompoundWrite to get the node from.
8365 * @param path - The path to get a complete write
8366 * @returns The node if complete at that path, or null otherwise.
8367 */
8368function compoundWriteGetCompleteNode(compoundWrite, path) {
8369 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8370 if (rootmost != null) {
8371 return compoundWrite.writeTree_
8372 .get(rootmost.path)
8373 .getChild(newRelativePath(rootmost.path, path));
8374 }
8375 else {
8376 return null;
8377 }
8378}
8379/**
8380 * Returns all children that are guaranteed to be a complete overwrite.
8381 *
8382 * @param compoundWrite - The CompoundWrite to get children from.
8383 * @returns A list of all complete children.
8384 */
8385function compoundWriteGetCompleteChildren(compoundWrite) {
8386 var children = [];
8387 var node = compoundWrite.writeTree_.value;
8388 if (node != null) {
8389 // If it's a leaf node, it has no children; so nothing to do.
8390 if (!node.isLeafNode()) {
8391 node.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8392 children.push(new NamedNode(childName, childNode));
8393 });
8394 }
8395 }
8396 else {
8397 compoundWrite.writeTree_.children.inorderTraversal(function (childName, childTree) {
8398 if (childTree.value != null) {
8399 children.push(new NamedNode(childName, childTree.value));
8400 }
8401 });
8402 }
8403 return children;
8404}
8405function compoundWriteChildCompoundWrite(compoundWrite, path) {
8406 if (pathIsEmpty(path)) {
8407 return compoundWrite;
8408 }
8409 else {
8410 var shadowingNode = compoundWriteGetCompleteNode(compoundWrite, path);
8411 if (shadowingNode != null) {
8412 return new CompoundWrite(new ImmutableTree(shadowingNode));
8413 }
8414 else {
8415 return new CompoundWrite(compoundWrite.writeTree_.subtree(path));
8416 }
8417 }
8418}
8419/**
8420 * Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
8421 * @returns Whether this CompoundWrite is empty
8422 */
8423function compoundWriteIsEmpty(compoundWrite) {
8424 return compoundWrite.writeTree_.isEmpty();
8425}
8426/**
8427 * Applies this CompoundWrite to a node. The node is returned with all writes from this CompoundWrite applied to the
8428 * node
8429 * @param node - The node to apply this CompoundWrite to
8430 * @returns The node with all writes applied
8431 */
8432function compoundWriteApply(compoundWrite, node) {
8433 return applySubtreeWrite(newEmptyPath(), compoundWrite.writeTree_, node);
8434}
8435function applySubtreeWrite(relativePath, writeTree, node) {
8436 if (writeTree.value != null) {
8437 // Since there a write is always a leaf, we're done here
8438 return node.updateChild(relativePath, writeTree.value);
8439 }
8440 else {
8441 var priorityWrite_1 = null;
8442 writeTree.children.inorderTraversal(function (childKey, childTree) {
8443 if (childKey === '.priority') {
8444 // Apply priorities at the end so we don't update priorities for either empty nodes or forget
8445 // to apply priorities to empty nodes that are later filled
8446 util.assert(childTree.value !== null, 'Priority writes must always be leaf nodes');
8447 priorityWrite_1 = childTree.value;
8448 }
8449 else {
8450 node = applySubtreeWrite(pathChild(relativePath, childKey), childTree, node);
8451 }
8452 });
8453 // If there was a priority write, we only apply it if the node is not empty
8454 if (!node.getChild(relativePath).isEmpty() && priorityWrite_1 !== null) {
8455 node = node.updateChild(pathChild(relativePath, '.priority'), priorityWrite_1);
8456 }
8457 return node;
8458 }
8459}
8460
8461/**
8462 * @license
8463 * Copyright 2017 Google LLC
8464 *
8465 * Licensed under the Apache License, Version 2.0 (the "License");
8466 * you may not use this file except in compliance with the License.
8467 * You may obtain a copy of the License at
8468 *
8469 * http://www.apache.org/licenses/LICENSE-2.0
8470 *
8471 * Unless required by applicable law or agreed to in writing, software
8472 * distributed under the License is distributed on an "AS IS" BASIS,
8473 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8474 * See the License for the specific language governing permissions and
8475 * limitations under the License.
8476 */
8477/**
8478 * Create a new WriteTreeRef for the given path. For use with a new sync point at the given path.
8479 *
8480 */
8481function writeTreeChildWrites(writeTree, path) {
8482 return newWriteTreeRef(path, writeTree);
8483}
8484/**
8485 * Record a new overwrite from user code.
8486 *
8487 * @param visible - This is set to false by some transactions. It should be excluded from event caches
8488 */
8489function writeTreeAddOverwrite(writeTree, path, snap, writeId, visible) {
8490 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older write on top of newer ones');
8491 if (visible === undefined) {
8492 visible = true;
8493 }
8494 writeTree.allWrites.push({
8495 path: path,
8496 snap: snap,
8497 writeId: writeId,
8498 visible: visible
8499 });
8500 if (visible) {
8501 writeTree.visibleWrites = compoundWriteAddWrite(writeTree.visibleWrites, path, snap);
8502 }
8503 writeTree.lastWriteId = writeId;
8504}
8505/**
8506 * Record a new merge from user code.
8507 */
8508function writeTreeAddMerge(writeTree, path, changedChildren, writeId) {
8509 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older merge on top of newer ones');
8510 writeTree.allWrites.push({
8511 path: path,
8512 children: changedChildren,
8513 writeId: writeId,
8514 visible: true
8515 });
8516 writeTree.visibleWrites = compoundWriteAddWrites(writeTree.visibleWrites, path, changedChildren);
8517 writeTree.lastWriteId = writeId;
8518}
8519function writeTreeGetWrite(writeTree, writeId) {
8520 for (var i = 0; i < writeTree.allWrites.length; i++) {
8521 var record = writeTree.allWrites[i];
8522 if (record.writeId === writeId) {
8523 return record;
8524 }
8525 }
8526 return null;
8527}
8528/**
8529 * Remove a write (either an overwrite or merge) that has been successfully acknowledge by the server. Recalculates
8530 * the tree if necessary. We return true if it may have been visible, meaning views need to reevaluate.
8531 *
8532 * @returns true if the write may have been visible (meaning we'll need to reevaluate / raise
8533 * events as a result).
8534 */
8535function writeTreeRemoveWrite(writeTree, writeId) {
8536 // Note: disabling this check. It could be a transaction that preempted another transaction, and thus was applied
8537 // out of order.
8538 //const validClear = revert || this.allWrites_.length === 0 || writeId <= this.allWrites_[0].writeId;
8539 //assert(validClear, "Either we don't have this write, or it's the first one in the queue");
8540 var idx = writeTree.allWrites.findIndex(function (s) {
8541 return s.writeId === writeId;
8542 });
8543 util.assert(idx >= 0, 'removeWrite called with nonexistent writeId.');
8544 var writeToRemove = writeTree.allWrites[idx];
8545 writeTree.allWrites.splice(idx, 1);
8546 var removedWriteWasVisible = writeToRemove.visible;
8547 var removedWriteOverlapsWithOtherWrites = false;
8548 var i = writeTree.allWrites.length - 1;
8549 while (removedWriteWasVisible && i >= 0) {
8550 var currentWrite = writeTree.allWrites[i];
8551 if (currentWrite.visible) {
8552 if (i >= idx &&
8553 writeTreeRecordContainsPath_(currentWrite, writeToRemove.path)) {
8554 // The removed write was completely shadowed by a subsequent write.
8555 removedWriteWasVisible = false;
8556 }
8557 else if (pathContains(writeToRemove.path, currentWrite.path)) {
8558 // Either we're covering some writes or they're covering part of us (depending on which came first).
8559 removedWriteOverlapsWithOtherWrites = true;
8560 }
8561 }
8562 i--;
8563 }
8564 if (!removedWriteWasVisible) {
8565 return false;
8566 }
8567 else if (removedWriteOverlapsWithOtherWrites) {
8568 // There's some shadowing going on. Just rebuild the visible writes from scratch.
8569 writeTreeResetTree_(writeTree);
8570 return true;
8571 }
8572 else {
8573 // There's no shadowing. We can safely just remove the write(s) from visibleWrites.
8574 if (writeToRemove.snap) {
8575 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, writeToRemove.path);
8576 }
8577 else {
8578 var children = writeToRemove.children;
8579 each(children, function (childName) {
8580 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, pathChild(writeToRemove.path, childName));
8581 });
8582 }
8583 return true;
8584 }
8585}
8586function writeTreeRecordContainsPath_(writeRecord, path) {
8587 if (writeRecord.snap) {
8588 return pathContains(writeRecord.path, path);
8589 }
8590 else {
8591 for (var childName in writeRecord.children) {
8592 if (writeRecord.children.hasOwnProperty(childName) &&
8593 pathContains(pathChild(writeRecord.path, childName), path)) {
8594 return true;
8595 }
8596 }
8597 return false;
8598 }
8599}
8600/**
8601 * Re-layer the writes and merges into a tree so we can efficiently calculate event snapshots
8602 */
8603function writeTreeResetTree_(writeTree) {
8604 writeTree.visibleWrites = writeTreeLayerTree_(writeTree.allWrites, writeTreeDefaultFilter_, newEmptyPath());
8605 if (writeTree.allWrites.length > 0) {
8606 writeTree.lastWriteId =
8607 writeTree.allWrites[writeTree.allWrites.length - 1].writeId;
8608 }
8609 else {
8610 writeTree.lastWriteId = -1;
8611 }
8612}
8613/**
8614 * The default filter used when constructing the tree. Keep everything that's visible.
8615 */
8616function writeTreeDefaultFilter_(write) {
8617 return write.visible;
8618}
8619/**
8620 * Static method. Given an array of WriteRecords, a filter for which ones to include, and a path, construct the tree of
8621 * event data at that path.
8622 */
8623function writeTreeLayerTree_(writes, filter, treeRoot) {
8624 var compoundWrite = CompoundWrite.empty();
8625 for (var i = 0; i < writes.length; ++i) {
8626 var write = writes[i];
8627 // Theory, a later set will either:
8628 // a) abort a relevant transaction, so no need to worry about excluding it from calculating that transaction
8629 // b) not be relevant to a transaction (separate branch), so again will not affect the data for that transaction
8630 if (filter(write)) {
8631 var writePath = write.path;
8632 var relativePath = void 0;
8633 if (write.snap) {
8634 if (pathContains(treeRoot, writePath)) {
8635 relativePath = newRelativePath(treeRoot, writePath);
8636 compoundWrite = compoundWriteAddWrite(compoundWrite, relativePath, write.snap);
8637 }
8638 else if (pathContains(writePath, treeRoot)) {
8639 relativePath = newRelativePath(writePath, treeRoot);
8640 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), write.snap.getChild(relativePath));
8641 }
8642 else ;
8643 }
8644 else if (write.children) {
8645 if (pathContains(treeRoot, writePath)) {
8646 relativePath = newRelativePath(treeRoot, writePath);
8647 compoundWrite = compoundWriteAddWrites(compoundWrite, relativePath, write.children);
8648 }
8649 else if (pathContains(writePath, treeRoot)) {
8650 relativePath = newRelativePath(writePath, treeRoot);
8651 if (pathIsEmpty(relativePath)) {
8652 compoundWrite = compoundWriteAddWrites(compoundWrite, newEmptyPath(), write.children);
8653 }
8654 else {
8655 var child = util.safeGet(write.children, pathGetFront(relativePath));
8656 if (child) {
8657 // There exists a child in this node that matches the root path
8658 var deepNode = child.getChild(pathPopFront(relativePath));
8659 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), deepNode);
8660 }
8661 }
8662 }
8663 else ;
8664 }
8665 else {
8666 throw util.assertionError('WriteRecord should have .snap or .children');
8667 }
8668 }
8669 }
8670 return compoundWrite;
8671}
8672/**
8673 * Given optional, underlying server data, and an optional set of constraints (exclude some sets, include hidden
8674 * writes), attempt to calculate a complete snapshot for the given path
8675 *
8676 * @param writeIdsToExclude - An optional set to be excluded
8677 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8678 */
8679function writeTreeCalcCompleteEventCache(writeTree, treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8680 if (!writeIdsToExclude && !includeHiddenWrites) {
8681 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8682 if (shadowingNode != null) {
8683 return shadowingNode;
8684 }
8685 else {
8686 var subMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8687 if (compoundWriteIsEmpty(subMerge)) {
8688 return completeServerCache;
8689 }
8690 else if (completeServerCache == null &&
8691 !compoundWriteHasCompleteWrite(subMerge, newEmptyPath())) {
8692 // We wouldn't have a complete snapshot, since there's no underlying data and no complete shadow
8693 return null;
8694 }
8695 else {
8696 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8697 return compoundWriteApply(subMerge, layeredCache);
8698 }
8699 }
8700 }
8701 else {
8702 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8703 if (!includeHiddenWrites && compoundWriteIsEmpty(merge)) {
8704 return completeServerCache;
8705 }
8706 else {
8707 // If the server cache is null, and we don't have a complete cache, we need to return null
8708 if (!includeHiddenWrites &&
8709 completeServerCache == null &&
8710 !compoundWriteHasCompleteWrite(merge, newEmptyPath())) {
8711 return null;
8712 }
8713 else {
8714 var filter = function (write) {
8715 return ((write.visible || includeHiddenWrites) &&
8716 (!writeIdsToExclude ||
8717 !~writeIdsToExclude.indexOf(write.writeId)) &&
8718 (pathContains(write.path, treePath) ||
8719 pathContains(treePath, write.path)));
8720 };
8721 var mergeAtPath = writeTreeLayerTree_(writeTree.allWrites, filter, treePath);
8722 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8723 return compoundWriteApply(mergeAtPath, layeredCache);
8724 }
8725 }
8726 }
8727}
8728/**
8729 * With optional, underlying server data, attempt to return a children node of children that we have complete data for.
8730 * Used when creating new views, to pre-fill their complete event children snapshot.
8731 */
8732function writeTreeCalcCompleteEventChildren(writeTree, treePath, completeServerChildren) {
8733 var completeChildren = ChildrenNode.EMPTY_NODE;
8734 var topLevelSet = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8735 if (topLevelSet) {
8736 if (!topLevelSet.isLeafNode()) {
8737 // we're shadowing everything. Return the children.
8738 topLevelSet.forEachChild(PRIORITY_INDEX, function (childName, childSnap) {
8739 completeChildren = completeChildren.updateImmediateChild(childName, childSnap);
8740 });
8741 }
8742 return completeChildren;
8743 }
8744 else if (completeServerChildren) {
8745 // Layer any children we have on top of this
8746 // We know we don't have a top-level set, so just enumerate existing children
8747 var merge_1 = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8748 completeServerChildren.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8749 var node = compoundWriteApply(compoundWriteChildCompoundWrite(merge_1, new Path(childName)), childNode);
8750 completeChildren = completeChildren.updateImmediateChild(childName, node);
8751 });
8752 // Add any complete children we have from the set
8753 compoundWriteGetCompleteChildren(merge_1).forEach(function (namedNode) {
8754 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8755 });
8756 return completeChildren;
8757 }
8758 else {
8759 // We don't have anything to layer on top of. Layer on any children we have
8760 // Note that we can return an empty snap if we have a defined delete
8761 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8762 compoundWriteGetCompleteChildren(merge).forEach(function (namedNode) {
8763 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8764 });
8765 return completeChildren;
8766 }
8767}
8768/**
8769 * Given that the underlying server data has updated, determine what, if anything, needs to be
8770 * applied to the event cache.
8771 *
8772 * Possibilities:
8773 *
8774 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8775 *
8776 * 2. Some write is completely shadowing. No events to be raised
8777 *
8778 * 3. Is partially shadowed. Events
8779 *
8780 * Either existingEventSnap or existingServerSnap must exist
8781 */
8782function writeTreeCalcEventCacheAfterServerOverwrite(writeTree, treePath, childPath, existingEventSnap, existingServerSnap) {
8783 util.assert(existingEventSnap || existingServerSnap, 'Either existingEventSnap or existingServerSnap must exist');
8784 var path = pathChild(treePath, childPath);
8785 if (compoundWriteHasCompleteWrite(writeTree.visibleWrites, path)) {
8786 // At this point we can probably guarantee that we're in case 2, meaning no events
8787 // May need to check visibility while doing the findRootMostValueAndPath call
8788 return null;
8789 }
8790 else {
8791 // No complete shadowing. We're either partially shadowing or not shadowing at all.
8792 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8793 if (compoundWriteIsEmpty(childMerge)) {
8794 // We're not shadowing at all. Case 1
8795 return existingServerSnap.getChild(childPath);
8796 }
8797 else {
8798 // This could be more efficient if the serverNode + updates doesn't change the eventSnap
8799 // However this is tricky to find out, since user updates don't necessary change the server
8800 // snap, e.g. priority updates on empty nodes, or deep deletes. Another special case is if the server
8801 // adds nodes, but doesn't change any existing writes. It is therefore not enough to
8802 // only check if the updates change the serverNode.
8803 // Maybe check if the merge tree contains these special cases and only do a full overwrite in that case?
8804 return compoundWriteApply(childMerge, existingServerSnap.getChild(childPath));
8805 }
8806 }
8807}
8808/**
8809 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8810 * complete child for this ChildKey.
8811 */
8812function writeTreeCalcCompleteChild(writeTree, treePath, childKey, existingServerSnap) {
8813 var path = pathChild(treePath, childKey);
8814 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8815 if (shadowingNode != null) {
8816 return shadowingNode;
8817 }
8818 else {
8819 if (existingServerSnap.isCompleteForChild(childKey)) {
8820 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8821 return compoundWriteApply(childMerge, existingServerSnap.getNode().getImmediateChild(childKey));
8822 }
8823 else {
8824 return null;
8825 }
8826 }
8827}
8828/**
8829 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8830 * a higher path, this will return the child of that write relative to the write and this path.
8831 * Returns null if there is no write at this path.
8832 */
8833function writeTreeShadowingWrite(writeTree, path) {
8834 return compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8835}
8836/**
8837 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8838 * the window, but may now be in the window.
8839 */
8840function writeTreeCalcIndexedSlice(writeTree, treePath, completeServerData, startPost, count, reverse, index) {
8841 var toIterate;
8842 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8843 var shadowingNode = compoundWriteGetCompleteNode(merge, newEmptyPath());
8844 if (shadowingNode != null) {
8845 toIterate = shadowingNode;
8846 }
8847 else if (completeServerData != null) {
8848 toIterate = compoundWriteApply(merge, completeServerData);
8849 }
8850 else {
8851 // no children to iterate on
8852 return [];
8853 }
8854 toIterate = toIterate.withIndex(index);
8855 if (!toIterate.isEmpty() && !toIterate.isLeafNode()) {
8856 var nodes = [];
8857 var cmp = index.getCompare();
8858 var iter = reverse
8859 ? toIterate.getReverseIteratorFrom(startPost, index)
8860 : toIterate.getIteratorFrom(startPost, index);
8861 var next = iter.getNext();
8862 while (next && nodes.length < count) {
8863 if (cmp(next, startPost) !== 0) {
8864 nodes.push(next);
8865 }
8866 next = iter.getNext();
8867 }
8868 return nodes;
8869 }
8870 else {
8871 return [];
8872 }
8873}
8874function newWriteTree() {
8875 return {
8876 visibleWrites: CompoundWrite.empty(),
8877 allWrites: [],
8878 lastWriteId: -1
8879 };
8880}
8881/**
8882 * If possible, returns a complete event cache, using the underlying server data if possible. In addition, can be used
8883 * to get a cache that includes hidden writes, and excludes arbitrary writes. Note that customizing the returned node
8884 * can lead to a more expensive calculation.
8885 *
8886 * @param writeIdsToExclude - Optional writes to exclude.
8887 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8888 */
8889function writeTreeRefCalcCompleteEventCache(writeTreeRef, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8890 return writeTreeCalcCompleteEventCache(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites);
8891}
8892/**
8893 * If possible, returns a children node containing all of the complete children we have data for. The returned data is a
8894 * mix of the given server data and write data.
8895 *
8896 */
8897function writeTreeRefCalcCompleteEventChildren(writeTreeRef, completeServerChildren) {
8898 return writeTreeCalcCompleteEventChildren(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerChildren);
8899}
8900/**
8901 * Given that either the underlying server data has updated or the outstanding writes have updated, determine what,
8902 * if anything, needs to be applied to the event cache.
8903 *
8904 * Possibilities:
8905 *
8906 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8907 *
8908 * 2. Some write is completely shadowing. No events to be raised
8909 *
8910 * 3. Is partially shadowed. Events should be raised
8911 *
8912 * Either existingEventSnap or existingServerSnap must exist, this is validated via an assert
8913 *
8914 *
8915 */
8916function writeTreeRefCalcEventCacheAfterServerOverwrite(writeTreeRef, path, existingEventSnap, existingServerSnap) {
8917 return writeTreeCalcEventCacheAfterServerOverwrite(writeTreeRef.writeTree, writeTreeRef.treePath, path, existingEventSnap, existingServerSnap);
8918}
8919/**
8920 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8921 * a higher path, this will return the child of that write relative to the write and this path.
8922 * Returns null if there is no write at this path.
8923 *
8924 */
8925function writeTreeRefShadowingWrite(writeTreeRef, path) {
8926 return writeTreeShadowingWrite(writeTreeRef.writeTree, pathChild(writeTreeRef.treePath, path));
8927}
8928/**
8929 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8930 * the window, but may now be in the window
8931 */
8932function writeTreeRefCalcIndexedSlice(writeTreeRef, completeServerData, startPost, count, reverse, index) {
8933 return writeTreeCalcIndexedSlice(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerData, startPost, count, reverse, index);
8934}
8935/**
8936 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8937 * complete child for this ChildKey.
8938 */
8939function writeTreeRefCalcCompleteChild(writeTreeRef, childKey, existingServerCache) {
8940 return writeTreeCalcCompleteChild(writeTreeRef.writeTree, writeTreeRef.treePath, childKey, existingServerCache);
8941}
8942/**
8943 * Return a WriteTreeRef for a child.
8944 */
8945function writeTreeRefChild(writeTreeRef, childName) {
8946 return newWriteTreeRef(pathChild(writeTreeRef.treePath, childName), writeTreeRef.writeTree);
8947}
8948function newWriteTreeRef(path, writeTree) {
8949 return {
8950 treePath: path,
8951 writeTree: writeTree
8952 };
8953}
8954
8955/**
8956 * @license
8957 * Copyright 2017 Google LLC
8958 *
8959 * Licensed under the Apache License, Version 2.0 (the "License");
8960 * you may not use this file except in compliance with the License.
8961 * You may obtain a copy of the License at
8962 *
8963 * http://www.apache.org/licenses/LICENSE-2.0
8964 *
8965 * Unless required by applicable law or agreed to in writing, software
8966 * distributed under the License is distributed on an "AS IS" BASIS,
8967 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8968 * See the License for the specific language governing permissions and
8969 * limitations under the License.
8970 */
8971var ChildChangeAccumulator = /** @class */ (function () {
8972 function ChildChangeAccumulator() {
8973 this.changeMap = new Map();
8974 }
8975 ChildChangeAccumulator.prototype.trackChildChange = function (change) {
8976 var type = change.type;
8977 var childKey = change.childName;
8978 util.assert(type === "child_added" /* CHILD_ADDED */ ||
8979 type === "child_changed" /* CHILD_CHANGED */ ||
8980 type === "child_removed" /* CHILD_REMOVED */, 'Only child changes supported for tracking');
8981 util.assert(childKey !== '.priority', 'Only non-priority child changes can be tracked.');
8982 var oldChange = this.changeMap.get(childKey);
8983 if (oldChange) {
8984 var oldType = oldChange.type;
8985 if (type === "child_added" /* CHILD_ADDED */ &&
8986 oldType === "child_removed" /* CHILD_REMOVED */) {
8987 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.snapshotNode));
8988 }
8989 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8990 oldType === "child_added" /* CHILD_ADDED */) {
8991 this.changeMap.delete(childKey);
8992 }
8993 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8994 oldType === "child_changed" /* CHILD_CHANGED */) {
8995 this.changeMap.set(childKey, changeChildRemoved(childKey, oldChange.oldSnap));
8996 }
8997 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8998 oldType === "child_added" /* CHILD_ADDED */) {
8999 this.changeMap.set(childKey, changeChildAdded(childKey, change.snapshotNode));
9000 }
9001 else if (type === "child_changed" /* CHILD_CHANGED */ &&
9002 oldType === "child_changed" /* CHILD_CHANGED */) {
9003 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.oldSnap));
9004 }
9005 else {
9006 throw util.assertionError('Illegal combination of changes: ' +
9007 change +
9008 ' occurred after ' +
9009 oldChange);
9010 }
9011 }
9012 else {
9013 this.changeMap.set(childKey, change);
9014 }
9015 };
9016 ChildChangeAccumulator.prototype.getChanges = function () {
9017 return Array.from(this.changeMap.values());
9018 };
9019 return ChildChangeAccumulator;
9020}());
9021
9022/**
9023 * @license
9024 * Copyright 2017 Google LLC
9025 *
9026 * Licensed under the Apache License, Version 2.0 (the "License");
9027 * you may not use this file except in compliance with the License.
9028 * You may obtain a copy of the License at
9029 *
9030 * http://www.apache.org/licenses/LICENSE-2.0
9031 *
9032 * Unless required by applicable law or agreed to in writing, software
9033 * distributed under the License is distributed on an "AS IS" BASIS,
9034 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9035 * See the License for the specific language governing permissions and
9036 * limitations under the License.
9037 */
9038/**
9039 * An implementation of CompleteChildSource that never returns any additional children
9040 */
9041// eslint-disable-next-line @typescript-eslint/naming-convention
9042var NoCompleteChildSource_ = /** @class */ (function () {
9043 function NoCompleteChildSource_() {
9044 }
9045 NoCompleteChildSource_.prototype.getCompleteChild = function (childKey) {
9046 return null;
9047 };
9048 NoCompleteChildSource_.prototype.getChildAfterChild = function (index, child, reverse) {
9049 return null;
9050 };
9051 return NoCompleteChildSource_;
9052}());
9053/**
9054 * Singleton instance.
9055 */
9056var NO_COMPLETE_CHILD_SOURCE = new NoCompleteChildSource_();
9057/**
9058 * An implementation of CompleteChildSource that uses a WriteTree in addition to any other server data or
9059 * old event caches available to calculate complete children.
9060 */
9061var WriteTreeCompleteChildSource = /** @class */ (function () {
9062 function WriteTreeCompleteChildSource(writes_, viewCache_, optCompleteServerCache_) {
9063 if (optCompleteServerCache_ === void 0) { optCompleteServerCache_ = null; }
9064 this.writes_ = writes_;
9065 this.viewCache_ = viewCache_;
9066 this.optCompleteServerCache_ = optCompleteServerCache_;
9067 }
9068 WriteTreeCompleteChildSource.prototype.getCompleteChild = function (childKey) {
9069 var node = this.viewCache_.eventCache;
9070 if (node.isCompleteForChild(childKey)) {
9071 return node.getNode().getImmediateChild(childKey);
9072 }
9073 else {
9074 var serverNode = this.optCompleteServerCache_ != null
9075 ? new CacheNode(this.optCompleteServerCache_, true, false)
9076 : this.viewCache_.serverCache;
9077 return writeTreeRefCalcCompleteChild(this.writes_, childKey, serverNode);
9078 }
9079 };
9080 WriteTreeCompleteChildSource.prototype.getChildAfterChild = function (index, child, reverse) {
9081 var completeServerData = this.optCompleteServerCache_ != null
9082 ? this.optCompleteServerCache_
9083 : viewCacheGetCompleteServerSnap(this.viewCache_);
9084 var nodes = writeTreeRefCalcIndexedSlice(this.writes_, completeServerData, child, 1, reverse, index);
9085 if (nodes.length === 0) {
9086 return null;
9087 }
9088 else {
9089 return nodes[0];
9090 }
9091 };
9092 return WriteTreeCompleteChildSource;
9093}());
9094
9095/**
9096 * @license
9097 * Copyright 2017 Google LLC
9098 *
9099 * Licensed under the Apache License, Version 2.0 (the "License");
9100 * you may not use this file except in compliance with the License.
9101 * You may obtain a copy of the License at
9102 *
9103 * http://www.apache.org/licenses/LICENSE-2.0
9104 *
9105 * Unless required by applicable law or agreed to in writing, software
9106 * distributed under the License is distributed on an "AS IS" BASIS,
9107 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9108 * See the License for the specific language governing permissions and
9109 * limitations under the License.
9110 */
9111function newViewProcessor(filter) {
9112 return { filter: filter };
9113}
9114function viewProcessorAssertIndexed(viewProcessor, viewCache) {
9115 util.assert(viewCache.eventCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Event snap not indexed');
9116 util.assert(viewCache.serverCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Server snap not indexed');
9117}
9118function viewProcessorApplyOperation(viewProcessor, oldViewCache, operation, writesCache, completeCache) {
9119 var accumulator = new ChildChangeAccumulator();
9120 var newViewCache, filterServerNode;
9121 if (operation.type === OperationType.OVERWRITE) {
9122 var overwrite = operation;
9123 if (overwrite.source.fromUser) {
9124 newViewCache = viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, accumulator);
9125 }
9126 else {
9127 util.assert(overwrite.source.fromServer, 'Unknown source.');
9128 // We filter the node if it's a tagged update or the node has been previously filtered and the
9129 // update is not at the root in which case it is ok (and necessary) to mark the node unfiltered
9130 // again
9131 filterServerNode =
9132 overwrite.source.tagged ||
9133 (oldViewCache.serverCache.isFiltered() && !pathIsEmpty(overwrite.path));
9134 newViewCache = viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, filterServerNode, accumulator);
9135 }
9136 }
9137 else if (operation.type === OperationType.MERGE) {
9138 var merge = operation;
9139 if (merge.source.fromUser) {
9140 newViewCache = viewProcessorApplyUserMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, accumulator);
9141 }
9142 else {
9143 util.assert(merge.source.fromServer, 'Unknown source.');
9144 // We filter the node if it's a tagged update or the node has been previously filtered
9145 filterServerNode =
9146 merge.source.tagged || oldViewCache.serverCache.isFiltered();
9147 newViewCache = viewProcessorApplyServerMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, filterServerNode, accumulator);
9148 }
9149 }
9150 else if (operation.type === OperationType.ACK_USER_WRITE) {
9151 var ackUserWrite = operation;
9152 if (!ackUserWrite.revert) {
9153 newViewCache = viewProcessorAckUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, ackUserWrite.affectedTree, writesCache, completeCache, accumulator);
9154 }
9155 else {
9156 newViewCache = viewProcessorRevertUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, writesCache, completeCache, accumulator);
9157 }
9158 }
9159 else if (operation.type === OperationType.LISTEN_COMPLETE) {
9160 newViewCache = viewProcessorListenComplete(viewProcessor, oldViewCache, operation.path, writesCache, accumulator);
9161 }
9162 else {
9163 throw util.assertionError('Unknown operation type: ' + operation.type);
9164 }
9165 var changes = accumulator.getChanges();
9166 viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, changes);
9167 return { viewCache: newViewCache, changes: changes };
9168}
9169function viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, accumulator) {
9170 var eventSnap = newViewCache.eventCache;
9171 if (eventSnap.isFullyInitialized()) {
9172 var isLeafOrEmpty = eventSnap.getNode().isLeafNode() || eventSnap.getNode().isEmpty();
9173 var oldCompleteSnap = viewCacheGetCompleteEventSnap(oldViewCache);
9174 if (accumulator.length > 0 ||
9175 !oldViewCache.eventCache.isFullyInitialized() ||
9176 (isLeafOrEmpty && !eventSnap.getNode().equals(oldCompleteSnap)) ||
9177 !eventSnap.getNode().getPriority().equals(oldCompleteSnap.getPriority())) {
9178 accumulator.push(changeValue(viewCacheGetCompleteEventSnap(newViewCache)));
9179 }
9180 }
9181}
9182function viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, viewCache, changePath, writesCache, source, accumulator) {
9183 var oldEventSnap = viewCache.eventCache;
9184 if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
9185 // we have a shadowing write, ignore changes
9186 return viewCache;
9187 }
9188 else {
9189 var newEventCache = void 0, serverNode = void 0;
9190 if (pathIsEmpty(changePath)) {
9191 // TODO: figure out how this plays with "sliding ack windows"
9192 util.assert(viewCache.serverCache.isFullyInitialized(), 'If change path is empty, we must have complete server data');
9193 if (viewCache.serverCache.isFiltered()) {
9194 // We need to special case this, because we need to only apply writes to complete children, or
9195 // we might end up raising events for incomplete children. If the server data is filtered deep
9196 // writes cannot be guaranteed to be complete
9197 var serverCache = viewCacheGetCompleteServerSnap(viewCache);
9198 var completeChildren = serverCache instanceof ChildrenNode
9199 ? serverCache
9200 : ChildrenNode.EMPTY_NODE;
9201 var completeEventChildren = writeTreeRefCalcCompleteEventChildren(writesCache, completeChildren);
9202 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeEventChildren, accumulator);
9203 }
9204 else {
9205 var completeNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9206 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeNode, accumulator);
9207 }
9208 }
9209 else {
9210 var childKey = pathGetFront(changePath);
9211 if (childKey === '.priority') {
9212 util.assert(pathGetLength(changePath) === 1, "Can't have a priority with additional path components");
9213 var oldEventNode = oldEventSnap.getNode();
9214 serverNode = viewCache.serverCache.getNode();
9215 // we might have overwrites for this priority
9216 var updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventNode, serverNode);
9217 if (updatedPriority != null) {
9218 newEventCache = viewProcessor.filter.updatePriority(oldEventNode, updatedPriority);
9219 }
9220 else {
9221 // priority didn't change, keep old node
9222 newEventCache = oldEventSnap.getNode();
9223 }
9224 }
9225 else {
9226 var childChangePath = pathPopFront(changePath);
9227 // update child
9228 var newEventChild = void 0;
9229 if (oldEventSnap.isCompleteForChild(childKey)) {
9230 serverNode = viewCache.serverCache.getNode();
9231 var eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventSnap.getNode(), serverNode);
9232 if (eventChildUpdate != null) {
9233 newEventChild = oldEventSnap
9234 .getNode()
9235 .getImmediateChild(childKey)
9236 .updateChild(childChangePath, eventChildUpdate);
9237 }
9238 else {
9239 // Nothing changed, just keep the old child
9240 newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
9241 }
9242 }
9243 else {
9244 newEventChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9245 }
9246 if (newEventChild != null) {
9247 newEventCache = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newEventChild, childChangePath, source, accumulator);
9248 }
9249 else {
9250 // no complete child available or no change
9251 newEventCache = oldEventSnap.getNode();
9252 }
9253 }
9254 }
9255 return viewCacheUpdateEventSnap(viewCache, newEventCache, oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath), viewProcessor.filter.filtersNodes());
9256 }
9257}
9258function viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, filterServerNode, accumulator) {
9259 var oldServerSnap = oldViewCache.serverCache;
9260 var newServerCache;
9261 var serverFilter = filterServerNode
9262 ? viewProcessor.filter
9263 : viewProcessor.filter.getIndexedFilter();
9264 if (pathIsEmpty(changePath)) {
9265 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), changedSnap, null);
9266 }
9267 else if (serverFilter.filtersNodes() && !oldServerSnap.isFiltered()) {
9268 // we want to filter the server node, but we didn't filter the server node yet, so simulate a full update
9269 var newServerNode = oldServerSnap
9270 .getNode()
9271 .updateChild(changePath, changedSnap);
9272 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), newServerNode, null);
9273 }
9274 else {
9275 var childKey = pathGetFront(changePath);
9276 if (!oldServerSnap.isCompleteForPath(changePath) &&
9277 pathGetLength(changePath) > 1) {
9278 // We don't update incomplete nodes with updates intended for other listeners
9279 return oldViewCache;
9280 }
9281 var childChangePath = pathPopFront(changePath);
9282 var childNode = oldServerSnap.getNode().getImmediateChild(childKey);
9283 var newChildNode = childNode.updateChild(childChangePath, changedSnap);
9284 if (childKey === '.priority') {
9285 newServerCache = serverFilter.updatePriority(oldServerSnap.getNode(), newChildNode);
9286 }
9287 else {
9288 newServerCache = serverFilter.updateChild(oldServerSnap.getNode(), childKey, newChildNode, childChangePath, NO_COMPLETE_CHILD_SOURCE, null);
9289 }
9290 }
9291 var newViewCache = viewCacheUpdateServerSnap(oldViewCache, newServerCache, oldServerSnap.isFullyInitialized() || pathIsEmpty(changePath), serverFilter.filtersNodes());
9292 var source = new WriteTreeCompleteChildSource(writesCache, newViewCache, completeCache);
9293 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, changePath, writesCache, source, accumulator);
9294}
9295function viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, accumulator) {
9296 var oldEventSnap = oldViewCache.eventCache;
9297 var newViewCache, newEventCache;
9298 var source = new WriteTreeCompleteChildSource(writesCache, oldViewCache, completeCache);
9299 if (pathIsEmpty(changePath)) {
9300 newEventCache = viewProcessor.filter.updateFullNode(oldViewCache.eventCache.getNode(), changedSnap, accumulator);
9301 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, true, viewProcessor.filter.filtersNodes());
9302 }
9303 else {
9304 var childKey = pathGetFront(changePath);
9305 if (childKey === '.priority') {
9306 newEventCache = viewProcessor.filter.updatePriority(oldViewCache.eventCache.getNode(), changedSnap);
9307 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, oldEventSnap.isFullyInitialized(), oldEventSnap.isFiltered());
9308 }
9309 else {
9310 var childChangePath = pathPopFront(changePath);
9311 var oldChild = oldEventSnap.getNode().getImmediateChild(childKey);
9312 var newChild = void 0;
9313 if (pathIsEmpty(childChangePath)) {
9314 // Child overwrite, we can replace the child
9315 newChild = changedSnap;
9316 }
9317 else {
9318 var childNode = source.getCompleteChild(childKey);
9319 if (childNode != null) {
9320 if (pathGetBack(childChangePath) === '.priority' &&
9321 childNode.getChild(pathParent(childChangePath)).isEmpty()) {
9322 // This is a priority update on an empty node. If this node exists on the server, the
9323 // server will send down the priority in the update, so ignore for now
9324 newChild = childNode;
9325 }
9326 else {
9327 newChild = childNode.updateChild(childChangePath, changedSnap);
9328 }
9329 }
9330 else {
9331 // There is no complete child node available
9332 newChild = ChildrenNode.EMPTY_NODE;
9333 }
9334 }
9335 if (!oldChild.equals(newChild)) {
9336 var newEventSnap = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newChild, childChangePath, source, accumulator);
9337 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventSnap, oldEventSnap.isFullyInitialized(), viewProcessor.filter.filtersNodes());
9338 }
9339 else {
9340 newViewCache = oldViewCache;
9341 }
9342 }
9343 }
9344 return newViewCache;
9345}
9346function viewProcessorCacheHasChild(viewCache, childKey) {
9347 return viewCache.eventCache.isCompleteForChild(childKey);
9348}
9349function viewProcessorApplyUserMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, accumulator) {
9350 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9351 // window leaving room for new items. It's important we process these changes first, so we
9352 // iterate the changes twice, first processing any that affect items currently in view.
9353 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9354 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9355 // not the other.
9356 var curViewCache = viewCache;
9357 changedChildren.foreach(function (relativePath, childNode) {
9358 var writePath = pathChild(path, relativePath);
9359 if (viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9360 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9361 }
9362 });
9363 changedChildren.foreach(function (relativePath, childNode) {
9364 var writePath = pathChild(path, relativePath);
9365 if (!viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9366 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9367 }
9368 });
9369 return curViewCache;
9370}
9371function viewProcessorApplyMerge(viewProcessor, node, merge) {
9372 merge.foreach(function (relativePath, childNode) {
9373 node = node.updateChild(relativePath, childNode);
9374 });
9375 return node;
9376}
9377function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, filterServerNode, accumulator) {
9378 // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
9379 // wait for the complete data update coming soon.
9380 if (viewCache.serverCache.getNode().isEmpty() &&
9381 !viewCache.serverCache.isFullyInitialized()) {
9382 return viewCache;
9383 }
9384 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9385 // window leaving room for new items. It's important we process these changes first, so we
9386 // iterate the changes twice, first processing any that affect items currently in view.
9387 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9388 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9389 // not the other.
9390 var curViewCache = viewCache;
9391 var viewMergeTree;
9392 if (pathIsEmpty(path)) {
9393 viewMergeTree = changedChildren;
9394 }
9395 else {
9396 viewMergeTree = new ImmutableTree(null).setTree(path, changedChildren);
9397 }
9398 var serverNode = viewCache.serverCache.getNode();
9399 viewMergeTree.children.inorderTraversal(function (childKey, childTree) {
9400 if (serverNode.hasChild(childKey)) {
9401 var serverChild = viewCache.serverCache
9402 .getNode()
9403 .getImmediateChild(childKey);
9404 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childTree);
9405 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9406 }
9407 });
9408 viewMergeTree.children.inorderTraversal(function (childKey, childMergeTree) {
9409 var isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9410 childMergeTree.value === undefined;
9411 if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9412 var serverChild = viewCache.serverCache
9413 .getNode()
9414 .getImmediateChild(childKey);
9415 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childMergeTree);
9416 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9417 }
9418 });
9419 return curViewCache;
9420}
9421function viewProcessorAckUserWrite(viewProcessor, viewCache, ackPath, affectedTree, writesCache, completeCache, accumulator) {
9422 if (writeTreeRefShadowingWrite(writesCache, ackPath) != null) {
9423 return viewCache;
9424 }
9425 // Only filter server node if it is currently filtered
9426 var filterServerNode = viewCache.serverCache.isFiltered();
9427 // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
9428 // now that it won't be shadowed.
9429 var serverCache = viewCache.serverCache;
9430 if (affectedTree.value != null) {
9431 // This is an overwrite.
9432 if ((pathIsEmpty(ackPath) && serverCache.isFullyInitialized()) ||
9433 serverCache.isCompleteForPath(ackPath)) {
9434 return viewProcessorApplyServerOverwrite(viewProcessor, viewCache, ackPath, serverCache.getNode().getChild(ackPath), writesCache, completeCache, filterServerNode, accumulator);
9435 }
9436 else if (pathIsEmpty(ackPath)) {
9437 // This is a goofy edge case where we are acking data at this location but don't have full data. We
9438 // should just re-apply whatever we have in our cache as a merge.
9439 var changedChildren_1 = new ImmutableTree(null);
9440 serverCache.getNode().forEachChild(KEY_INDEX, function (name, node) {
9441 changedChildren_1 = changedChildren_1.set(new Path(name), node);
9442 });
9443 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_1, writesCache, completeCache, filterServerNode, accumulator);
9444 }
9445 else {
9446 return viewCache;
9447 }
9448 }
9449 else {
9450 // This is a merge.
9451 var changedChildren_2 = new ImmutableTree(null);
9452 affectedTree.foreach(function (mergePath, value) {
9453 var serverCachePath = pathChild(ackPath, mergePath);
9454 if (serverCache.isCompleteForPath(serverCachePath)) {
9455 changedChildren_2 = changedChildren_2.set(mergePath, serverCache.getNode().getChild(serverCachePath));
9456 }
9457 });
9458 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_2, writesCache, completeCache, filterServerNode, accumulator);
9459 }
9460}
9461function viewProcessorListenComplete(viewProcessor, viewCache, path, writesCache, accumulator) {
9462 var oldServerNode = viewCache.serverCache;
9463 var newViewCache = viewCacheUpdateServerSnap(viewCache, oldServerNode.getNode(), oldServerNode.isFullyInitialized() || pathIsEmpty(path), oldServerNode.isFiltered());
9464 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, path, writesCache, NO_COMPLETE_CHILD_SOURCE, accumulator);
9465}
9466function viewProcessorRevertUserWrite(viewProcessor, viewCache, path, writesCache, completeServerCache, accumulator) {
9467 var complete;
9468 if (writeTreeRefShadowingWrite(writesCache, path) != null) {
9469 return viewCache;
9470 }
9471 else {
9472 var source = new WriteTreeCompleteChildSource(writesCache, viewCache, completeServerCache);
9473 var oldEventCache = viewCache.eventCache.getNode();
9474 var newEventCache = void 0;
9475 if (pathIsEmpty(path) || pathGetFront(path) === '.priority') {
9476 var newNode = void 0;
9477 if (viewCache.serverCache.isFullyInitialized()) {
9478 newNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9479 }
9480 else {
9481 var serverChildren = viewCache.serverCache.getNode();
9482 util.assert(serverChildren instanceof ChildrenNode, 'serverChildren would be complete if leaf node');
9483 newNode = writeTreeRefCalcCompleteEventChildren(writesCache, serverChildren);
9484 }
9485 newNode = newNode;
9486 newEventCache = viewProcessor.filter.updateFullNode(oldEventCache, newNode, accumulator);
9487 }
9488 else {
9489 var childKey = pathGetFront(path);
9490 var newChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9491 if (newChild == null &&
9492 viewCache.serverCache.isCompleteForChild(childKey)) {
9493 newChild = oldEventCache.getImmediateChild(childKey);
9494 }
9495 if (newChild != null) {
9496 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, newChild, pathPopFront(path), source, accumulator);
9497 }
9498 else if (viewCache.eventCache.getNode().hasChild(childKey)) {
9499 // No complete child available, delete the existing one, if any
9500 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, ChildrenNode.EMPTY_NODE, pathPopFront(path), source, accumulator);
9501 }
9502 else {
9503 newEventCache = oldEventCache;
9504 }
9505 if (newEventCache.isEmpty() &&
9506 viewCache.serverCache.isFullyInitialized()) {
9507 // We might have reverted all child writes. Maybe the old event was a leaf node
9508 complete = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9509 if (complete.isLeafNode()) {
9510 newEventCache = viewProcessor.filter.updateFullNode(newEventCache, complete, accumulator);
9511 }
9512 }
9513 }
9514 complete =
9515 viewCache.serverCache.isFullyInitialized() ||
9516 writeTreeRefShadowingWrite(writesCache, newEmptyPath()) != null;
9517 return viewCacheUpdateEventSnap(viewCache, newEventCache, complete, viewProcessor.filter.filtersNodes());
9518 }
9519}
9520
9521/**
9522 * @license
9523 * Copyright 2017 Google LLC
9524 *
9525 * Licensed under the Apache License, Version 2.0 (the "License");
9526 * you may not use this file except in compliance with the License.
9527 * You may obtain a copy of the License at
9528 *
9529 * http://www.apache.org/licenses/LICENSE-2.0
9530 *
9531 * Unless required by applicable law or agreed to in writing, software
9532 * distributed under the License is distributed on an "AS IS" BASIS,
9533 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9534 * See the License for the specific language governing permissions and
9535 * limitations under the License.
9536 */
9537/**
9538 * A view represents a specific location and query that has 1 or more event registrations.
9539 *
9540 * It does several things:
9541 * - Maintains the list of event registrations for this location/query.
9542 * - Maintains a cache of the data visible for this location/query.
9543 * - Applies new operations (via applyOperation), updates the cache, and based on the event
9544 * registrations returns the set of events to be raised.
9545 */
9546var View = /** @class */ (function () {
9547 function View(query_, initialViewCache) {
9548 this.query_ = query_;
9549 this.eventRegistrations_ = [];
9550 var params = this.query_._queryParams;
9551 var indexFilter = new IndexedFilter(params.getIndex());
9552 var filter = queryParamsGetNodeFilter(params);
9553 this.processor_ = newViewProcessor(filter);
9554 var initialServerCache = initialViewCache.serverCache;
9555 var initialEventCache = initialViewCache.eventCache;
9556 // Don't filter server node with other filter than index, wait for tagged listen
9557 var serverSnap = indexFilter.updateFullNode(ChildrenNode.EMPTY_NODE, initialServerCache.getNode(), null);
9558 var eventSnap = filter.updateFullNode(ChildrenNode.EMPTY_NODE, initialEventCache.getNode(), null);
9559 var newServerCache = new CacheNode(serverSnap, initialServerCache.isFullyInitialized(), indexFilter.filtersNodes());
9560 var newEventCache = new CacheNode(eventSnap, initialEventCache.isFullyInitialized(), filter.filtersNodes());
9561 this.viewCache_ = newViewCache(newEventCache, newServerCache);
9562 this.eventGenerator_ = new EventGenerator(this.query_);
9563 }
9564 Object.defineProperty(View.prototype, "query", {
9565 get: function () {
9566 return this.query_;
9567 },
9568 enumerable: false,
9569 configurable: true
9570 });
9571 return View;
9572}());
9573function viewGetServerCache(view) {
9574 return view.viewCache_.serverCache.getNode();
9575}
9576function viewGetCompleteNode(view) {
9577 return viewCacheGetCompleteEventSnap(view.viewCache_);
9578}
9579function viewGetCompleteServerCache(view, path) {
9580 var cache = viewCacheGetCompleteServerSnap(view.viewCache_);
9581 if (cache) {
9582 // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
9583 // we need to see if it contains the child we're interested in.
9584 if (view.query._queryParams.loadsAllData() ||
9585 (!pathIsEmpty(path) &&
9586 !cache.getImmediateChild(pathGetFront(path)).isEmpty())) {
9587 return cache.getChild(path);
9588 }
9589 }
9590 return null;
9591}
9592function viewIsEmpty(view) {
9593 return view.eventRegistrations_.length === 0;
9594}
9595function viewAddEventRegistration(view, eventRegistration) {
9596 view.eventRegistrations_.push(eventRegistration);
9597}
9598/**
9599 * @param eventRegistration - If null, remove all callbacks.
9600 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9601 * @returns Cancel events, if cancelError was provided.
9602 */
9603function viewRemoveEventRegistration(view, eventRegistration, cancelError) {
9604 var cancelEvents = [];
9605 if (cancelError) {
9606 util.assert(eventRegistration == null, 'A cancel should cancel all event registrations.');
9607 var path_1 = view.query._path;
9608 view.eventRegistrations_.forEach(function (registration) {
9609 var maybeEvent = registration.createCancelEvent(cancelError, path_1);
9610 if (maybeEvent) {
9611 cancelEvents.push(maybeEvent);
9612 }
9613 });
9614 }
9615 if (eventRegistration) {
9616 var remaining = [];
9617 for (var i = 0; i < view.eventRegistrations_.length; ++i) {
9618 var existing = view.eventRegistrations_[i];
9619 if (!existing.matches(eventRegistration)) {
9620 remaining.push(existing);
9621 }
9622 else if (eventRegistration.hasAnyCallback()) {
9623 // We're removing just this one
9624 remaining = remaining.concat(view.eventRegistrations_.slice(i + 1));
9625 break;
9626 }
9627 }
9628 view.eventRegistrations_ = remaining;
9629 }
9630 else {
9631 view.eventRegistrations_ = [];
9632 }
9633 return cancelEvents;
9634}
9635/**
9636 * Applies the given Operation, updates our cache, and returns the appropriate events.
9637 */
9638function viewApplyOperation(view, operation, writesCache, completeServerCache) {
9639 if (operation.type === OperationType.MERGE &&
9640 operation.source.queryId !== null) {
9641 util.assert(viewCacheGetCompleteServerSnap(view.viewCache_), 'We should always have a full cache before handling merges');
9642 util.assert(viewCacheGetCompleteEventSnap(view.viewCache_), 'Missing event cache, even though we have a server cache');
9643 }
9644 var oldViewCache = view.viewCache_;
9645 var result = viewProcessorApplyOperation(view.processor_, oldViewCache, operation, writesCache, completeServerCache);
9646 viewProcessorAssertIndexed(view.processor_, result.viewCache);
9647 util.assert(result.viewCache.serverCache.isFullyInitialized() ||
9648 !oldViewCache.serverCache.isFullyInitialized(), 'Once a server snap is complete, it should never go back');
9649 view.viewCache_ = result.viewCache;
9650 return viewGenerateEventsForChanges_(view, result.changes, result.viewCache.eventCache.getNode(), null);
9651}
9652function viewGetInitialEvents(view, registration) {
9653 var eventSnap = view.viewCache_.eventCache;
9654 var initialChanges = [];
9655 if (!eventSnap.getNode().isLeafNode()) {
9656 var eventNode = eventSnap.getNode();
9657 eventNode.forEachChild(PRIORITY_INDEX, function (key, childNode) {
9658 initialChanges.push(changeChildAdded(key, childNode));
9659 });
9660 }
9661 if (eventSnap.isFullyInitialized()) {
9662 initialChanges.push(changeValue(eventSnap.getNode()));
9663 }
9664 return viewGenerateEventsForChanges_(view, initialChanges, eventSnap.getNode(), registration);
9665}
9666function viewGenerateEventsForChanges_(view, changes, eventCache, eventRegistration) {
9667 var registrations = eventRegistration
9668 ? [eventRegistration]
9669 : view.eventRegistrations_;
9670 return eventGeneratorGenerateEventsForChanges(view.eventGenerator_, changes, eventCache, registrations);
9671}
9672
9673/**
9674 * @license
9675 * Copyright 2017 Google LLC
9676 *
9677 * Licensed under the Apache License, Version 2.0 (the "License");
9678 * you may not use this file except in compliance with the License.
9679 * You may obtain a copy of the License at
9680 *
9681 * http://www.apache.org/licenses/LICENSE-2.0
9682 *
9683 * Unless required by applicable law or agreed to in writing, software
9684 * distributed under the License is distributed on an "AS IS" BASIS,
9685 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9686 * See the License for the specific language governing permissions and
9687 * limitations under the License.
9688 */
9689var referenceConstructor$1;
9690/**
9691 * SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
9692 * maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
9693 * and user writes (set, transaction, update).
9694 *
9695 * It's responsible for:
9696 * - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
9697 * - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
9698 * applyUserOverwrite, etc.)
9699 */
9700var SyncPoint = /** @class */ (function () {
9701 function SyncPoint() {
9702 /**
9703 * The Views being tracked at this location in the tree, stored as a map where the key is a
9704 * queryId and the value is the View for that query.
9705 *
9706 * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
9707 */
9708 this.views = new Map();
9709 }
9710 return SyncPoint;
9711}());
9712function syncPointSetReferenceConstructor(val) {
9713 util.assert(!referenceConstructor$1, '__referenceConstructor has already been defined');
9714 referenceConstructor$1 = val;
9715}
9716function syncPointGetReferenceConstructor() {
9717 util.assert(referenceConstructor$1, 'Reference.ts has not been loaded');
9718 return referenceConstructor$1;
9719}
9720function syncPointIsEmpty(syncPoint) {
9721 return syncPoint.views.size === 0;
9722}
9723function syncPointApplyOperation(syncPoint, operation, writesCache, optCompleteServerCache) {
9724 var e_1, _a;
9725 var queryId = operation.source.queryId;
9726 if (queryId !== null) {
9727 var view = syncPoint.views.get(queryId);
9728 util.assert(view != null, 'SyncTree gave us an op for an invalid query.');
9729 return viewApplyOperation(view, operation, writesCache, optCompleteServerCache);
9730 }
9731 else {
9732 var events = [];
9733 try {
9734 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9735 var view = _c.value;
9736 events = events.concat(viewApplyOperation(view, operation, writesCache, optCompleteServerCache));
9737 }
9738 }
9739 catch (e_1_1) { e_1 = { error: e_1_1 }; }
9740 finally {
9741 try {
9742 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9743 }
9744 finally { if (e_1) throw e_1.error; }
9745 }
9746 return events;
9747 }
9748}
9749/**
9750 * Get a view for the specified query.
9751 *
9752 * @param query - The query to return a view for
9753 * @param writesCache
9754 * @param serverCache
9755 * @param serverCacheComplete
9756 * @returns Events to raise.
9757 */
9758function syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete) {
9759 var queryId = query._queryIdentifier;
9760 var view = syncPoint.views.get(queryId);
9761 if (!view) {
9762 // TODO: make writesCache take flag for complete server node
9763 var eventCache = writeTreeRefCalcCompleteEventCache(writesCache, serverCacheComplete ? serverCache : null);
9764 var eventCacheComplete = false;
9765 if (eventCache) {
9766 eventCacheComplete = true;
9767 }
9768 else if (serverCache instanceof ChildrenNode) {
9769 eventCache = writeTreeRefCalcCompleteEventChildren(writesCache, serverCache);
9770 eventCacheComplete = false;
9771 }
9772 else {
9773 eventCache = ChildrenNode.EMPTY_NODE;
9774 eventCacheComplete = false;
9775 }
9776 var viewCache = newViewCache(new CacheNode(eventCache, eventCacheComplete, false), new CacheNode(serverCache, serverCacheComplete, false));
9777 return new View(query, viewCache);
9778 }
9779 return view;
9780}
9781/**
9782 * Add an event callback for the specified query.
9783 *
9784 * @param query
9785 * @param eventRegistration
9786 * @param writesCache
9787 * @param serverCache - Complete server cache, if we have it.
9788 * @param serverCacheComplete
9789 * @returns Events to raise.
9790 */
9791function syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete) {
9792 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9793 if (!syncPoint.views.has(query._queryIdentifier)) {
9794 syncPoint.views.set(query._queryIdentifier, view);
9795 }
9796 // This is guaranteed to exist now, we just created anything that was missing
9797 viewAddEventRegistration(view, eventRegistration);
9798 return viewGetInitialEvents(view, eventRegistration);
9799}
9800/**
9801 * Remove event callback(s). Return cancelEvents if a cancelError is specified.
9802 *
9803 * If query is the default query, we'll check all views for the specified eventRegistration.
9804 * If eventRegistration is null, we'll remove all callbacks for the specified view(s).
9805 *
9806 * @param eventRegistration - If null, remove all callbacks.
9807 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9808 * @returns removed queries and any cancel events
9809 */
9810function syncPointRemoveEventRegistration(syncPoint, query, eventRegistration, cancelError) {
9811 var e_2, _a;
9812 var queryId = query._queryIdentifier;
9813 var removed = [];
9814 var cancelEvents = [];
9815 var hadCompleteView = syncPointHasCompleteView(syncPoint);
9816 if (queryId === 'default') {
9817 try {
9818 // When you do ref.off(...), we search all views for the registration to remove.
9819 for (var _b = tslib.__values(syncPoint.views.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
9820 var _d = tslib.__read(_c.value, 2), viewQueryId = _d[0], view = _d[1];
9821 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9822 if (viewIsEmpty(view)) {
9823 syncPoint.views.delete(viewQueryId);
9824 // We'll deal with complete views later.
9825 if (!view.query._queryParams.loadsAllData()) {
9826 removed.push(view.query);
9827 }
9828 }
9829 }
9830 }
9831 catch (e_2_1) { e_2 = { error: e_2_1 }; }
9832 finally {
9833 try {
9834 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9835 }
9836 finally { if (e_2) throw e_2.error; }
9837 }
9838 }
9839 else {
9840 // remove the callback from the specific view.
9841 var view = syncPoint.views.get(queryId);
9842 if (view) {
9843 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9844 if (viewIsEmpty(view)) {
9845 syncPoint.views.delete(queryId);
9846 // We'll deal with complete views later.
9847 if (!view.query._queryParams.loadsAllData()) {
9848 removed.push(view.query);
9849 }
9850 }
9851 }
9852 }
9853 if (hadCompleteView && !syncPointHasCompleteView(syncPoint)) {
9854 // We removed our last complete view.
9855 removed.push(new (syncPointGetReferenceConstructor())(query._repo, query._path));
9856 }
9857 return { removed: removed, events: cancelEvents };
9858}
9859function syncPointGetQueryViews(syncPoint) {
9860 var e_3, _a;
9861 var result = [];
9862 try {
9863 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9864 var view = _c.value;
9865 if (!view.query._queryParams.loadsAllData()) {
9866 result.push(view);
9867 }
9868 }
9869 }
9870 catch (e_3_1) { e_3 = { error: e_3_1 }; }
9871 finally {
9872 try {
9873 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9874 }
9875 finally { if (e_3) throw e_3.error; }
9876 }
9877 return result;
9878}
9879/**
9880 * @param path - The path to the desired complete snapshot
9881 * @returns A complete cache, if it exists
9882 */
9883function syncPointGetCompleteServerCache(syncPoint, path) {
9884 var e_4, _a;
9885 var serverCache = null;
9886 try {
9887 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9888 var view = _c.value;
9889 serverCache = serverCache || viewGetCompleteServerCache(view, path);
9890 }
9891 }
9892 catch (e_4_1) { e_4 = { error: e_4_1 }; }
9893 finally {
9894 try {
9895 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9896 }
9897 finally { if (e_4) throw e_4.error; }
9898 }
9899 return serverCache;
9900}
9901function syncPointViewForQuery(syncPoint, query) {
9902 var params = query._queryParams;
9903 if (params.loadsAllData()) {
9904 return syncPointGetCompleteView(syncPoint);
9905 }
9906 else {
9907 var queryId = query._queryIdentifier;
9908 return syncPoint.views.get(queryId);
9909 }
9910}
9911function syncPointViewExistsForQuery(syncPoint, query) {
9912 return syncPointViewForQuery(syncPoint, query) != null;
9913}
9914function syncPointHasCompleteView(syncPoint) {
9915 return syncPointGetCompleteView(syncPoint) != null;
9916}
9917function syncPointGetCompleteView(syncPoint) {
9918 var e_5, _a;
9919 try {
9920 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9921 var view = _c.value;
9922 if (view.query._queryParams.loadsAllData()) {
9923 return view;
9924 }
9925 }
9926 }
9927 catch (e_5_1) { e_5 = { error: e_5_1 }; }
9928 finally {
9929 try {
9930 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9931 }
9932 finally { if (e_5) throw e_5.error; }
9933 }
9934 return null;
9935}
9936
9937/**
9938 * @license
9939 * Copyright 2017 Google LLC
9940 *
9941 * Licensed under the Apache License, Version 2.0 (the "License");
9942 * you may not use this file except in compliance with the License.
9943 * You may obtain a copy of the License at
9944 *
9945 * http://www.apache.org/licenses/LICENSE-2.0
9946 *
9947 * Unless required by applicable law or agreed to in writing, software
9948 * distributed under the License is distributed on an "AS IS" BASIS,
9949 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9950 * See the License for the specific language governing permissions and
9951 * limitations under the License.
9952 */
9953var referenceConstructor;
9954function syncTreeSetReferenceConstructor(val) {
9955 util.assert(!referenceConstructor, '__referenceConstructor has already been defined');
9956 referenceConstructor = val;
9957}
9958function syncTreeGetReferenceConstructor() {
9959 util.assert(referenceConstructor, 'Reference.ts has not been loaded');
9960 return referenceConstructor;
9961}
9962/**
9963 * Static tracker for next query tag.
9964 */
9965var syncTreeNextQueryTag_ = 1;
9966/**
9967 * SyncTree is the central class for managing event callback registration, data caching, views
9968 * (query processing), and event generation. There are typically two SyncTree instances for
9969 * each Repo, one for the normal Firebase data, and one for the .info data.
9970 *
9971 * It has a number of responsibilities, including:
9972 * - Tracking all user event callbacks (registered via addEventRegistration() and removeEventRegistration()).
9973 * - Applying and caching data changes for user set(), transaction(), and update() calls
9974 * (applyUserOverwrite(), applyUserMerge()).
9975 * - Applying and caching data changes for server data changes (applyServerOverwrite(),
9976 * applyServerMerge()).
9977 * - Generating user-facing events for server and user changes (all of the apply* methods
9978 * return the set of events that need to be raised as a result).
9979 * - Maintaining the appropriate set of server listens to ensure we are always subscribed
9980 * to the correct set of paths and queries to satisfy the current set of user event
9981 * callbacks (listens are started/stopped using the provided listenProvider).
9982 *
9983 * NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
9984 * events are returned to the caller rather than raised synchronously.
9985 *
9986 */
9987var SyncTree = /** @class */ (function () {
9988 /**
9989 * @param listenProvider_ - Used by SyncTree to start / stop listening
9990 * to server data.
9991 */
9992 function SyncTree(listenProvider_) {
9993 this.listenProvider_ = listenProvider_;
9994 /**
9995 * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
9996 */
9997 this.syncPointTree_ = new ImmutableTree(null);
9998 /**
9999 * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
10000 */
10001 this.pendingWriteTree_ = newWriteTree();
10002 this.tagToQueryMap = new Map();
10003 this.queryToTagMap = new Map();
10004 }
10005 return SyncTree;
10006}());
10007/**
10008 * Apply the data changes for a user-generated set() or transaction() call.
10009 *
10010 * @returns Events to raise.
10011 */
10012function syncTreeApplyUserOverwrite(syncTree, path, newData, writeId, visible) {
10013 // Record pending write.
10014 writeTreeAddOverwrite(syncTree.pendingWriteTree_, path, newData, writeId, visible);
10015 if (!visible) {
10016 return [];
10017 }
10018 else {
10019 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceUser(), path, newData));
10020 }
10021}
10022/**
10023 * Apply the data from a user-generated update() call
10024 *
10025 * @returns Events to raise.
10026 */
10027function syncTreeApplyUserMerge(syncTree, path, changedChildren, writeId) {
10028 // Record pending merge.
10029 writeTreeAddMerge(syncTree.pendingWriteTree_, path, changedChildren, writeId);
10030 var changeTree = ImmutableTree.fromObject(changedChildren);
10031 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceUser(), path, changeTree));
10032}
10033/**
10034 * Acknowledge a pending user write that was previously registered with applyUserOverwrite() or applyUserMerge().
10035 *
10036 * @param revert - True if the given write failed and needs to be reverted
10037 * @returns Events to raise.
10038 */
10039function syncTreeAckUserWrite(syncTree, writeId, revert) {
10040 if (revert === void 0) { revert = false; }
10041 var write = writeTreeGetWrite(syncTree.pendingWriteTree_, writeId);
10042 var needToReevaluate = writeTreeRemoveWrite(syncTree.pendingWriteTree_, writeId);
10043 if (!needToReevaluate) {
10044 return [];
10045 }
10046 else {
10047 var affectedTree_1 = new ImmutableTree(null);
10048 if (write.snap != null) {
10049 // overwrite
10050 affectedTree_1 = affectedTree_1.set(newEmptyPath(), true);
10051 }
10052 else {
10053 each(write.children, function (pathString) {
10054 affectedTree_1 = affectedTree_1.set(new Path(pathString), true);
10055 });
10056 }
10057 return syncTreeApplyOperationToSyncPoints_(syncTree, new AckUserWrite(write.path, affectedTree_1, revert));
10058 }
10059}
10060/**
10061 * Apply new server data for the specified path..
10062 *
10063 * @returns Events to raise.
10064 */
10065function syncTreeApplyServerOverwrite(syncTree, path, newData) {
10066 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceServer(), path, newData));
10067}
10068/**
10069 * Apply new server data to be merged in at the specified path.
10070 *
10071 * @returns Events to raise.
10072 */
10073function syncTreeApplyServerMerge(syncTree, path, changedChildren) {
10074 var changeTree = ImmutableTree.fromObject(changedChildren);
10075 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceServer(), path, changeTree));
10076}
10077/**
10078 * Apply a listen complete for a query
10079 *
10080 * @returns Events to raise.
10081 */
10082function syncTreeApplyListenComplete(syncTree, path) {
10083 return syncTreeApplyOperationToSyncPoints_(syncTree, new ListenComplete(newOperationSourceServer(), path));
10084}
10085/**
10086 * Apply a listen complete for a tagged query
10087 *
10088 * @returns Events to raise.
10089 */
10090function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
10091 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10092 if (queryKey) {
10093 var r = syncTreeParseQueryKey_(queryKey);
10094 var queryPath = r.path, queryId = r.queryId;
10095 var relativePath = newRelativePath(queryPath, path);
10096 var op = new ListenComplete(newOperationSourceServerTaggedQuery(queryId), relativePath);
10097 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10098 }
10099 else {
10100 // We've already removed the query. No big deal, ignore the update
10101 return [];
10102 }
10103}
10104/**
10105 * Remove event callback(s).
10106 *
10107 * If query is the default query, we'll check all queries for the specified eventRegistration.
10108 * If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
10109 *
10110 * @param eventRegistration - If null, all callbacks are removed.
10111 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
10112 * @returns Cancel events, if cancelError was provided.
10113 */
10114function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError) {
10115 // Find the syncPoint first. Then deal with whether or not it has matching listeners
10116 var path = query._path;
10117 var maybeSyncPoint = syncTree.syncPointTree_.get(path);
10118 var cancelEvents = [];
10119 // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
10120 // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
10121 // not loadsAllData().
10122 if (maybeSyncPoint &&
10123 (query._queryIdentifier === 'default' ||
10124 syncPointViewExistsForQuery(maybeSyncPoint, query))) {
10125 var removedAndEvents = syncPointRemoveEventRegistration(maybeSyncPoint, query, eventRegistration, cancelError);
10126 if (syncPointIsEmpty(maybeSyncPoint)) {
10127 syncTree.syncPointTree_ = syncTree.syncPointTree_.remove(path);
10128 }
10129 var removed = removedAndEvents.removed;
10130 cancelEvents = removedAndEvents.events;
10131 // We may have just removed one of many listeners and can short-circuit this whole process
10132 // We may also not have removed a default listener, in which case all of the descendant listeners should already be
10133 // properly set up.
10134 //
10135 // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
10136 // queryId === 'default'
10137 var removingDefault = -1 !==
10138 removed.findIndex(function (query) {
10139 return query._queryParams.loadsAllData();
10140 });
10141 var covered = syncTree.syncPointTree_.findOnPath(path, function (relativePath, parentSyncPoint) {
10142 return syncPointHasCompleteView(parentSyncPoint);
10143 });
10144 if (removingDefault && !covered) {
10145 var subtree = syncTree.syncPointTree_.subtree(path);
10146 // There are potentially child listeners. Determine what if any listens we need to send before executing the
10147 // removal
10148 if (!subtree.isEmpty()) {
10149 // We need to fold over our subtree and collect the listeners to send
10150 var newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
10151 // Ok, we've collected all the listens we need. Set them up.
10152 for (var i = 0; i < newViews.length; ++i) {
10153 var view = newViews[i], newQuery = view.query;
10154 var listener = syncTreeCreateListenerForView_(syncTree, view);
10155 syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery_(syncTree, newQuery), listener.hashFn, listener.onComplete);
10156 }
10157 }
10158 }
10159 // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
10160 // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
10161 // Also, note that if we have a cancelError, it's already been removed at the provider level.
10162 if (!covered && removed.length > 0 && !cancelError) {
10163 // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
10164 // default. Otherwise, we need to iterate through and cancel each individual query
10165 if (removingDefault) {
10166 // We don't tag default listeners
10167 var defaultTag = null;
10168 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
10169 }
10170 else {
10171 removed.forEach(function (queryToRemove) {
10172 var tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
10173 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
10174 });
10175 }
10176 }
10177 // Now, clear all of the tags we're tracking for the removed listens
10178 syncTreeRemoveTags_(syncTree, removed);
10179 }
10180 return cancelEvents;
10181}
10182/**
10183 * This function was added to support non-listener queries,
10184 * specifically for use in repoGetValue. It sets up all the same
10185 * local cache data-structures (SyncPoint + View) that are
10186 * needed for listeners without installing an event registration.
10187 * If `query` is not `loadsAllData`, it will also provision a tag for
10188 * the query so that query results can be merged into the sync
10189 * tree using existing logic for tagged listener queries.
10190 *
10191 * @param syncTree - Synctree to add the query to.
10192 * @param query - Query to register
10193 * @returns tag as a string if query is not a default query, null if query is not.
10194 */
10195function syncTreeRegisterQuery(syncTree, query) {
10196 var _a = syncTreeRegisterSyncPoint(query, syncTree), syncPoint = _a.syncPoint, serverCache = _a.serverCache, writesCache = _a.writesCache, serverCacheComplete = _a.serverCacheComplete;
10197 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
10198 if (!syncPoint.views.has(query._queryIdentifier)) {
10199 syncPoint.views.set(query._queryIdentifier, view);
10200 }
10201 if (!query._queryParams.loadsAllData()) {
10202 return syncTreeTagForQuery_(syncTree, query);
10203 }
10204 return null;
10205}
10206/**
10207 * Apply new server data for the specified tagged query.
10208 *
10209 * @returns Events to raise.
10210 */
10211function syncTreeApplyTaggedQueryOverwrite(syncTree, path, snap, tag) {
10212 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10213 if (queryKey != null) {
10214 var r = syncTreeParseQueryKey_(queryKey);
10215 var queryPath = r.path, queryId = r.queryId;
10216 var relativePath = newRelativePath(queryPath, path);
10217 var op = new Overwrite(newOperationSourceServerTaggedQuery(queryId), relativePath, snap);
10218 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10219 }
10220 else {
10221 // Query must have been removed already
10222 return [];
10223 }
10224}
10225/**
10226 * Apply server data to be merged in for the specified tagged query.
10227 *
10228 * @returns Events to raise.
10229 */
10230function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
10231 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10232 if (queryKey) {
10233 var r = syncTreeParseQueryKey_(queryKey);
10234 var queryPath = r.path, queryId = r.queryId;
10235 var relativePath = newRelativePath(queryPath, path);
10236 var changeTree = ImmutableTree.fromObject(changedChildren);
10237 var op = new Merge(newOperationSourceServerTaggedQuery(queryId), relativePath, changeTree);
10238 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10239 }
10240 else {
10241 // We've already removed the query. No big deal, ignore the update
10242 return [];
10243 }
10244}
10245/**
10246 * Creates a new syncpoint for a query and creates a tag if the view doesn't exist.
10247 * Extracted from addEventRegistration to allow `repoGetValue` to properly set up the SyncTree
10248 * without actually listening on a query.
10249 */
10250function syncTreeRegisterSyncPoint(query, syncTree) {
10251 var path = query._path;
10252 var serverCache = null;
10253 var foundAncestorDefaultView = false;
10254 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10255 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10256 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10257 var relativePath = newRelativePath(pathToSyncPoint, path);
10258 serverCache =
10259 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10260 foundAncestorDefaultView =
10261 foundAncestorDefaultView || syncPointHasCompleteView(sp);
10262 });
10263 var syncPoint = syncTree.syncPointTree_.get(path);
10264 if (!syncPoint) {
10265 syncPoint = new SyncPoint();
10266 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10267 }
10268 else {
10269 foundAncestorDefaultView =
10270 foundAncestorDefaultView || syncPointHasCompleteView(syncPoint);
10271 serverCache =
10272 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10273 }
10274 var serverCacheComplete;
10275 if (serverCache != null) {
10276 serverCacheComplete = true;
10277 }
10278 else {
10279 serverCacheComplete = false;
10280 serverCache = ChildrenNode.EMPTY_NODE;
10281 var subtree = syncTree.syncPointTree_.subtree(path);
10282 subtree.foreachChild(function (childName, childSyncPoint) {
10283 var completeCache = syncPointGetCompleteServerCache(childSyncPoint, newEmptyPath());
10284 if (completeCache) {
10285 serverCache = serverCache.updateImmediateChild(childName, completeCache);
10286 }
10287 });
10288 }
10289 var viewAlreadyExists = syncPointViewExistsForQuery(syncPoint, query);
10290 if (!viewAlreadyExists && !query._queryParams.loadsAllData()) {
10291 // We need to track a tag for this query
10292 var queryKey = syncTreeMakeQueryKey_(query);
10293 util.assert(!syncTree.queryToTagMap.has(queryKey), 'View does not exist, but we have a tag');
10294 var tag = syncTreeGetNextQueryTag_();
10295 syncTree.queryToTagMap.set(queryKey, tag);
10296 syncTree.tagToQueryMap.set(tag, queryKey);
10297 }
10298 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
10299 return {
10300 syncPoint: syncPoint,
10301 writesCache: writesCache,
10302 serverCache: serverCache,
10303 serverCacheComplete: serverCacheComplete,
10304 foundAncestorDefaultView: foundAncestorDefaultView,
10305 viewAlreadyExists: viewAlreadyExists
10306 };
10307}
10308/**
10309 * Add an event callback for the specified query.
10310 *
10311 * @returns Events to raise.
10312 */
10313function syncTreeAddEventRegistration(syncTree, query, eventRegistration) {
10314 var _a = syncTreeRegisterSyncPoint(query, syncTree), syncPoint = _a.syncPoint, serverCache = _a.serverCache, writesCache = _a.writesCache, serverCacheComplete = _a.serverCacheComplete, viewAlreadyExists = _a.viewAlreadyExists, foundAncestorDefaultView = _a.foundAncestorDefaultView;
10315 var events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
10316 if (!viewAlreadyExists && !foundAncestorDefaultView) {
10317 var view = syncPointViewForQuery(syncPoint, query);
10318 events = events.concat(syncTreeSetupListener_(syncTree, query, view));
10319 }
10320 return events;
10321}
10322/**
10323 * Returns a complete cache, if we have one, of the data at a particular path. If the location does not have a
10324 * listener above it, we will get a false "null". This shouldn't be a problem because transactions will always
10325 * have a listener above, and atomic operations would correctly show a jitter of <increment value> ->
10326 * <incremented total> as the write is applied locally and then acknowledged at the server.
10327 *
10328 * Note: this method will *include* hidden writes from transaction with applyLocally set to false.
10329 *
10330 * @param path - The path to the data we want
10331 * @param writeIdsToExclude - A specific set to be excluded
10332 */
10333function syncTreeCalcCompleteEventCache(syncTree, path, writeIdsToExclude) {
10334 var includeHiddenSets = true;
10335 var writeTree = syncTree.pendingWriteTree_;
10336 var serverCache = syncTree.syncPointTree_.findOnPath(path, function (pathSoFar, syncPoint) {
10337 var relativePath = newRelativePath(pathSoFar, path);
10338 var serverCache = syncPointGetCompleteServerCache(syncPoint, relativePath);
10339 if (serverCache) {
10340 return serverCache;
10341 }
10342 });
10343 return writeTreeCalcCompleteEventCache(writeTree, path, serverCache, writeIdsToExclude, includeHiddenSets);
10344}
10345function syncTreeGetServerValue(syncTree, query) {
10346 var path = query._path;
10347 var serverCache = null;
10348 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10349 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10350 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10351 var relativePath = newRelativePath(pathToSyncPoint, path);
10352 serverCache =
10353 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10354 });
10355 var syncPoint = syncTree.syncPointTree_.get(path);
10356 if (!syncPoint) {
10357 syncPoint = new SyncPoint();
10358 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10359 }
10360 else {
10361 serverCache =
10362 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10363 }
10364 var serverCacheComplete = serverCache != null;
10365 var serverCacheNode = serverCacheComplete
10366 ? new CacheNode(serverCache, true, false)
10367 : null;
10368 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, query._path);
10369 var view = syncPointGetView(syncPoint, query, writesCache, serverCacheComplete ? serverCacheNode.getNode() : ChildrenNode.EMPTY_NODE, serverCacheComplete);
10370 return viewGetCompleteNode(view);
10371}
10372/**
10373 * A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
10374 *
10375 * NOTES:
10376 * - Descendant SyncPoints will be visited first (since we raise events depth-first).
10377 *
10378 * - We call applyOperation() on each SyncPoint passing three things:
10379 * 1. A version of the Operation that has been made relative to the SyncPoint location.
10380 * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
10381 * 3. A snapshot Node with cached server data, if we have it.
10382 *
10383 * - We concatenate all of the events returned by each SyncPoint and return the result.
10384 */
10385function syncTreeApplyOperationToSyncPoints_(syncTree, operation) {
10386 return syncTreeApplyOperationHelper_(operation, syncTree.syncPointTree_,
10387 /*serverCache=*/ null, writeTreeChildWrites(syncTree.pendingWriteTree_, newEmptyPath()));
10388}
10389/**
10390 * Recursive helper for applyOperationToSyncPoints_
10391 */
10392function syncTreeApplyOperationHelper_(operation, syncPointTree, serverCache, writesCache) {
10393 if (pathIsEmpty(operation.path)) {
10394 return syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache);
10395 }
10396 else {
10397 var syncPoint = syncPointTree.get(newEmptyPath());
10398 // If we don't have cached server data, see if we can get it from this SyncPoint.
10399 if (serverCache == null && syncPoint != null) {
10400 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10401 }
10402 var events = [];
10403 var childName = pathGetFront(operation.path);
10404 var childOperation = operation.operationForChild(childName);
10405 var childTree = syncPointTree.children.get(childName);
10406 if (childTree && childOperation) {
10407 var childServerCache = serverCache
10408 ? serverCache.getImmediateChild(childName)
10409 : null;
10410 var childWritesCache = writeTreeRefChild(writesCache, childName);
10411 events = events.concat(syncTreeApplyOperationHelper_(childOperation, childTree, childServerCache, childWritesCache));
10412 }
10413 if (syncPoint) {
10414 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10415 }
10416 return events;
10417 }
10418}
10419/**
10420 * Recursive helper for applyOperationToSyncPoints_
10421 */
10422function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache) {
10423 var syncPoint = syncPointTree.get(newEmptyPath());
10424 // If we don't have cached server data, see if we can get it from this SyncPoint.
10425 if (serverCache == null && syncPoint != null) {
10426 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10427 }
10428 var events = [];
10429 syncPointTree.children.inorderTraversal(function (childName, childTree) {
10430 var childServerCache = serverCache
10431 ? serverCache.getImmediateChild(childName)
10432 : null;
10433 var childWritesCache = writeTreeRefChild(writesCache, childName);
10434 var childOperation = operation.operationForChild(childName);
10435 if (childOperation) {
10436 events = events.concat(syncTreeApplyOperationDescendantsHelper_(childOperation, childTree, childServerCache, childWritesCache));
10437 }
10438 });
10439 if (syncPoint) {
10440 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10441 }
10442 return events;
10443}
10444function syncTreeCreateListenerForView_(syncTree, view) {
10445 var query = view.query;
10446 var tag = syncTreeTagForQuery_(syncTree, query);
10447 return {
10448 hashFn: function () {
10449 var cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
10450 return cache.hash();
10451 },
10452 onComplete: function (status) {
10453 if (status === 'ok') {
10454 if (tag) {
10455 return syncTreeApplyTaggedListenComplete(syncTree, query._path, tag);
10456 }
10457 else {
10458 return syncTreeApplyListenComplete(syncTree, query._path);
10459 }
10460 }
10461 else {
10462 // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
10463 // Note that this may need to be scoped to just this listener if we change permissions on filtered children
10464 var error = errorForServerCode(status, query);
10465 return syncTreeRemoveEventRegistration(syncTree, query,
10466 /*eventRegistration*/ null, error);
10467 }
10468 }
10469 };
10470}
10471/**
10472 * Return the tag associated with the given query.
10473 */
10474function syncTreeTagForQuery_(syncTree, query) {
10475 var queryKey = syncTreeMakeQueryKey_(query);
10476 return syncTree.queryToTagMap.get(queryKey);
10477}
10478/**
10479 * Given a query, computes a "queryKey" suitable for use in our queryToTagMap_.
10480 */
10481function syncTreeMakeQueryKey_(query) {
10482 return query._path.toString() + '$' + query._queryIdentifier;
10483}
10484/**
10485 * Return the query associated with the given tag, if we have one
10486 */
10487function syncTreeQueryKeyForTag_(syncTree, tag) {
10488 return syncTree.tagToQueryMap.get(tag);
10489}
10490/**
10491 * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId.
10492 */
10493function syncTreeParseQueryKey_(queryKey) {
10494 var splitIndex = queryKey.indexOf('$');
10495 util.assert(splitIndex !== -1 && splitIndex < queryKey.length - 1, 'Bad queryKey.');
10496 return {
10497 queryId: queryKey.substr(splitIndex + 1),
10498 path: new Path(queryKey.substr(0, splitIndex))
10499 };
10500}
10501/**
10502 * A helper method to apply tagged operations
10503 */
10504function syncTreeApplyTaggedOperation_(syncTree, queryPath, operation) {
10505 var syncPoint = syncTree.syncPointTree_.get(queryPath);
10506 util.assert(syncPoint, "Missing sync point for query tag that we're tracking");
10507 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, queryPath);
10508 return syncPointApplyOperation(syncPoint, operation, writesCache, null);
10509}
10510/**
10511 * This collapses multiple unfiltered views into a single view, since we only need a single
10512 * listener for them.
10513 */
10514function syncTreeCollectDistinctViewsForSubTree_(subtree) {
10515 return subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10516 if (maybeChildSyncPoint && syncPointHasCompleteView(maybeChildSyncPoint)) {
10517 var completeView = syncPointGetCompleteView(maybeChildSyncPoint);
10518 return [completeView];
10519 }
10520 else {
10521 // No complete view here, flatten any deeper listens into an array
10522 var views_1 = [];
10523 if (maybeChildSyncPoint) {
10524 views_1 = syncPointGetQueryViews(maybeChildSyncPoint);
10525 }
10526 each(childMap, function (_key, childViews) {
10527 views_1 = views_1.concat(childViews);
10528 });
10529 return views_1;
10530 }
10531 });
10532}
10533/**
10534 * Normalizes a query to a query we send the server for listening
10535 *
10536 * @returns The normalized query
10537 */
10538function syncTreeQueryForListening_(query) {
10539 if (query._queryParams.loadsAllData() && !query._queryParams.isDefault()) {
10540 // We treat queries that load all data as default queries
10541 // Cast is necessary because ref() technically returns Firebase which is actually fb.api.Firebase which inherits
10542 // from Query
10543 return new (syncTreeGetReferenceConstructor())(query._repo, query._path);
10544 }
10545 else {
10546 return query;
10547 }
10548}
10549function syncTreeRemoveTags_(syncTree, queries) {
10550 for (var j = 0; j < queries.length; ++j) {
10551 var removedQuery = queries[j];
10552 if (!removedQuery._queryParams.loadsAllData()) {
10553 // We should have a tag for this
10554 var removedQueryKey = syncTreeMakeQueryKey_(removedQuery);
10555 var removedQueryTag = syncTree.queryToTagMap.get(removedQueryKey);
10556 syncTree.queryToTagMap.delete(removedQueryKey);
10557 syncTree.tagToQueryMap.delete(removedQueryTag);
10558 }
10559 }
10560}
10561/**
10562 * Static accessor for query tags.
10563 */
10564function syncTreeGetNextQueryTag_() {
10565 return syncTreeNextQueryTag_++;
10566}
10567/**
10568 * For a given new listen, manage the de-duplication of outstanding subscriptions.
10569 *
10570 * @returns This method can return events to support synchronous data sources
10571 */
10572function syncTreeSetupListener_(syncTree, query, view) {
10573 var path = query._path;
10574 var tag = syncTreeTagForQuery_(syncTree, query);
10575 var listener = syncTreeCreateListenerForView_(syncTree, view);
10576 var events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10577 var subtree = syncTree.syncPointTree_.subtree(path);
10578 // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
10579 // may need to shadow other listens as well.
10580 if (tag) {
10581 util.assert(!syncPointHasCompleteView(subtree.value), "If we're adding a query, it shouldn't be shadowed");
10582 }
10583 else {
10584 // Shadow everything at or below this location, this is a default listener.
10585 var queriesToStop = subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10586 if (!pathIsEmpty(relativePath) &&
10587 maybeChildSyncPoint &&
10588 syncPointHasCompleteView(maybeChildSyncPoint)) {
10589 return [syncPointGetCompleteView(maybeChildSyncPoint).query];
10590 }
10591 else {
10592 // No default listener here, flatten any deeper queries into an array
10593 var queries_1 = [];
10594 if (maybeChildSyncPoint) {
10595 queries_1 = queries_1.concat(syncPointGetQueryViews(maybeChildSyncPoint).map(function (view) { return view.query; }));
10596 }
10597 each(childMap, function (_key, childQueries) {
10598 queries_1 = queries_1.concat(childQueries);
10599 });
10600 return queries_1;
10601 }
10602 });
10603 for (var i = 0; i < queriesToStop.length; ++i) {
10604 var queryToStop = queriesToStop[i];
10605 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery_(syncTree, queryToStop));
10606 }
10607 }
10608 return events;
10609}
10610
10611/**
10612 * @license
10613 * Copyright 2017 Google LLC
10614 *
10615 * Licensed under the Apache License, Version 2.0 (the "License");
10616 * you may not use this file except in compliance with the License.
10617 * You may obtain a copy of the License at
10618 *
10619 * http://www.apache.org/licenses/LICENSE-2.0
10620 *
10621 * Unless required by applicable law or agreed to in writing, software
10622 * distributed under the License is distributed on an "AS IS" BASIS,
10623 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10624 * See the License for the specific language governing permissions and
10625 * limitations under the License.
10626 */
10627var ExistingValueProvider = /** @class */ (function () {
10628 function ExistingValueProvider(node_) {
10629 this.node_ = node_;
10630 }
10631 ExistingValueProvider.prototype.getImmediateChild = function (childName) {
10632 var child = this.node_.getImmediateChild(childName);
10633 return new ExistingValueProvider(child);
10634 };
10635 ExistingValueProvider.prototype.node = function () {
10636 return this.node_;
10637 };
10638 return ExistingValueProvider;
10639}());
10640var DeferredValueProvider = /** @class */ (function () {
10641 function DeferredValueProvider(syncTree, path) {
10642 this.syncTree_ = syncTree;
10643 this.path_ = path;
10644 }
10645 DeferredValueProvider.prototype.getImmediateChild = function (childName) {
10646 var childPath = pathChild(this.path_, childName);
10647 return new DeferredValueProvider(this.syncTree_, childPath);
10648 };
10649 DeferredValueProvider.prototype.node = function () {
10650 return syncTreeCalcCompleteEventCache(this.syncTree_, this.path_);
10651 };
10652 return DeferredValueProvider;
10653}());
10654/**
10655 * Generate placeholders for deferred values.
10656 */
10657var generateWithValues = function (values) {
10658 values = values || {};
10659 values['timestamp'] = values['timestamp'] || new Date().getTime();
10660 return values;
10661};
10662/**
10663 * Value to use when firing local events. When writing server values, fire
10664 * local events with an approximate value, otherwise return value as-is.
10665 */
10666var resolveDeferredLeafValue = function (value, existingVal, serverValues) {
10667 if (!value || typeof value !== 'object') {
10668 return value;
10669 }
10670 util.assert('.sv' in value, 'Unexpected leaf node or priority contents');
10671 if (typeof value['.sv'] === 'string') {
10672 return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
10673 }
10674 else if (typeof value['.sv'] === 'object') {
10675 return resolveComplexDeferredValue(value['.sv'], existingVal);
10676 }
10677 else {
10678 util.assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
10679 }
10680};
10681var resolveScalarDeferredValue = function (op, existing, serverValues) {
10682 switch (op) {
10683 case 'timestamp':
10684 return serverValues['timestamp'];
10685 default:
10686 util.assert(false, 'Unexpected server value: ' + op);
10687 }
10688};
10689var resolveComplexDeferredValue = function (op, existing, unused) {
10690 if (!op.hasOwnProperty('increment')) {
10691 util.assert(false, 'Unexpected server value: ' + JSON.stringify(op, null, 2));
10692 }
10693 var delta = op['increment'];
10694 if (typeof delta !== 'number') {
10695 util.assert(false, 'Unexpected increment value: ' + delta);
10696 }
10697 var existingNode = existing.node();
10698 util.assert(existingNode !== null && typeof existingNode !== 'undefined', 'Expected ChildrenNode.EMPTY_NODE for nulls');
10699 // Incrementing a non-number sets the value to the incremented amount
10700 if (!existingNode.isLeafNode()) {
10701 return delta;
10702 }
10703 var leaf = existingNode;
10704 var existingVal = leaf.getValue();
10705 if (typeof existingVal !== 'number') {
10706 return delta;
10707 }
10708 // No need to do over/underflow arithmetic here because JS only handles floats under the covers
10709 return existingVal + delta;
10710};
10711/**
10712 * Recursively replace all deferred values and priorities in the tree with the
10713 * specified generated replacement values.
10714 * @param path - path to which write is relative
10715 * @param node - new data written at path
10716 * @param syncTree - current data
10717 */
10718var resolveDeferredValueTree = function (path, node, syncTree, serverValues) {
10719 return resolveDeferredValue(node, new DeferredValueProvider(syncTree, path), serverValues);
10720};
10721/**
10722 * Recursively replace all deferred values and priorities in the node with the
10723 * specified generated replacement values. If there are no server values in the node,
10724 * it'll be returned as-is.
10725 */
10726var resolveDeferredValueSnapshot = function (node, existing, serverValues) {
10727 return resolveDeferredValue(node, new ExistingValueProvider(existing), serverValues);
10728};
10729function resolveDeferredValue(node, existingVal, serverValues) {
10730 var rawPri = node.getPriority().val();
10731 var priority = resolveDeferredLeafValue(rawPri, existingVal.getImmediateChild('.priority'), serverValues);
10732 var newNode;
10733 if (node.isLeafNode()) {
10734 var leafNode = node;
10735 var value = resolveDeferredLeafValue(leafNode.getValue(), existingVal, serverValues);
10736 if (value !== leafNode.getValue() ||
10737 priority !== leafNode.getPriority().val()) {
10738 return new LeafNode(value, nodeFromJSON(priority));
10739 }
10740 else {
10741 return node;
10742 }
10743 }
10744 else {
10745 var childrenNode = node;
10746 newNode = childrenNode;
10747 if (priority !== childrenNode.getPriority().val()) {
10748 newNode = newNode.updatePriority(new LeafNode(priority));
10749 }
10750 childrenNode.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
10751 var newChildNode = resolveDeferredValue(childNode, existingVal.getImmediateChild(childName), serverValues);
10752 if (newChildNode !== childNode) {
10753 newNode = newNode.updateImmediateChild(childName, newChildNode);
10754 }
10755 });
10756 return newNode;
10757 }
10758}
10759
10760/**
10761 * @license
10762 * Copyright 2017 Google LLC
10763 *
10764 * Licensed under the Apache License, Version 2.0 (the "License");
10765 * you may not use this file except in compliance with the License.
10766 * You may obtain a copy of the License at
10767 *
10768 * http://www.apache.org/licenses/LICENSE-2.0
10769 *
10770 * Unless required by applicable law or agreed to in writing, software
10771 * distributed under the License is distributed on an "AS IS" BASIS,
10772 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10773 * See the License for the specific language governing permissions and
10774 * limitations under the License.
10775 */
10776/**
10777 * A light-weight tree, traversable by path. Nodes can have both values and children.
10778 * Nodes are not enumerated (by forEachChild) unless they have a value or non-empty
10779 * children.
10780 */
10781var Tree = /** @class */ (function () {
10782 /**
10783 * @param name - Optional name of the node.
10784 * @param parent - Optional parent node.
10785 * @param node - Optional node to wrap.
10786 */
10787 function Tree(name, parent, node) {
10788 if (name === void 0) { name = ''; }
10789 if (parent === void 0) { parent = null; }
10790 if (node === void 0) { node = { children: {}, childCount: 0 }; }
10791 this.name = name;
10792 this.parent = parent;
10793 this.node = node;
10794 }
10795 return Tree;
10796}());
10797/**
10798 * Returns a sub-Tree for the given path.
10799 *
10800 * @param pathObj - Path to look up.
10801 * @returns Tree for path.
10802 */
10803function treeSubTree(tree, pathObj) {
10804 // TODO: Require pathObj to be Path?
10805 var path = pathObj instanceof Path ? pathObj : new Path(pathObj);
10806 var child = tree, next = pathGetFront(path);
10807 while (next !== null) {
10808 var childNode = util.safeGet(child.node.children, next) || {
10809 children: {},
10810 childCount: 0
10811 };
10812 child = new Tree(next, child, childNode);
10813 path = pathPopFront(path);
10814 next = pathGetFront(path);
10815 }
10816 return child;
10817}
10818/**
10819 * Returns the data associated with this tree node.
10820 *
10821 * @returns The data or null if no data exists.
10822 */
10823function treeGetValue(tree) {
10824 return tree.node.value;
10825}
10826/**
10827 * Sets data to this tree node.
10828 *
10829 * @param value - Value to set.
10830 */
10831function treeSetValue(tree, value) {
10832 tree.node.value = value;
10833 treeUpdateParents(tree);
10834}
10835/**
10836 * @returns Whether the tree has any children.
10837 */
10838function treeHasChildren(tree) {
10839 return tree.node.childCount > 0;
10840}
10841/**
10842 * @returns Whethe rthe tree is empty (no value or children).
10843 */
10844function treeIsEmpty(tree) {
10845 return treeGetValue(tree) === undefined && !treeHasChildren(tree);
10846}
10847/**
10848 * Calls action for each child of this tree node.
10849 *
10850 * @param action - Action to be called for each child.
10851 */
10852function treeForEachChild(tree, action) {
10853 each(tree.node.children, function (child, childTree) {
10854 action(new Tree(child, tree, childTree));
10855 });
10856}
10857/**
10858 * Does a depth-first traversal of this node's descendants, calling action for each one.
10859 *
10860 * @param action - Action to be called for each child.
10861 * @param includeSelf - Whether to call action on this node as well. Defaults to
10862 * false.
10863 * @param childrenFirst - Whether to call action on children before calling it on
10864 * parent.
10865 */
10866function treeForEachDescendant(tree, action, includeSelf, childrenFirst) {
10867 if (includeSelf && !childrenFirst) {
10868 action(tree);
10869 }
10870 treeForEachChild(tree, function (child) {
10871 treeForEachDescendant(child, action, true, childrenFirst);
10872 });
10873 if (includeSelf && childrenFirst) {
10874 action(tree);
10875 }
10876}
10877/**
10878 * Calls action on each ancestor node.
10879 *
10880 * @param action - Action to be called on each parent; return
10881 * true to abort.
10882 * @param includeSelf - Whether to call action on this node as well.
10883 * @returns true if the action callback returned true.
10884 */
10885function treeForEachAncestor(tree, action, includeSelf) {
10886 var node = includeSelf ? tree : tree.parent;
10887 while (node !== null) {
10888 if (action(node)) {
10889 return true;
10890 }
10891 node = node.parent;
10892 }
10893 return false;
10894}
10895/**
10896 * @returns The path of this tree node, as a Path.
10897 */
10898function treeGetPath(tree) {
10899 return new Path(tree.parent === null
10900 ? tree.name
10901 : treeGetPath(tree.parent) + '/' + tree.name);
10902}
10903/**
10904 * Adds or removes this child from its parent based on whether it's empty or not.
10905 */
10906function treeUpdateParents(tree) {
10907 if (tree.parent !== null) {
10908 treeUpdateChild(tree.parent, tree.name, tree);
10909 }
10910}
10911/**
10912 * Adds or removes the passed child to this tree node, depending on whether it's empty.
10913 *
10914 * @param childName - The name of the child to update.
10915 * @param child - The child to update.
10916 */
10917function treeUpdateChild(tree, childName, child) {
10918 var childEmpty = treeIsEmpty(child);
10919 var childExists = util.contains(tree.node.children, childName);
10920 if (childEmpty && childExists) {
10921 delete tree.node.children[childName];
10922 tree.node.childCount--;
10923 treeUpdateParents(tree);
10924 }
10925 else if (!childEmpty && !childExists) {
10926 tree.node.children[childName] = child.node;
10927 tree.node.childCount++;
10928 treeUpdateParents(tree);
10929 }
10930}
10931
10932/**
10933 * @license
10934 * Copyright 2017 Google LLC
10935 *
10936 * Licensed under the Apache License, Version 2.0 (the "License");
10937 * you may not use this file except in compliance with the License.
10938 * You may obtain a copy of the License at
10939 *
10940 * http://www.apache.org/licenses/LICENSE-2.0
10941 *
10942 * Unless required by applicable law or agreed to in writing, software
10943 * distributed under the License is distributed on an "AS IS" BASIS,
10944 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10945 * See the License for the specific language governing permissions and
10946 * limitations under the License.
10947 */
10948/**
10949 * True for invalid Firebase keys
10950 */
10951var INVALID_KEY_REGEX_ = /[\[\].#$\/\u0000-\u001F\u007F]/;
10952/**
10953 * True for invalid Firebase paths.
10954 * Allows '/' in paths.
10955 */
10956var INVALID_PATH_REGEX_ = /[\[\].#$\u0000-\u001F\u007F]/;
10957/**
10958 * Maximum number of characters to allow in leaf value
10959 */
10960var MAX_LEAF_SIZE_ = 10 * 1024 * 1024;
10961var isValidKey = function (key) {
10962 return (typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX_.test(key));
10963};
10964var isValidPathString = function (pathString) {
10965 return (typeof pathString === 'string' &&
10966 pathString.length !== 0 &&
10967 !INVALID_PATH_REGEX_.test(pathString));
10968};
10969var isValidRootPathString = function (pathString) {
10970 if (pathString) {
10971 // Allow '/.info/' at the beginning.
10972 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10973 }
10974 return isValidPathString(pathString);
10975};
10976var isValidPriority = function (priority) {
10977 return (priority === null ||
10978 typeof priority === 'string' ||
10979 (typeof priority === 'number' && !isInvalidJSONNumber(priority)) ||
10980 (priority &&
10981 typeof priority === 'object' &&
10982 // eslint-disable-next-line @typescript-eslint/no-explicit-any
10983 util.contains(priority, '.sv')));
10984};
10985/**
10986 * Pre-validate a datum passed as an argument to Firebase function.
10987 */
10988var validateFirebaseDataArg = function (fnName, value, path, optional) {
10989 if (optional && value === undefined) {
10990 return;
10991 }
10992 validateFirebaseData(util.errorPrefix(fnName, 'value'), value, path);
10993};
10994/**
10995 * Validate a data object client-side before sending to server.
10996 */
10997var validateFirebaseData = function (errorPrefix, data, path_) {
10998 var path = path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;
10999 if (data === undefined) {
11000 throw new Error(errorPrefix + 'contains undefined ' + validationPathToErrorString(path));
11001 }
11002 if (typeof data === 'function') {
11003 throw new Error(errorPrefix +
11004 'contains a function ' +
11005 validationPathToErrorString(path) +
11006 ' with contents = ' +
11007 data.toString());
11008 }
11009 if (isInvalidJSONNumber(data)) {
11010 throw new Error(errorPrefix +
11011 'contains ' +
11012 data.toString() +
11013 ' ' +
11014 validationPathToErrorString(path));
11015 }
11016 // Check max leaf size, but try to avoid the utf8 conversion if we can.
11017 if (typeof data === 'string' &&
11018 data.length > MAX_LEAF_SIZE_ / 3 &&
11019 util.stringLength(data) > MAX_LEAF_SIZE_) {
11020 throw new Error(errorPrefix +
11021 'contains a string greater than ' +
11022 MAX_LEAF_SIZE_ +
11023 ' utf8 bytes ' +
11024 validationPathToErrorString(path) +
11025 " ('" +
11026 data.substring(0, 50) +
11027 "...')");
11028 }
11029 // TODO = Perf = Consider combining the recursive validation of keys into NodeFromJSON
11030 // to save extra walking of large objects.
11031 if (data && typeof data === 'object') {
11032 var hasDotValue_1 = false;
11033 var hasActualChild_1 = false;
11034 each(data, function (key, value) {
11035 if (key === '.value') {
11036 hasDotValue_1 = true;
11037 }
11038 else if (key !== '.priority' && key !== '.sv') {
11039 hasActualChild_1 = true;
11040 if (!isValidKey(key)) {
11041 throw new Error(errorPrefix +
11042 ' contains an invalid key (' +
11043 key +
11044 ') ' +
11045 validationPathToErrorString(path) +
11046 '. Keys must be non-empty strings ' +
11047 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11048 }
11049 }
11050 validationPathPush(path, key);
11051 validateFirebaseData(errorPrefix, value, path);
11052 validationPathPop(path);
11053 });
11054 if (hasDotValue_1 && hasActualChild_1) {
11055 throw new Error(errorPrefix +
11056 ' contains ".value" child ' +
11057 validationPathToErrorString(path) +
11058 ' in addition to actual children.');
11059 }
11060 }
11061};
11062/**
11063 * Pre-validate paths passed in the firebase function.
11064 */
11065var validateFirebaseMergePaths = function (errorPrefix, mergePaths) {
11066 var i, curPath;
11067 for (i = 0; i < mergePaths.length; i++) {
11068 curPath = mergePaths[i];
11069 var keys = pathSlice(curPath);
11070 for (var j = 0; j < keys.length; j++) {
11071 if (keys[j] === '.priority' && j === keys.length - 1) ;
11072 else if (!isValidKey(keys[j])) {
11073 throw new Error(errorPrefix +
11074 'contains an invalid key (' +
11075 keys[j] +
11076 ') in path ' +
11077 curPath.toString() +
11078 '. Keys must be non-empty strings ' +
11079 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11080 }
11081 }
11082 }
11083 // Check that update keys are not descendants of each other.
11084 // We rely on the property that sorting guarantees that ancestors come
11085 // right before descendants.
11086 mergePaths.sort(pathCompare);
11087 var prevPath = null;
11088 for (i = 0; i < mergePaths.length; i++) {
11089 curPath = mergePaths[i];
11090 if (prevPath !== null && pathContains(prevPath, curPath)) {
11091 throw new Error(errorPrefix +
11092 'contains a path ' +
11093 prevPath.toString() +
11094 ' that is ancestor of another path ' +
11095 curPath.toString());
11096 }
11097 prevPath = curPath;
11098 }
11099};
11100/**
11101 * pre-validate an object passed as an argument to firebase function (
11102 * must be an object - e.g. for firebase.update()).
11103 */
11104var validateFirebaseMergeDataArg = function (fnName, data, path, optional) {
11105 if (optional && data === undefined) {
11106 return;
11107 }
11108 var errorPrefix = util.errorPrefix(fnName, 'values');
11109 if (!(data && typeof data === 'object') || Array.isArray(data)) {
11110 throw new Error(errorPrefix + ' must be an object containing the children to replace.');
11111 }
11112 var mergePaths = [];
11113 each(data, function (key, value) {
11114 var curPath = new Path(key);
11115 validateFirebaseData(errorPrefix, value, pathChild(path, curPath));
11116 if (pathGetBack(curPath) === '.priority') {
11117 if (!isValidPriority(value)) {
11118 throw new Error(errorPrefix +
11119 "contains an invalid value for '" +
11120 curPath.toString() +
11121 "', which must be a valid " +
11122 'Firebase priority (a string, finite number, server value, or null).');
11123 }
11124 }
11125 mergePaths.push(curPath);
11126 });
11127 validateFirebaseMergePaths(errorPrefix, mergePaths);
11128};
11129var validatePriority = function (fnName, priority, optional) {
11130 if (optional && priority === undefined) {
11131 return;
11132 }
11133 if (isInvalidJSONNumber(priority)) {
11134 throw new Error(util.errorPrefix(fnName, 'priority') +
11135 'is ' +
11136 priority.toString() +
11137 ', but must be a valid Firebase priority (a string, finite number, ' +
11138 'server value, or null).');
11139 }
11140 // Special case to allow importing data with a .sv.
11141 if (!isValidPriority(priority)) {
11142 throw new Error(util.errorPrefix(fnName, 'priority') +
11143 'must be a valid Firebase priority ' +
11144 '(a string, finite number, server value, or null).');
11145 }
11146};
11147var validateKey = function (fnName, argumentName, key, optional) {
11148 if (optional && key === undefined) {
11149 return;
11150 }
11151 if (!isValidKey(key)) {
11152 throw new Error(util.errorPrefix(fnName, argumentName) +
11153 'was an invalid key = "' +
11154 key +
11155 '". Firebase keys must be non-empty strings and ' +
11156 'can\'t contain ".", "#", "$", "/", "[", or "]").');
11157 }
11158};
11159/**
11160 * @internal
11161 */
11162var validatePathString = function (fnName, argumentName, pathString, optional) {
11163 if (optional && pathString === undefined) {
11164 return;
11165 }
11166 if (!isValidPathString(pathString)) {
11167 throw new Error(util.errorPrefix(fnName, argumentName) +
11168 'was an invalid path = "' +
11169 pathString +
11170 '". Paths must be non-empty strings and ' +
11171 'can\'t contain ".", "#", "$", "[", or "]"');
11172 }
11173};
11174var validateRootPathString = function (fnName, argumentName, pathString, optional) {
11175 if (pathString) {
11176 // Allow '/.info/' at the beginning.
11177 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
11178 }
11179 validatePathString(fnName, argumentName, pathString, optional);
11180};
11181/**
11182 * @internal
11183 */
11184var validateWritablePath = function (fnName, path) {
11185 if (pathGetFront(path) === '.info') {
11186 throw new Error(fnName + " failed = Can't modify data under /.info/");
11187 }
11188};
11189var validateUrl = function (fnName, parsedUrl) {
11190 // TODO = Validate server better.
11191 var pathString = parsedUrl.path.toString();
11192 if (!(typeof parsedUrl.repoInfo.host === 'string') ||
11193 parsedUrl.repoInfo.host.length === 0 ||
11194 (!isValidKey(parsedUrl.repoInfo.namespace) &&
11195 parsedUrl.repoInfo.host.split(':')[0] !== 'localhost') ||
11196 (pathString.length !== 0 && !isValidRootPathString(pathString))) {
11197 throw new Error(util.errorPrefix(fnName, 'url') +
11198 'must be a valid firebase URL and ' +
11199 'the path can\'t contain ".", "#", "$", "[", or "]".');
11200 }
11201};
11202
11203/**
11204 * @license
11205 * Copyright 2017 Google LLC
11206 *
11207 * Licensed under the Apache License, Version 2.0 (the "License");
11208 * you may not use this file except in compliance with the License.
11209 * You may obtain a copy of the License at
11210 *
11211 * http://www.apache.org/licenses/LICENSE-2.0
11212 *
11213 * Unless required by applicable law or agreed to in writing, software
11214 * distributed under the License is distributed on an "AS IS" BASIS,
11215 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11216 * See the License for the specific language governing permissions and
11217 * limitations under the License.
11218 */
11219/**
11220 * The event queue serves a few purposes:
11221 * 1. It ensures we maintain event order in the face of event callbacks doing operations that result in more
11222 * events being queued.
11223 * 2. raiseQueuedEvents() handles being called reentrantly nicely. That is, if in the course of raising events,
11224 * raiseQueuedEvents() is called again, the "inner" call will pick up raising events where the "outer" call
11225 * left off, ensuring that the events are still raised synchronously and in order.
11226 * 3. You can use raiseEventsAtPath and raiseEventsForChangedPath to ensure only relevant previously-queued
11227 * events are raised synchronously.
11228 *
11229 * NOTE: This can all go away if/when we move to async events.
11230 *
11231 */
11232var EventQueue = /** @class */ (function () {
11233 function EventQueue() {
11234 this.eventLists_ = [];
11235 /**
11236 * Tracks recursion depth of raiseQueuedEvents_, for debugging purposes.
11237 */
11238 this.recursionDepth_ = 0;
11239 }
11240 return EventQueue;
11241}());
11242/**
11243 * @param eventDataList - The new events to queue.
11244 */
11245function eventQueueQueueEvents(eventQueue, eventDataList) {
11246 // We group events by path, storing them in a single EventList, to make it easier to skip over them quickly.
11247 var currList = null;
11248 for (var i = 0; i < eventDataList.length; i++) {
11249 var data = eventDataList[i];
11250 var path = data.getPath();
11251 if (currList !== null && !pathEquals(path, currList.path)) {
11252 eventQueue.eventLists_.push(currList);
11253 currList = null;
11254 }
11255 if (currList === null) {
11256 currList = { events: [], path: path };
11257 }
11258 currList.events.push(data);
11259 }
11260 if (currList) {
11261 eventQueue.eventLists_.push(currList);
11262 }
11263}
11264/**
11265 * Queues the specified events and synchronously raises all events (including previously queued ones)
11266 * for the specified path.
11267 *
11268 * It is assumed that the new events are all for the specified path.
11269 *
11270 * @param path - The path to raise events for.
11271 * @param eventDataList - The new events to raise.
11272 */
11273function eventQueueRaiseEventsAtPath(eventQueue, path, eventDataList) {
11274 eventQueueQueueEvents(eventQueue, eventDataList);
11275 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11276 return pathEquals(eventPath, path);
11277 });
11278}
11279/**
11280 * Queues the specified events and synchronously raises all events (including previously queued ones) for
11281 * locations related to the specified change path (i.e. all ancestors and descendants).
11282 *
11283 * It is assumed that the new events are all related (ancestor or descendant) to the specified path.
11284 *
11285 * @param changedPath - The path to raise events for.
11286 * @param eventDataList - The events to raise
11287 */
11288function eventQueueRaiseEventsForChangedPath(eventQueue, changedPath, eventDataList) {
11289 eventQueueQueueEvents(eventQueue, eventDataList);
11290 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11291 return pathContains(eventPath, changedPath) ||
11292 pathContains(changedPath, eventPath);
11293 });
11294}
11295function eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, predicate) {
11296 eventQueue.recursionDepth_++;
11297 var sentAll = true;
11298 for (var i = 0; i < eventQueue.eventLists_.length; i++) {
11299 var eventList = eventQueue.eventLists_[i];
11300 if (eventList) {
11301 var eventPath = eventList.path;
11302 if (predicate(eventPath)) {
11303 eventListRaise(eventQueue.eventLists_[i]);
11304 eventQueue.eventLists_[i] = null;
11305 }
11306 else {
11307 sentAll = false;
11308 }
11309 }
11310 }
11311 if (sentAll) {
11312 eventQueue.eventLists_ = [];
11313 }
11314 eventQueue.recursionDepth_--;
11315}
11316/**
11317 * Iterates through the list and raises each event
11318 */
11319function eventListRaise(eventList) {
11320 for (var i = 0; i < eventList.events.length; i++) {
11321 var eventData = eventList.events[i];
11322 if (eventData !== null) {
11323 eventList.events[i] = null;
11324 var eventFn = eventData.getEventRunner();
11325 if (logger) {
11326 log('event: ' + eventData.toString());
11327 }
11328 exceptionGuard(eventFn);
11329 }
11330 }
11331}
11332
11333/**
11334 * @license
11335 * Copyright 2017 Google LLC
11336 *
11337 * Licensed under the Apache License, Version 2.0 (the "License");
11338 * you may not use this file except in compliance with the License.
11339 * You may obtain a copy of the License at
11340 *
11341 * http://www.apache.org/licenses/LICENSE-2.0
11342 *
11343 * Unless required by applicable law or agreed to in writing, software
11344 * distributed under the License is distributed on an "AS IS" BASIS,
11345 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11346 * See the License for the specific language governing permissions and
11347 * limitations under the License.
11348 */
11349var INTERRUPT_REASON = 'repo_interrupt';
11350/**
11351 * If a transaction does not succeed after 25 retries, we abort it. Among other
11352 * things this ensure that if there's ever a bug causing a mismatch between
11353 * client / server hashes for some data, we won't retry indefinitely.
11354 */
11355var MAX_TRANSACTION_RETRIES = 25;
11356/**
11357 * A connection to a single data repository.
11358 */
11359var Repo = /** @class */ (function () {
11360 function Repo(repoInfo_, forceRestClient_, authTokenProvider_, appCheckProvider_) {
11361 this.repoInfo_ = repoInfo_;
11362 this.forceRestClient_ = forceRestClient_;
11363 this.authTokenProvider_ = authTokenProvider_;
11364 this.appCheckProvider_ = appCheckProvider_;
11365 this.dataUpdateCount = 0;
11366 this.statsListener_ = null;
11367 this.eventQueue_ = new EventQueue();
11368 this.nextWriteId_ = 1;
11369 this.interceptServerDataCallback_ = null;
11370 /** A list of data pieces and paths to be set when this client disconnects. */
11371 this.onDisconnect_ = newSparseSnapshotTree();
11372 /** Stores queues of outstanding transactions for Firebase locations. */
11373 this.transactionQueueTree_ = new Tree();
11374 // TODO: This should be @private but it's used by test_access.js and internal.js
11375 this.persistentConnection_ = null;
11376 // This key is intentionally not updated if RepoInfo is later changed or replaced
11377 this.key = this.repoInfo_.toURLString();
11378 }
11379 /**
11380 * @returns The URL corresponding to the root of this Firebase.
11381 */
11382 Repo.prototype.toString = function () {
11383 return ((this.repoInfo_.secure ? 'https://' : 'http://') + this.repoInfo_.host);
11384 };
11385 return Repo;
11386}());
11387function repoStart(repo, appId, authOverride) {
11388 repo.stats_ = statsManagerGetCollection(repo.repoInfo_);
11389 if (repo.forceRestClient_ || beingCrawled()) {
11390 repo.server_ = new ReadonlyRestClient(repo.repoInfo_, function (pathString, data, isMerge, tag) {
11391 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11392 }, repo.authTokenProvider_, repo.appCheckProvider_);
11393 // Minor hack: Fire onConnect immediately, since there's no actual connection.
11394 setTimeout(function () { return repoOnConnectStatus(repo, /* connectStatus= */ true); }, 0);
11395 }
11396 else {
11397 // Validate authOverride
11398 if (typeof authOverride !== 'undefined' && authOverride !== null) {
11399 if (typeof authOverride !== 'object') {
11400 throw new Error('Only objects are supported for option databaseAuthVariableOverride');
11401 }
11402 try {
11403 util.stringify(authOverride);
11404 }
11405 catch (e) {
11406 throw new Error('Invalid authOverride provided: ' + e);
11407 }
11408 }
11409 repo.persistentConnection_ = new PersistentConnection(repo.repoInfo_, appId, function (pathString, data, isMerge, tag) {
11410 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11411 }, function (connectStatus) {
11412 repoOnConnectStatus(repo, connectStatus);
11413 }, function (updates) {
11414 repoOnServerInfoUpdate(repo, updates);
11415 }, repo.authTokenProvider_, repo.appCheckProvider_, authOverride);
11416 repo.server_ = repo.persistentConnection_;
11417 }
11418 repo.authTokenProvider_.addTokenChangeListener(function (token) {
11419 repo.server_.refreshAuthToken(token);
11420 });
11421 repo.appCheckProvider_.addTokenChangeListener(function (result) {
11422 repo.server_.refreshAppCheckToken(result.token);
11423 });
11424 // In the case of multiple Repos for the same repoInfo (i.e. there are multiple Firebase.Contexts being used),
11425 // we only want to create one StatsReporter. As such, we'll report stats over the first Repo created.
11426 repo.statsReporter_ = statsManagerGetOrCreateReporter(repo.repoInfo_, function () { return new StatsReporter(repo.stats_, repo.server_); });
11427 // Used for .info.
11428 repo.infoData_ = new SnapshotHolder();
11429 repo.infoSyncTree_ = new SyncTree({
11430 startListening: function (query, tag, currentHashFn, onComplete) {
11431 var infoEvents = [];
11432 var node = repo.infoData_.getNode(query._path);
11433 // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
11434 // on initial data...
11435 if (!node.isEmpty()) {
11436 infoEvents = syncTreeApplyServerOverwrite(repo.infoSyncTree_, query._path, node);
11437 setTimeout(function () {
11438 onComplete('ok');
11439 }, 0);
11440 }
11441 return infoEvents;
11442 },
11443 stopListening: function () { }
11444 });
11445 repoUpdateInfo(repo, 'connected', false);
11446 repo.serverSyncTree_ = new SyncTree({
11447 startListening: function (query, tag, currentHashFn, onComplete) {
11448 repo.server_.listen(query, currentHashFn, tag, function (status, data) {
11449 var events = onComplete(status, data);
11450 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11451 });
11452 // No synchronous events for network-backed sync trees
11453 return [];
11454 },
11455 stopListening: function (query, tag) {
11456 repo.server_.unlisten(query, tag);
11457 }
11458 });
11459}
11460/**
11461 * @returns The time in milliseconds, taking the server offset into account if we have one.
11462 */
11463function repoServerTime(repo) {
11464 var offsetNode = repo.infoData_.getNode(new Path('.info/serverTimeOffset'));
11465 var offset = offsetNode.val() || 0;
11466 return new Date().getTime() + offset;
11467}
11468/**
11469 * Generate ServerValues using some variables from the repo object.
11470 */
11471function repoGenerateServerValues(repo) {
11472 return generateWithValues({
11473 timestamp: repoServerTime(repo)
11474 });
11475}
11476/**
11477 * Called by realtime when we get new messages from the server.
11478 */
11479function repoOnDataUpdate(repo, pathString, data, isMerge, tag) {
11480 // For testing.
11481 repo.dataUpdateCount++;
11482 var path = new Path(pathString);
11483 data = repo.interceptServerDataCallback_
11484 ? repo.interceptServerDataCallback_(pathString, data)
11485 : data;
11486 var events = [];
11487 if (tag) {
11488 if (isMerge) {
11489 var taggedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11490 events = syncTreeApplyTaggedQueryMerge(repo.serverSyncTree_, path, taggedChildren, tag);
11491 }
11492 else {
11493 var taggedSnap = nodeFromJSON(data);
11494 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, path, taggedSnap, tag);
11495 }
11496 }
11497 else if (isMerge) {
11498 var changedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11499 events = syncTreeApplyServerMerge(repo.serverSyncTree_, path, changedChildren);
11500 }
11501 else {
11502 var snap = nodeFromJSON(data);
11503 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap);
11504 }
11505 var affectedPath = path;
11506 if (events.length > 0) {
11507 // Since we have a listener outstanding for each transaction, receiving any events
11508 // is a proxy for some change having occurred.
11509 affectedPath = repoRerunTransactions(repo, path);
11510 }
11511 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, events);
11512}
11513function repoOnConnectStatus(repo, connectStatus) {
11514 repoUpdateInfo(repo, 'connected', connectStatus);
11515 if (connectStatus === false) {
11516 repoRunOnDisconnectEvents(repo);
11517 }
11518}
11519function repoOnServerInfoUpdate(repo, updates) {
11520 each(updates, function (key, value) {
11521 repoUpdateInfo(repo, key, value);
11522 });
11523}
11524function repoUpdateInfo(repo, pathString, value) {
11525 var path = new Path('/.info/' + pathString);
11526 var newNode = nodeFromJSON(value);
11527 repo.infoData_.updateSnapshot(path, newNode);
11528 var events = syncTreeApplyServerOverwrite(repo.infoSyncTree_, path, newNode);
11529 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11530}
11531function repoGetNextWriteId(repo) {
11532 return repo.nextWriteId_++;
11533}
11534/**
11535 * The purpose of `getValue` is to return the latest known value
11536 * satisfying `query`.
11537 *
11538 * This method will first check for in-memory cached values
11539 * belonging to active listeners. If they are found, such values
11540 * are considered to be the most up-to-date.
11541 *
11542 * If the client is not connected, this method will try to
11543 * establish a connection and request the value for `query`. If
11544 * the client is not able to retrieve the query result, it reports
11545 * an error.
11546 *
11547 * @param query - The query to surface a value for.
11548 */
11549function repoGetValue(repo, query) {
11550 // Only active queries are cached. There is no persisted cache.
11551 var cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11552 if (cached != null) {
11553 return Promise.resolve(cached);
11554 }
11555 return repo.server_.get(query).then(function (payload) {
11556 var node = nodeFromJSON(payload).withIndex(query._queryParams.getIndex());
11557 // if this is a filtered query, then overwrite at path
11558 if (query._queryParams.loadsAllData()) {
11559 syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11560 }
11561 else {
11562 // Simulate `syncTreeAddEventRegistration` without events/listener setup.
11563 // We do this (along with the syncTreeRemoveEventRegistration` below) so that
11564 // `repoGetValue` results have the same cache effects as initial listener(s)
11565 // updates.
11566 var tag = syncTreeRegisterQuery(repo.serverSyncTree_, query);
11567 syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, query._path, node, tag);
11568 // Call `syncTreeRemoveEventRegistration` with a null event registration, since there is none.
11569 // Note: The below code essentially unregisters the query and cleans up any views/syncpoints temporarily created above.
11570 }
11571 var cancels = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, null);
11572 if (cancels.length > 0) {
11573 repoLog(repo, 'unexpected cancel events in repoGetValue');
11574 }
11575 return node;
11576 }, function (err) {
11577 repoLog(repo, 'get for query ' + util.stringify(query) + ' failed: ' + err);
11578 return Promise.reject(new Error(err));
11579 });
11580}
11581function repoSetWithPriority(repo, path, newVal, newPriority, onComplete) {
11582 repoLog(repo, 'set', {
11583 path: path.toString(),
11584 value: newVal,
11585 priority: newPriority
11586 });
11587 // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
11588 // (b) store unresolved paths on JSON parse
11589 var serverValues = repoGenerateServerValues(repo);
11590 var newNodeUnresolved = nodeFromJSON(newVal, newPriority);
11591 var existing = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path);
11592 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, existing, serverValues);
11593 var writeId = repoGetNextWriteId(repo);
11594 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, writeId, true);
11595 eventQueueQueueEvents(repo.eventQueue_, events);
11596 repo.server_.put(path.toString(), newNodeUnresolved.val(/*export=*/ true), function (status, errorReason) {
11597 var success = status === 'ok';
11598 if (!success) {
11599 warn('set at ' + path + ' failed: ' + status);
11600 }
11601 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId, !success);
11602 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, clearEvents);
11603 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11604 });
11605 var affectedPath = repoAbortTransactions(repo, path);
11606 repoRerunTransactions(repo, affectedPath);
11607 // We queued the events above, so just flush the queue here
11608 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, []);
11609}
11610function repoUpdate(repo, path, childrenToMerge, onComplete) {
11611 repoLog(repo, 'update', { path: path.toString(), value: childrenToMerge });
11612 // Start with our existing data and merge each child into it.
11613 var empty = true;
11614 var serverValues = repoGenerateServerValues(repo);
11615 var changedChildren = {};
11616 each(childrenToMerge, function (changedKey, changedValue) {
11617 empty = false;
11618 changedChildren[changedKey] = resolveDeferredValueTree(pathChild(path, changedKey), nodeFromJSON(changedValue), repo.serverSyncTree_, serverValues);
11619 });
11620 if (!empty) {
11621 var writeId_1 = repoGetNextWriteId(repo);
11622 var events = syncTreeApplyUserMerge(repo.serverSyncTree_, path, changedChildren, writeId_1);
11623 eventQueueQueueEvents(repo.eventQueue_, events);
11624 repo.server_.merge(path.toString(), childrenToMerge, function (status, errorReason) {
11625 var success = status === 'ok';
11626 if (!success) {
11627 warn('update at ' + path + ' failed: ' + status);
11628 }
11629 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId_1, !success);
11630 var affectedPath = clearEvents.length > 0 ? repoRerunTransactions(repo, path) : path;
11631 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, clearEvents);
11632 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11633 });
11634 each(childrenToMerge, function (changedPath) {
11635 var affectedPath = repoAbortTransactions(repo, pathChild(path, changedPath));
11636 repoRerunTransactions(repo, affectedPath);
11637 });
11638 // We queued the events above, so just flush the queue here
11639 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, []);
11640 }
11641 else {
11642 log("update() called with empty data. Don't do anything.");
11643 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11644 }
11645}
11646/**
11647 * Applies all of the changes stored up in the onDisconnect_ tree.
11648 */
11649function repoRunOnDisconnectEvents(repo) {
11650 repoLog(repo, 'onDisconnectEvents');
11651 var serverValues = repoGenerateServerValues(repo);
11652 var resolvedOnDisconnectTree = newSparseSnapshotTree();
11653 sparseSnapshotTreeForEachTree(repo.onDisconnect_, newEmptyPath(), function (path, node) {
11654 var resolved = resolveDeferredValueTree(path, node, repo.serverSyncTree_, serverValues);
11655 sparseSnapshotTreeRemember(resolvedOnDisconnectTree, path, resolved);
11656 });
11657 var events = [];
11658 sparseSnapshotTreeForEachTree(resolvedOnDisconnectTree, newEmptyPath(), function (path, snap) {
11659 events = events.concat(syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap));
11660 var affectedPath = repoAbortTransactions(repo, path);
11661 repoRerunTransactions(repo, affectedPath);
11662 });
11663 repo.onDisconnect_ = newSparseSnapshotTree();
11664 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, newEmptyPath(), events);
11665}
11666function repoOnDisconnectCancel(repo, path, onComplete) {
11667 repo.server_.onDisconnectCancel(path.toString(), function (status, errorReason) {
11668 if (status === 'ok') {
11669 sparseSnapshotTreeForget(repo.onDisconnect_, path);
11670 }
11671 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11672 });
11673}
11674function repoOnDisconnectSet(repo, path, value, onComplete) {
11675 var newNode = nodeFromJSON(value);
11676 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11677 if (status === 'ok') {
11678 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11679 }
11680 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11681 });
11682}
11683function repoOnDisconnectSetWithPriority(repo, path, value, priority, onComplete) {
11684 var newNode = nodeFromJSON(value, priority);
11685 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11686 if (status === 'ok') {
11687 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11688 }
11689 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11690 });
11691}
11692function repoOnDisconnectUpdate(repo, path, childrenToMerge, onComplete) {
11693 if (util.isEmpty(childrenToMerge)) {
11694 log("onDisconnect().update() called with empty data. Don't do anything.");
11695 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11696 return;
11697 }
11698 repo.server_.onDisconnectMerge(path.toString(), childrenToMerge, function (status, errorReason) {
11699 if (status === 'ok') {
11700 each(childrenToMerge, function (childName, childNode) {
11701 var newChildNode = nodeFromJSON(childNode);
11702 sparseSnapshotTreeRemember(repo.onDisconnect_, pathChild(path, childName), newChildNode);
11703 });
11704 }
11705 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11706 });
11707}
11708function repoAddEventCallbackForQuery(repo, query, eventRegistration) {
11709 var events;
11710 if (pathGetFront(query._path) === '.info') {
11711 events = syncTreeAddEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11712 }
11713 else {
11714 events = syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11715 }
11716 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11717}
11718function repoRemoveEventCallbackForQuery(repo, query, eventRegistration) {
11719 // These are guaranteed not to raise events, since we're not passing in a cancelError. However, we can future-proof
11720 // a little bit by handling the return values anyways.
11721 var events;
11722 if (pathGetFront(query._path) === '.info') {
11723 events = syncTreeRemoveEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11724 }
11725 else {
11726 events = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11727 }
11728 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11729}
11730function repoInterrupt(repo) {
11731 if (repo.persistentConnection_) {
11732 repo.persistentConnection_.interrupt(INTERRUPT_REASON);
11733 }
11734}
11735function repoResume(repo) {
11736 if (repo.persistentConnection_) {
11737 repo.persistentConnection_.resume(INTERRUPT_REASON);
11738 }
11739}
11740function repoLog(repo) {
11741 var varArgs = [];
11742 for (var _i = 1; _i < arguments.length; _i++) {
11743 varArgs[_i - 1] = arguments[_i];
11744 }
11745 var prefix = '';
11746 if (repo.persistentConnection_) {
11747 prefix = repo.persistentConnection_.id + ':';
11748 }
11749 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
11750}
11751function repoCallOnCompleteCallback(repo, callback, status, errorReason) {
11752 if (callback) {
11753 exceptionGuard(function () {
11754 if (status === 'ok') {
11755 callback(null);
11756 }
11757 else {
11758 var code = (status || 'error').toUpperCase();
11759 var message = code;
11760 if (errorReason) {
11761 message += ': ' + errorReason;
11762 }
11763 var error = new Error(message);
11764 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11765 error.code = code;
11766 callback(error);
11767 }
11768 });
11769 }
11770}
11771/**
11772 * Creates a new transaction, adds it to the transactions we're tracking, and
11773 * sends it to the server if possible.
11774 *
11775 * @param path - Path at which to do transaction.
11776 * @param transactionUpdate - Update callback.
11777 * @param onComplete - Completion callback.
11778 * @param unwatcher - Function that will be called when the transaction no longer
11779 * need data updates for `path`.
11780 * @param applyLocally - Whether or not to make intermediate results visible
11781 */
11782function repoStartTransaction(repo, path, transactionUpdate, onComplete, unwatcher, applyLocally) {
11783 repoLog(repo, 'transaction on ' + path);
11784 // Initialize transaction.
11785 var transaction = {
11786 path: path,
11787 update: transactionUpdate,
11788 onComplete: onComplete,
11789 // One of TransactionStatus enums.
11790 status: null,
11791 // Used when combining transactions at different locations to figure out
11792 // which one goes first.
11793 order: LUIDGenerator(),
11794 // Whether to raise local events for this transaction.
11795 applyLocally: applyLocally,
11796 // Count of how many times we've retried the transaction.
11797 retryCount: 0,
11798 // Function to call to clean up our .on() listener.
11799 unwatcher: unwatcher,
11800 // Stores why a transaction was aborted.
11801 abortReason: null,
11802 currentWriteId: null,
11803 currentInputSnapshot: null,
11804 currentOutputSnapshotRaw: null,
11805 currentOutputSnapshotResolved: null
11806 };
11807 // Run transaction initially.
11808 var currentState = repoGetLatestState(repo, path, undefined);
11809 transaction.currentInputSnapshot = currentState;
11810 var newVal = transaction.update(currentState.val());
11811 if (newVal === undefined) {
11812 // Abort transaction.
11813 transaction.unwatcher();
11814 transaction.currentOutputSnapshotRaw = null;
11815 transaction.currentOutputSnapshotResolved = null;
11816 if (transaction.onComplete) {
11817 transaction.onComplete(null, false, transaction.currentInputSnapshot);
11818 }
11819 }
11820 else {
11821 validateFirebaseData('transaction failed: Data returned ', newVal, transaction.path);
11822 // Mark as run and add to our queue.
11823 transaction.status = 0 /* RUN */;
11824 var queueNode = treeSubTree(repo.transactionQueueTree_, path);
11825 var nodeQueue = treeGetValue(queueNode) || [];
11826 nodeQueue.push(transaction);
11827 treeSetValue(queueNode, nodeQueue);
11828 // Update visibleData and raise events
11829 // Note: We intentionally raise events after updating all of our
11830 // transaction state, since the user could start new transactions from the
11831 // event callbacks.
11832 var priorityForNode = void 0;
11833 if (typeof newVal === 'object' &&
11834 newVal !== null &&
11835 util.contains(newVal, '.priority')) {
11836 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11837 priorityForNode = util.safeGet(newVal, '.priority');
11838 util.assert(isValidPriority(priorityForNode), 'Invalid priority returned by transaction. ' +
11839 'Priority must be a valid string, finite number, server value, or null.');
11840 }
11841 else {
11842 var currentNode = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path) ||
11843 ChildrenNode.EMPTY_NODE;
11844 priorityForNode = currentNode.getPriority().val();
11845 }
11846 var serverValues = repoGenerateServerValues(repo);
11847 var newNodeUnresolved = nodeFromJSON(newVal, priorityForNode);
11848 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, currentState, serverValues);
11849 transaction.currentOutputSnapshotRaw = newNodeUnresolved;
11850 transaction.currentOutputSnapshotResolved = newNode;
11851 transaction.currentWriteId = repoGetNextWriteId(repo);
11852 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, transaction.currentWriteId, transaction.applyLocally);
11853 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11854 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11855 }
11856}
11857/**
11858 * @param excludeSets - A specific set to exclude
11859 */
11860function repoGetLatestState(repo, path, excludeSets) {
11861 return (syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path, excludeSets) ||
11862 ChildrenNode.EMPTY_NODE);
11863}
11864/**
11865 * Sends any already-run transactions that aren't waiting for outstanding
11866 * transactions to complete.
11867 *
11868 * Externally it's called with no arguments, but it calls itself recursively
11869 * with a particular transactionQueueTree node to recurse through the tree.
11870 *
11871 * @param node - transactionQueueTree node to start at.
11872 */
11873function repoSendReadyTransactions(repo, node) {
11874 if (node === void 0) { node = repo.transactionQueueTree_; }
11875 // Before recursing, make sure any completed transactions are removed.
11876 if (!node) {
11877 repoPruneCompletedTransactionsBelowNode(repo, node);
11878 }
11879 if (treeGetValue(node)) {
11880 var queue = repoBuildTransactionQueue(repo, node);
11881 util.assert(queue.length > 0, 'Sending zero length transaction queue');
11882 var allRun = queue.every(function (transaction) { return transaction.status === 0 /* RUN */; });
11883 // If they're all run (and not sent), we can send them. Else, we must wait.
11884 if (allRun) {
11885 repoSendTransactionQueue(repo, treeGetPath(node), queue);
11886 }
11887 }
11888 else if (treeHasChildren(node)) {
11889 treeForEachChild(node, function (childNode) {
11890 repoSendReadyTransactions(repo, childNode);
11891 });
11892 }
11893}
11894/**
11895 * Given a list of run transactions, send them to the server and then handle
11896 * the result (success or failure).
11897 *
11898 * @param path - The location of the queue.
11899 * @param queue - Queue of transactions under the specified location.
11900 */
11901function repoSendTransactionQueue(repo, path, queue) {
11902 // Mark transactions as sent and increment retry count!
11903 var setsToIgnore = queue.map(function (txn) {
11904 return txn.currentWriteId;
11905 });
11906 var latestState = repoGetLatestState(repo, path, setsToIgnore);
11907 var snapToSend = latestState;
11908 var latestHash = latestState.hash();
11909 for (var i = 0; i < queue.length; i++) {
11910 var txn = queue[i];
11911 util.assert(txn.status === 0 /* RUN */, 'tryToSendTransactionQueue_: items in queue should all be run.');
11912 txn.status = 1 /* SENT */;
11913 txn.retryCount++;
11914 var relativePath = newRelativePath(path, txn.path);
11915 // If we've gotten to this point, the output snapshot must be defined.
11916 snapToSend = snapToSend.updateChild(relativePath /** @type {!Node} */, txn.currentOutputSnapshotRaw);
11917 }
11918 var dataToSend = snapToSend.val(true);
11919 var pathToSend = path;
11920 // Send the put.
11921 repo.server_.put(pathToSend.toString(), dataToSend, function (status) {
11922 repoLog(repo, 'transaction put response', {
11923 path: pathToSend.toString(),
11924 status: status
11925 });
11926 var events = [];
11927 if (status === 'ok') {
11928 // Queue up the callbacks and fire them after cleaning up all of our
11929 // transaction state, since the callback could trigger more
11930 // transactions or sets.
11931 var callbacks = [];
11932 var _loop_1 = function (i) {
11933 queue[i].status = 2 /* COMPLETED */;
11934 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId));
11935 if (queue[i].onComplete) {
11936 // We never unset the output snapshot, and given that this
11937 // transaction is complete, it should be set
11938 callbacks.push(function () {
11939 return queue[i].onComplete(null, true, queue[i].currentOutputSnapshotResolved);
11940 });
11941 }
11942 queue[i].unwatcher();
11943 };
11944 for (var i = 0; i < queue.length; i++) {
11945 _loop_1(i);
11946 }
11947 // Now remove the completed transactions.
11948 repoPruneCompletedTransactionsBelowNode(repo, treeSubTree(repo.transactionQueueTree_, path));
11949 // There may be pending transactions that we can now send.
11950 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11951 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11952 // Finally, trigger onComplete callbacks.
11953 for (var i = 0; i < callbacks.length; i++) {
11954 exceptionGuard(callbacks[i]);
11955 }
11956 }
11957 else {
11958 // transactions are no longer sent. Update their status appropriately.
11959 if (status === 'datastale') {
11960 for (var i = 0; i < queue.length; i++) {
11961 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) {
11962 queue[i].status = 4 /* NEEDS_ABORT */;
11963 }
11964 else {
11965 queue[i].status = 0 /* RUN */;
11966 }
11967 }
11968 }
11969 else {
11970 warn('transaction at ' + pathToSend.toString() + ' failed: ' + status);
11971 for (var i = 0; i < queue.length; i++) {
11972 queue[i].status = 4 /* NEEDS_ABORT */;
11973 queue[i].abortReason = status;
11974 }
11975 }
11976 repoRerunTransactions(repo, path);
11977 }
11978 }, latestHash);
11979}
11980/**
11981 * Finds all transactions dependent on the data at changedPath and reruns them.
11982 *
11983 * Should be called any time cached data changes.
11984 *
11985 * Return the highest path that was affected by rerunning transactions. This
11986 * is the path at which events need to be raised for.
11987 *
11988 * @param changedPath - The path in mergedData that changed.
11989 * @returns The rootmost path that was affected by rerunning transactions.
11990 */
11991function repoRerunTransactions(repo, changedPath) {
11992 var rootMostTransactionNode = repoGetAncestorTransactionNode(repo, changedPath);
11993 var path = treeGetPath(rootMostTransactionNode);
11994 var queue = repoBuildTransactionQueue(repo, rootMostTransactionNode);
11995 repoRerunTransactionQueue(repo, queue, path);
11996 return path;
11997}
11998/**
11999 * Does all the work of rerunning transactions (as well as cleans up aborted
12000 * transactions and whatnot).
12001 *
12002 * @param queue - The queue of transactions to run.
12003 * @param path - The path the queue is for.
12004 */
12005function repoRerunTransactionQueue(repo, queue, path) {
12006 if (queue.length === 0) {
12007 return; // Nothing to do!
12008 }
12009 // Queue up the callbacks and fire them after cleaning up all of our
12010 // transaction state, since the callback could trigger more transactions or
12011 // sets.
12012 var callbacks = [];
12013 var events = [];
12014 // Ignore all of the sets we're going to re-run.
12015 var txnsToRerun = queue.filter(function (q) {
12016 return q.status === 0 /* RUN */;
12017 });
12018 var setsToIgnore = txnsToRerun.map(function (q) {
12019 return q.currentWriteId;
12020 });
12021 var _loop_2 = function (i) {
12022 var transaction = queue[i];
12023 var relativePath = newRelativePath(path, transaction.path);
12024 var abortTransaction = false, abortReason;
12025 util.assert(relativePath !== null, 'rerunTransactionsUnderNode_: relativePath should not be null.');
12026 if (transaction.status === 4 /* NEEDS_ABORT */) {
12027 abortTransaction = true;
12028 abortReason = transaction.abortReason;
12029 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12030 }
12031 else if (transaction.status === 0 /* RUN */) {
12032 if (transaction.retryCount >= MAX_TRANSACTION_RETRIES) {
12033 abortTransaction = true;
12034 abortReason = 'maxretry';
12035 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12036 }
12037 else {
12038 // This code reruns a transaction
12039 var currentNode = repoGetLatestState(repo, transaction.path, setsToIgnore);
12040 transaction.currentInputSnapshot = currentNode;
12041 var newData = queue[i].update(currentNode.val());
12042 if (newData !== undefined) {
12043 validateFirebaseData('transaction failed: Data returned ', newData, transaction.path);
12044 var newDataNode = nodeFromJSON(newData);
12045 var hasExplicitPriority = typeof newData === 'object' &&
12046 newData != null &&
12047 util.contains(newData, '.priority');
12048 if (!hasExplicitPriority) {
12049 // Keep the old priority if there wasn't a priority explicitly specified.
12050 newDataNode = newDataNode.updatePriority(currentNode.getPriority());
12051 }
12052 var oldWriteId = transaction.currentWriteId;
12053 var serverValues = repoGenerateServerValues(repo);
12054 var newNodeResolved = resolveDeferredValueSnapshot(newDataNode, currentNode, serverValues);
12055 transaction.currentOutputSnapshotRaw = newDataNode;
12056 transaction.currentOutputSnapshotResolved = newNodeResolved;
12057 transaction.currentWriteId = repoGetNextWriteId(repo);
12058 // Mutates setsToIgnore in place
12059 setsToIgnore.splice(setsToIgnore.indexOf(oldWriteId), 1);
12060 events = events.concat(syncTreeApplyUserOverwrite(repo.serverSyncTree_, transaction.path, newNodeResolved, transaction.currentWriteId, transaction.applyLocally));
12061 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, oldWriteId, true));
12062 }
12063 else {
12064 abortTransaction = true;
12065 abortReason = 'nodata';
12066 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12067 }
12068 }
12069 }
12070 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
12071 events = [];
12072 if (abortTransaction) {
12073 // Abort.
12074 queue[i].status = 2 /* COMPLETED */;
12075 // Removing a listener can trigger pruning which can muck with
12076 // mergedData/visibleData (as it prunes data). So defer the unwatcher
12077 // until we're done.
12078 (function (unwatcher) {
12079 setTimeout(unwatcher, Math.floor(0));
12080 })(queue[i].unwatcher);
12081 if (queue[i].onComplete) {
12082 if (abortReason === 'nodata') {
12083 callbacks.push(function () {
12084 return queue[i].onComplete(null, false, queue[i].currentInputSnapshot);
12085 });
12086 }
12087 else {
12088 callbacks.push(function () {
12089 return queue[i].onComplete(new Error(abortReason), false, null);
12090 });
12091 }
12092 }
12093 }
12094 };
12095 for (var i = 0; i < queue.length; i++) {
12096 _loop_2(i);
12097 }
12098 // Clean up completed transactions.
12099 repoPruneCompletedTransactionsBelowNode(repo, repo.transactionQueueTree_);
12100 // Now fire callbacks, now that we're in a good, known state.
12101 for (var i = 0; i < callbacks.length; i++) {
12102 exceptionGuard(callbacks[i]);
12103 }
12104 // Try to send the transaction result to the server.
12105 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
12106}
12107/**
12108 * Returns the rootmost ancestor node of the specified path that has a pending
12109 * transaction on it, or just returns the node for the given path if there are
12110 * no pending transactions on any ancestor.
12111 *
12112 * @param path - The location to start at.
12113 * @returns The rootmost node with a transaction.
12114 */
12115function repoGetAncestorTransactionNode(repo, path) {
12116 var front;
12117 // Start at the root and walk deeper into the tree towards path until we
12118 // find a node with pending transactions.
12119 var transactionNode = repo.transactionQueueTree_;
12120 front = pathGetFront(path);
12121 while (front !== null && treeGetValue(transactionNode) === undefined) {
12122 transactionNode = treeSubTree(transactionNode, front);
12123 path = pathPopFront(path);
12124 front = pathGetFront(path);
12125 }
12126 return transactionNode;
12127}
12128/**
12129 * Builds the queue of all transactions at or below the specified
12130 * transactionNode.
12131 *
12132 * @param transactionNode
12133 * @returns The generated queue.
12134 */
12135function repoBuildTransactionQueue(repo, transactionNode) {
12136 // Walk any child transaction queues and aggregate them into a single queue.
12137 var transactionQueue = [];
12138 repoAggregateTransactionQueuesForNode(repo, transactionNode, transactionQueue);
12139 // Sort them by the order the transactions were created.
12140 transactionQueue.sort(function (a, b) { return a.order - b.order; });
12141 return transactionQueue;
12142}
12143function repoAggregateTransactionQueuesForNode(repo, node, queue) {
12144 var nodeQueue = treeGetValue(node);
12145 if (nodeQueue) {
12146 for (var i = 0; i < nodeQueue.length; i++) {
12147 queue.push(nodeQueue[i]);
12148 }
12149 }
12150 treeForEachChild(node, function (child) {
12151 repoAggregateTransactionQueuesForNode(repo, child, queue);
12152 });
12153}
12154/**
12155 * Remove COMPLETED transactions at or below this node in the transactionQueueTree_.
12156 */
12157function repoPruneCompletedTransactionsBelowNode(repo, node) {
12158 var queue = treeGetValue(node);
12159 if (queue) {
12160 var to = 0;
12161 for (var from = 0; from < queue.length; from++) {
12162 if (queue[from].status !== 2 /* COMPLETED */) {
12163 queue[to] = queue[from];
12164 to++;
12165 }
12166 }
12167 queue.length = to;
12168 treeSetValue(node, queue.length > 0 ? queue : undefined);
12169 }
12170 treeForEachChild(node, function (childNode) {
12171 repoPruneCompletedTransactionsBelowNode(repo, childNode);
12172 });
12173}
12174/**
12175 * Aborts all transactions on ancestors or descendants of the specified path.
12176 * Called when doing a set() or update() since we consider them incompatible
12177 * with transactions.
12178 *
12179 * @param path - Path for which we want to abort related transactions.
12180 */
12181function repoAbortTransactions(repo, path) {
12182 var affectedPath = treeGetPath(repoGetAncestorTransactionNode(repo, path));
12183 var transactionNode = treeSubTree(repo.transactionQueueTree_, path);
12184 treeForEachAncestor(transactionNode, function (node) {
12185 repoAbortTransactionsOnNode(repo, node);
12186 });
12187 repoAbortTransactionsOnNode(repo, transactionNode);
12188 treeForEachDescendant(transactionNode, function (node) {
12189 repoAbortTransactionsOnNode(repo, node);
12190 });
12191 return affectedPath;
12192}
12193/**
12194 * Abort transactions stored in this transaction queue node.
12195 *
12196 * @param node - Node to abort transactions for.
12197 */
12198function repoAbortTransactionsOnNode(repo, node) {
12199 var queue = treeGetValue(node);
12200 if (queue) {
12201 // Queue up the callbacks and fire them after cleaning up all of our
12202 // transaction state, since the callback could trigger more transactions
12203 // or sets.
12204 var callbacks = [];
12205 // Go through queue. Any already-sent transactions must be marked for
12206 // abort, while the unsent ones can be immediately aborted and removed.
12207 var events = [];
12208 var lastSent = -1;
12209 for (var i = 0; i < queue.length; i++) {
12210 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) ;
12211 else if (queue[i].status === 1 /* SENT */) {
12212 util.assert(lastSent === i - 1, 'All SENT items should be at beginning of queue.');
12213 lastSent = i;
12214 // Mark transaction for abort when it comes back.
12215 queue[i].status = 3 /* SENT_NEEDS_ABORT */;
12216 queue[i].abortReason = 'set';
12217 }
12218 else {
12219 util.assert(queue[i].status === 0 /* RUN */, 'Unexpected transaction status in abort');
12220 // We can abort it immediately.
12221 queue[i].unwatcher();
12222 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId, true));
12223 if (queue[i].onComplete) {
12224 callbacks.push(queue[i].onComplete.bind(null, new Error('set'), false, null));
12225 }
12226 }
12227 }
12228 if (lastSent === -1) {
12229 // We're not waiting for any sent transactions. We can clear the queue.
12230 treeSetValue(node, undefined);
12231 }
12232 else {
12233 // Remove the transactions we aborted.
12234 queue.length = lastSent + 1;
12235 }
12236 // Now fire the callbacks.
12237 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, treeGetPath(node), events);
12238 for (var i = 0; i < callbacks.length; i++) {
12239 exceptionGuard(callbacks[i]);
12240 }
12241 }
12242}
12243
12244/**
12245 * @license
12246 * Copyright 2017 Google LLC
12247 *
12248 * Licensed under the Apache License, Version 2.0 (the "License");
12249 * you may not use this file except in compliance with the License.
12250 * You may obtain a copy of the License at
12251 *
12252 * http://www.apache.org/licenses/LICENSE-2.0
12253 *
12254 * Unless required by applicable law or agreed to in writing, software
12255 * distributed under the License is distributed on an "AS IS" BASIS,
12256 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12257 * See the License for the specific language governing permissions and
12258 * limitations under the License.
12259 */
12260function decodePath(pathString) {
12261 var pathStringDecoded = '';
12262 var pieces = pathString.split('/');
12263 for (var i = 0; i < pieces.length; i++) {
12264 if (pieces[i].length > 0) {
12265 var piece = pieces[i];
12266 try {
12267 piece = decodeURIComponent(piece.replace(/\+/g, ' '));
12268 }
12269 catch (e) { }
12270 pathStringDecoded += '/' + piece;
12271 }
12272 }
12273 return pathStringDecoded;
12274}
12275/**
12276 * @returns key value hash
12277 */
12278function decodeQuery(queryString) {
12279 var e_1, _a;
12280 var results = {};
12281 if (queryString.charAt(0) === '?') {
12282 queryString = queryString.substring(1);
12283 }
12284 try {
12285 for (var _b = tslib.__values(queryString.split('&')), _c = _b.next(); !_c.done; _c = _b.next()) {
12286 var segment = _c.value;
12287 if (segment.length === 0) {
12288 continue;
12289 }
12290 var kv = segment.split('=');
12291 if (kv.length === 2) {
12292 results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
12293 }
12294 else {
12295 warn("Invalid query segment '" + segment + "' in query '" + queryString + "'");
12296 }
12297 }
12298 }
12299 catch (e_1_1) { e_1 = { error: e_1_1 }; }
12300 finally {
12301 try {
12302 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
12303 }
12304 finally { if (e_1) throw e_1.error; }
12305 }
12306 return results;
12307}
12308var parseRepoInfo = function (dataURL, nodeAdmin) {
12309 var parsedUrl = parseDatabaseURL(dataURL), namespace = parsedUrl.namespace;
12310 if (parsedUrl.domain === 'firebase.com') {
12311 fatal(parsedUrl.host +
12312 ' is no longer supported. ' +
12313 'Please use <YOUR FIREBASE>.firebaseio.com instead');
12314 }
12315 // Catch common error of uninitialized namespace value.
12316 if ((!namespace || namespace === 'undefined') &&
12317 parsedUrl.domain !== 'localhost') {
12318 fatal('Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com');
12319 }
12320 if (!parsedUrl.secure) {
12321 warnIfPageIsSecure();
12322 }
12323 var webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
12324 return {
12325 repoInfo: new RepoInfo(parsedUrl.host, parsedUrl.secure, namespace, webSocketOnly, nodeAdmin,
12326 /*persistenceKey=*/ '',
12327 /*includeNamespaceInQueryParams=*/ namespace !== parsedUrl.subdomain),
12328 path: new Path(parsedUrl.pathString)
12329 };
12330};
12331var parseDatabaseURL = function (dataURL) {
12332 // Default to empty strings in the event of a malformed string.
12333 var host = '', domain = '', subdomain = '', pathString = '', namespace = '';
12334 // Always default to SSL, unless otherwise specified.
12335 var secure = true, scheme = 'https', port = 443;
12336 // Don't do any validation here. The caller is responsible for validating the result of parsing.
12337 if (typeof dataURL === 'string') {
12338 // Parse scheme.
12339 var colonInd = dataURL.indexOf('//');
12340 if (colonInd >= 0) {
12341 scheme = dataURL.substring(0, colonInd - 1);
12342 dataURL = dataURL.substring(colonInd + 2);
12343 }
12344 // Parse host, path, and query string.
12345 var slashInd = dataURL.indexOf('/');
12346 if (slashInd === -1) {
12347 slashInd = dataURL.length;
12348 }
12349 var questionMarkInd = dataURL.indexOf('?');
12350 if (questionMarkInd === -1) {
12351 questionMarkInd = dataURL.length;
12352 }
12353 host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
12354 if (slashInd < questionMarkInd) {
12355 // For pathString, questionMarkInd will always come after slashInd
12356 pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
12357 }
12358 var queryParams = decodeQuery(dataURL.substring(Math.min(dataURL.length, questionMarkInd)));
12359 // If we have a port, use scheme for determining if it's secure.
12360 colonInd = host.indexOf(':');
12361 if (colonInd >= 0) {
12362 secure = scheme === 'https' || scheme === 'wss';
12363 port = parseInt(host.substring(colonInd + 1), 10);
12364 }
12365 else {
12366 colonInd = host.length;
12367 }
12368 var hostWithoutPort = host.slice(0, colonInd);
12369 if (hostWithoutPort.toLowerCase() === 'localhost') {
12370 domain = 'localhost';
12371 }
12372 else if (hostWithoutPort.split('.').length <= 2) {
12373 domain = hostWithoutPort;
12374 }
12375 else {
12376 // Interpret the subdomain of a 3 or more component URL as the namespace name.
12377 var dotInd = host.indexOf('.');
12378 subdomain = host.substring(0, dotInd).toLowerCase();
12379 domain = host.substring(dotInd + 1);
12380 // Normalize namespaces to lowercase to share storage / connection.
12381 namespace = subdomain;
12382 }
12383 // Always treat the value of the `ns` as the namespace name if it is present.
12384 if ('ns' in queryParams) {
12385 namespace = queryParams['ns'];
12386 }
12387 }
12388 return {
12389 host: host,
12390 port: port,
12391 domain: domain,
12392 subdomain: subdomain,
12393 secure: secure,
12394 scheme: scheme,
12395 pathString: pathString,
12396 namespace: namespace
12397 };
12398};
12399
12400/**
12401 * @license
12402 * Copyright 2017 Google LLC
12403 *
12404 * Licensed under the Apache License, Version 2.0 (the "License");
12405 * you may not use this file except in compliance with the License.
12406 * You may obtain a copy of the License at
12407 *
12408 * http://www.apache.org/licenses/LICENSE-2.0
12409 *
12410 * Unless required by applicable law or agreed to in writing, software
12411 * distributed under the License is distributed on an "AS IS" BASIS,
12412 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12413 * See the License for the specific language governing permissions and
12414 * limitations under the License.
12415 */
12416/**
12417 * Encapsulates the data needed to raise an event
12418 */
12419var DataEvent = /** @class */ (function () {
12420 /**
12421 * @param eventType - One of: value, child_added, child_changed, child_moved, child_removed
12422 * @param eventRegistration - The function to call to with the event data. User provided
12423 * @param snapshot - The data backing the event
12424 * @param prevName - Optional, the name of the previous child for child_* events.
12425 */
12426 function DataEvent(eventType, eventRegistration, snapshot, prevName) {
12427 this.eventType = eventType;
12428 this.eventRegistration = eventRegistration;
12429 this.snapshot = snapshot;
12430 this.prevName = prevName;
12431 }
12432 DataEvent.prototype.getPath = function () {
12433 var ref = this.snapshot.ref;
12434 if (this.eventType === 'value') {
12435 return ref._path;
12436 }
12437 else {
12438 return ref.parent._path;
12439 }
12440 };
12441 DataEvent.prototype.getEventType = function () {
12442 return this.eventType;
12443 };
12444 DataEvent.prototype.getEventRunner = function () {
12445 return this.eventRegistration.getEventRunner(this);
12446 };
12447 DataEvent.prototype.toString = function () {
12448 return (this.getPath().toString() +
12449 ':' +
12450 this.eventType +
12451 ':' +
12452 util.stringify(this.snapshot.exportVal()));
12453 };
12454 return DataEvent;
12455}());
12456var CancelEvent = /** @class */ (function () {
12457 function CancelEvent(eventRegistration, error, path) {
12458 this.eventRegistration = eventRegistration;
12459 this.error = error;
12460 this.path = path;
12461 }
12462 CancelEvent.prototype.getPath = function () {
12463 return this.path;
12464 };
12465 CancelEvent.prototype.getEventType = function () {
12466 return 'cancel';
12467 };
12468 CancelEvent.prototype.getEventRunner = function () {
12469 return this.eventRegistration.getEventRunner(this);
12470 };
12471 CancelEvent.prototype.toString = function () {
12472 return this.path.toString() + ':cancel';
12473 };
12474 return CancelEvent;
12475}());
12476
12477/**
12478 * @license
12479 * Copyright 2017 Google LLC
12480 *
12481 * Licensed under the Apache License, Version 2.0 (the "License");
12482 * you may not use this file except in compliance with the License.
12483 * You may obtain a copy of the License at
12484 *
12485 * http://www.apache.org/licenses/LICENSE-2.0
12486 *
12487 * Unless required by applicable law or agreed to in writing, software
12488 * distributed under the License is distributed on an "AS IS" BASIS,
12489 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12490 * See the License for the specific language governing permissions and
12491 * limitations under the License.
12492 */
12493/**
12494 * A wrapper class that converts events from the database@exp SDK to the legacy
12495 * Database SDK. Events are not converted directly as event registration relies
12496 * on reference comparison of the original user callback (see `matches()`) and
12497 * relies on equality of the legacy SDK's `context` object.
12498 */
12499var CallbackContext = /** @class */ (function () {
12500 function CallbackContext(snapshotCallback, cancelCallback) {
12501 this.snapshotCallback = snapshotCallback;
12502 this.cancelCallback = cancelCallback;
12503 }
12504 CallbackContext.prototype.onValue = function (expDataSnapshot, previousChildName) {
12505 this.snapshotCallback.call(null, expDataSnapshot, previousChildName);
12506 };
12507 CallbackContext.prototype.onCancel = function (error) {
12508 util.assert(this.hasCancelCallback, 'Raising a cancel event on a listener with no cancel callback');
12509 return this.cancelCallback.call(null, error);
12510 };
12511 Object.defineProperty(CallbackContext.prototype, "hasCancelCallback", {
12512 get: function () {
12513 return !!this.cancelCallback;
12514 },
12515 enumerable: false,
12516 configurable: true
12517 });
12518 CallbackContext.prototype.matches = function (other) {
12519 return (this.snapshotCallback === other.snapshotCallback ||
12520 (this.snapshotCallback.userCallback !== undefined &&
12521 this.snapshotCallback.userCallback ===
12522 other.snapshotCallback.userCallback &&
12523 this.snapshotCallback.context === other.snapshotCallback.context));
12524 };
12525 return CallbackContext;
12526}());
12527
12528/**
12529 * @license
12530 * Copyright 2021 Google LLC
12531 *
12532 * Licensed under the Apache License, Version 2.0 (the "License");
12533 * you may not use this file except in compliance with the License.
12534 * You may obtain a copy of the License at
12535 *
12536 * http://www.apache.org/licenses/LICENSE-2.0
12537 *
12538 * Unless required by applicable law or agreed to in writing, software
12539 * distributed under the License is distributed on an "AS IS" BASIS,
12540 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12541 * See the License for the specific language governing permissions and
12542 * limitations under the License.
12543 */
12544/**
12545 * The `onDisconnect` class allows you to write or clear data when your client
12546 * disconnects from the Database server. These updates occur whether your
12547 * client disconnects cleanly or not, so you can rely on them to clean up data
12548 * even if a connection is dropped or a client crashes.
12549 *
12550 * The `onDisconnect` class is most commonly used to manage presence in
12551 * applications where it is useful to detect how many clients are connected and
12552 * when other clients disconnect. See
12553 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12554 * for more information.
12555 *
12556 * To avoid problems when a connection is dropped before the requests can be
12557 * transferred to the Database server, these functions should be called before
12558 * writing any data.
12559 *
12560 * Note that `onDisconnect` operations are only triggered once. If you want an
12561 * operation to occur each time a disconnect occurs, you'll need to re-establish
12562 * the `onDisconnect` operations each time you reconnect.
12563 */
12564var OnDisconnect = /** @class */ (function () {
12565 /** @hideconstructor */
12566 function OnDisconnect(_repo, _path) {
12567 this._repo = _repo;
12568 this._path = _path;
12569 }
12570 /**
12571 * Cancels all previously queued `onDisconnect()` set or update events for this
12572 * location and all children.
12573 *
12574 * If a write has been queued for this location via a `set()` or `update()` at a
12575 * parent location, the write at this location will be canceled, though writes
12576 * to sibling locations will still occur.
12577 *
12578 * @returns Resolves when synchronization to the server is complete.
12579 */
12580 OnDisconnect.prototype.cancel = function () {
12581 var deferred = new util.Deferred();
12582 repoOnDisconnectCancel(this._repo, this._path, deferred.wrapCallback(function () { }));
12583 return deferred.promise;
12584 };
12585 /**
12586 * Ensures the data at this location is deleted when the client is disconnected
12587 * (due to closing the browser, navigating to a new page, or network issues).
12588 *
12589 * @returns Resolves when synchronization to the server is complete.
12590 */
12591 OnDisconnect.prototype.remove = function () {
12592 validateWritablePath('OnDisconnect.remove', this._path);
12593 var deferred = new util.Deferred();
12594 repoOnDisconnectSet(this._repo, this._path, null, deferred.wrapCallback(function () { }));
12595 return deferred.promise;
12596 };
12597 /**
12598 * Ensures the data at this location is set to the specified value when the
12599 * client is disconnected (due to closing the browser, navigating to a new page,
12600 * or network issues).
12601 *
12602 * `set()` is especially useful for implementing "presence" systems, where a
12603 * value should be changed or cleared when a user disconnects so that they
12604 * appear "offline" to other users. See
12605 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12606 * for more information.
12607 *
12608 * Note that `onDisconnect` operations are only triggered once. If you want an
12609 * operation to occur each time a disconnect occurs, you'll need to re-establish
12610 * the `onDisconnect` operations each time.
12611 *
12612 * @param value - The value to be written to this location on disconnect (can
12613 * be an object, array, string, number, boolean, or null).
12614 * @returns Resolves when synchronization to the Database is complete.
12615 */
12616 OnDisconnect.prototype.set = function (value) {
12617 validateWritablePath('OnDisconnect.set', this._path);
12618 validateFirebaseDataArg('OnDisconnect.set', value, this._path, false);
12619 var deferred = new util.Deferred();
12620 repoOnDisconnectSet(this._repo, this._path, value, deferred.wrapCallback(function () { }));
12621 return deferred.promise;
12622 };
12623 /**
12624 * Ensures the data at this location is set to the specified value and priority
12625 * when the client is disconnected (due to closing the browser, navigating to a
12626 * new page, or network issues).
12627 *
12628 * @param value - The value to be written to this location on disconnect (can
12629 * be an object, array, string, number, boolean, or null).
12630 * @param priority - The priority to be written (string, number, or null).
12631 * @returns Resolves when synchronization to the Database is complete.
12632 */
12633 OnDisconnect.prototype.setWithPriority = function (value, priority) {
12634 validateWritablePath('OnDisconnect.setWithPriority', this._path);
12635 validateFirebaseDataArg('OnDisconnect.setWithPriority', value, this._path, false);
12636 validatePriority('OnDisconnect.setWithPriority', priority, false);
12637 var deferred = new util.Deferred();
12638 repoOnDisconnectSetWithPriority(this._repo, this._path, value, priority, deferred.wrapCallback(function () { }));
12639 return deferred.promise;
12640 };
12641 /**
12642 * Writes multiple values at this location when the client is disconnected (due
12643 * to closing the browser, navigating to a new page, or network issues).
12644 *
12645 * The `values` argument contains multiple property-value pairs that will be
12646 * written to the Database together. Each child property can either be a simple
12647 * property (for example, "name") or a relative path (for example, "name/first")
12648 * from the current location to the data to update.
12649 *
12650 * As opposed to the `set()` method, `update()` can be use to selectively update
12651 * only the referenced properties at the current location (instead of replacing
12652 * all the child properties at the current location).
12653 *
12654 * @param values - Object containing multiple values.
12655 * @returns Resolves when synchronization to the Database is complete.
12656 */
12657 OnDisconnect.prototype.update = function (values) {
12658 validateWritablePath('OnDisconnect.update', this._path);
12659 validateFirebaseMergeDataArg('OnDisconnect.update', values, this._path, false);
12660 var deferred = new util.Deferred();
12661 repoOnDisconnectUpdate(this._repo, this._path, values, deferred.wrapCallback(function () { }));
12662 return deferred.promise;
12663 };
12664 return OnDisconnect;
12665}());
12666
12667/**
12668 * @license
12669 * Copyright 2020 Google LLC
12670 *
12671 * Licensed under the Apache License, Version 2.0 (the "License");
12672 * you may not use this file except in compliance with the License.
12673 * You may obtain a copy of the License at
12674 *
12675 * http://www.apache.org/licenses/LICENSE-2.0
12676 *
12677 * Unless required by applicable law or agreed to in writing, software
12678 * distributed under the License is distributed on an "AS IS" BASIS,
12679 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12680 * See the License for the specific language governing permissions and
12681 * limitations under the License.
12682 */
12683/**
12684 * @internal
12685 */
12686var QueryImpl = /** @class */ (function () {
12687 /**
12688 * @hideconstructor
12689 */
12690 function QueryImpl(_repo, _path, _queryParams, _orderByCalled) {
12691 this._repo = _repo;
12692 this._path = _path;
12693 this._queryParams = _queryParams;
12694 this._orderByCalled = _orderByCalled;
12695 }
12696 Object.defineProperty(QueryImpl.prototype, "key", {
12697 get: function () {
12698 if (pathIsEmpty(this._path)) {
12699 return null;
12700 }
12701 else {
12702 return pathGetBack(this._path);
12703 }
12704 },
12705 enumerable: false,
12706 configurable: true
12707 });
12708 Object.defineProperty(QueryImpl.prototype, "ref", {
12709 get: function () {
12710 return new ReferenceImpl(this._repo, this._path);
12711 },
12712 enumerable: false,
12713 configurable: true
12714 });
12715 Object.defineProperty(QueryImpl.prototype, "_queryIdentifier", {
12716 get: function () {
12717 var obj = queryParamsGetQueryObject(this._queryParams);
12718 var id = ObjectToUniqueKey(obj);
12719 return id === '{}' ? 'default' : id;
12720 },
12721 enumerable: false,
12722 configurable: true
12723 });
12724 Object.defineProperty(QueryImpl.prototype, "_queryObject", {
12725 /**
12726 * An object representation of the query parameters used by this Query.
12727 */
12728 get: function () {
12729 return queryParamsGetQueryObject(this._queryParams);
12730 },
12731 enumerable: false,
12732 configurable: true
12733 });
12734 QueryImpl.prototype.isEqual = function (other) {
12735 other = util.getModularInstance(other);
12736 if (!(other instanceof QueryImpl)) {
12737 return false;
12738 }
12739 var sameRepo = this._repo === other._repo;
12740 var samePath = pathEquals(this._path, other._path);
12741 var sameQueryIdentifier = this._queryIdentifier === other._queryIdentifier;
12742 return sameRepo && samePath && sameQueryIdentifier;
12743 };
12744 QueryImpl.prototype.toJSON = function () {
12745 return this.toString();
12746 };
12747 QueryImpl.prototype.toString = function () {
12748 return this._repo.toString() + pathToUrlEncodedString(this._path);
12749 };
12750 return QueryImpl;
12751}());
12752/**
12753 * Validates that no other order by call has been made
12754 */
12755function validateNoPreviousOrderByCall(query, fnName) {
12756 if (query._orderByCalled === true) {
12757 throw new Error(fnName + ": You can't combine multiple orderBy calls.");
12758 }
12759}
12760/**
12761 * Validates start/end values for queries.
12762 */
12763function validateQueryEndpoints(params) {
12764 var startNode = null;
12765 var endNode = null;
12766 if (params.hasStart()) {
12767 startNode = params.getIndexStartValue();
12768 }
12769 if (params.hasEnd()) {
12770 endNode = params.getIndexEndValue();
12771 }
12772 if (params.getIndex() === KEY_INDEX) {
12773 var tooManyArgsError = 'Query: When ordering by key, you may only pass one argument to ' +
12774 'startAt(), endAt(), or equalTo().';
12775 var wrongArgTypeError = 'Query: When ordering by key, the argument passed to startAt(), startAfter(), ' +
12776 'endAt(), endBefore(), or equalTo() must be a string.';
12777 if (params.hasStart()) {
12778 var startName = params.getIndexStartName();
12779 if (startName !== MIN_NAME) {
12780 throw new Error(tooManyArgsError);
12781 }
12782 else if (typeof startNode !== 'string') {
12783 throw new Error(wrongArgTypeError);
12784 }
12785 }
12786 if (params.hasEnd()) {
12787 var endName = params.getIndexEndName();
12788 if (endName !== MAX_NAME) {
12789 throw new Error(tooManyArgsError);
12790 }
12791 else if (typeof endNode !== 'string') {
12792 throw new Error(wrongArgTypeError);
12793 }
12794 }
12795 }
12796 else if (params.getIndex() === PRIORITY_INDEX) {
12797 if ((startNode != null && !isValidPriority(startNode)) ||
12798 (endNode != null && !isValidPriority(endNode))) {
12799 throw new Error('Query: When ordering by priority, the first argument passed to startAt(), ' +
12800 'startAfter() endAt(), endBefore(), or equalTo() must be a valid priority value ' +
12801 '(null, a number, or a string).');
12802 }
12803 }
12804 else {
12805 util.assert(params.getIndex() instanceof PathIndex ||
12806 params.getIndex() === VALUE_INDEX, 'unknown index type.');
12807 if ((startNode != null && typeof startNode === 'object') ||
12808 (endNode != null && typeof endNode === 'object')) {
12809 throw new Error('Query: First argument passed to startAt(), startAfter(), endAt(), endBefore(), or ' +
12810 'equalTo() cannot be an object.');
12811 }
12812 }
12813}
12814/**
12815 * Validates that limit* has been called with the correct combination of parameters
12816 */
12817function validateLimit(params) {
12818 if (params.hasStart() &&
12819 params.hasEnd() &&
12820 params.hasLimit() &&
12821 !params.hasAnchoredLimit()) {
12822 throw new Error("Query: Can't combine startAt(), startAfter(), endAt(), endBefore(), and limit(). Use " +
12823 'limitToFirst() or limitToLast() instead.');
12824 }
12825}
12826/**
12827 * @internal
12828 */
12829var ReferenceImpl = /** @class */ (function (_super) {
12830 tslib.__extends(ReferenceImpl, _super);
12831 /** @hideconstructor */
12832 function ReferenceImpl(repo, path) {
12833 return _super.call(this, repo, path, new QueryParams(), false) || this;
12834 }
12835 Object.defineProperty(ReferenceImpl.prototype, "parent", {
12836 get: function () {
12837 var parentPath = pathParent(this._path);
12838 return parentPath === null
12839 ? null
12840 : new ReferenceImpl(this._repo, parentPath);
12841 },
12842 enumerable: false,
12843 configurable: true
12844 });
12845 Object.defineProperty(ReferenceImpl.prototype, "root", {
12846 get: function () {
12847 var ref = this;
12848 while (ref.parent !== null) {
12849 ref = ref.parent;
12850 }
12851 return ref;
12852 },
12853 enumerable: false,
12854 configurable: true
12855 });
12856 return ReferenceImpl;
12857}(QueryImpl));
12858/**
12859 * A `DataSnapshot` contains data from a Database location.
12860 *
12861 * Any time you read data from the Database, you receive the data as a
12862 * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach
12863 * with `on()` or `once()`. You can extract the contents of the snapshot as a
12864 * JavaScript object by calling the `val()` method. Alternatively, you can
12865 * traverse into the snapshot by calling `child()` to return child snapshots
12866 * (which you could then call `val()` on).
12867 *
12868 * A `DataSnapshot` is an efficiently generated, immutable copy of the data at
12869 * a Database location. It cannot be modified and will never change (to modify
12870 * data, you always call the `set()` method on a `Reference` directly).
12871 */
12872var DataSnapshot = /** @class */ (function () {
12873 /**
12874 * @param _node - A SnapshotNode to wrap.
12875 * @param ref - The location this snapshot came from.
12876 * @param _index - The iteration order for this snapshot
12877 * @hideconstructor
12878 */
12879 function DataSnapshot(_node,
12880 /**
12881 * The location of this DataSnapshot.
12882 */
12883 ref, _index) {
12884 this._node = _node;
12885 this.ref = ref;
12886 this._index = _index;
12887 }
12888 Object.defineProperty(DataSnapshot.prototype, "priority", {
12889 /**
12890 * Gets the priority value of the data in this `DataSnapshot`.
12891 *
12892 * Applications need not use priority but can order collections by
12893 * ordinary properties (see
12894 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data |Sorting and filtering data}
12895 * ).
12896 */
12897 get: function () {
12898 // typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
12899 return this._node.getPriority().val();
12900 },
12901 enumerable: false,
12902 configurable: true
12903 });
12904 Object.defineProperty(DataSnapshot.prototype, "key", {
12905 /**
12906 * The key (last part of the path) of the location of this `DataSnapshot`.
12907 *
12908 * The last token in a Database location is considered its key. For example,
12909 * "ada" is the key for the /users/ada/ node. Accessing the key on any
12910 * `DataSnapshot` will return the key for the location that generated it.
12911 * However, accessing the key on the root URL of a Database will return
12912 * `null`.
12913 */
12914 get: function () {
12915 return this.ref.key;
12916 },
12917 enumerable: false,
12918 configurable: true
12919 });
12920 Object.defineProperty(DataSnapshot.prototype, "size", {
12921 /** Returns the number of child properties of this `DataSnapshot`. */
12922 get: function () {
12923 return this._node.numChildren();
12924 },
12925 enumerable: false,
12926 configurable: true
12927 });
12928 /**
12929 * Gets another `DataSnapshot` for the location at the specified relative path.
12930 *
12931 * Passing a relative path to the `child()` method of a DataSnapshot returns
12932 * another `DataSnapshot` for the location at the specified relative path. The
12933 * relative path can either be a simple child name (for example, "ada") or a
12934 * deeper, slash-separated path (for example, "ada/name/first"). If the child
12935 * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot`
12936 * whose value is `null`) is returned.
12937 *
12938 * @param path - A relative path to the location of child data.
12939 */
12940 DataSnapshot.prototype.child = function (path) {
12941 var childPath = new Path(path);
12942 var childRef = child(this.ref, path);
12943 return new DataSnapshot(this._node.getChild(childPath), childRef, PRIORITY_INDEX);
12944 };
12945 /**
12946 * Returns true if this `DataSnapshot` contains any data. It is slightly more
12947 * efficient than using `snapshot.val() !== null`.
12948 */
12949 DataSnapshot.prototype.exists = function () {
12950 return !this._node.isEmpty();
12951 };
12952 /**
12953 * Exports the entire contents of the DataSnapshot as a JavaScript object.
12954 *
12955 * The `exportVal()` method is similar to `val()`, except priority information
12956 * is included (if available), making it suitable for backing up your data.
12957 *
12958 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12959 * Array, string, number, boolean, or `null`).
12960 */
12961 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12962 DataSnapshot.prototype.exportVal = function () {
12963 return this._node.val(true);
12964 };
12965 /**
12966 * Enumerates the top-level children in the `DataSnapshot`.
12967 *
12968 * Because of the way JavaScript objects work, the ordering of data in the
12969 * JavaScript object returned by `val()` is not guaranteed to match the
12970 * ordering on the server nor the ordering of `onChildAdded()` events. That is
12971 * where `forEach()` comes in handy. It guarantees the children of a
12972 * `DataSnapshot` will be iterated in their query order.
12973 *
12974 * If no explicit `orderBy*()` method is used, results are returned
12975 * ordered by key (unless priorities are used, in which case, results are
12976 * returned by priority).
12977 *
12978 * @param action - A function that will be called for each child DataSnapshot.
12979 * The callback can return true to cancel further enumeration.
12980 * @returns true if enumeration was canceled due to your callback returning
12981 * true.
12982 */
12983 DataSnapshot.prototype.forEach = function (action) {
12984 var _this = this;
12985 if (this._node.isLeafNode()) {
12986 return false;
12987 }
12988 var childrenNode = this._node;
12989 // Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
12990 return !!childrenNode.forEachChild(this._index, function (key, node) {
12991 return action(new DataSnapshot(node, child(_this.ref, key), PRIORITY_INDEX));
12992 });
12993 };
12994 /**
12995 * Returns true if the specified child path has (non-null) data.
12996 *
12997 * @param path - A relative path to the location of a potential child.
12998 * @returns `true` if data exists at the specified child path; else
12999 * `false`.
13000 */
13001 DataSnapshot.prototype.hasChild = function (path) {
13002 var childPath = new Path(path);
13003 return !this._node.getChild(childPath).isEmpty();
13004 };
13005 /**
13006 * Returns whether or not the `DataSnapshot` has any non-`null` child
13007 * properties.
13008 *
13009 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
13010 * children. If it does, you can enumerate them using `forEach()`. If it
13011 * doesn't, then either this snapshot contains a primitive value (which can be
13012 * retrieved with `val()`) or it is empty (in which case, `val()` will return
13013 * `null`).
13014 *
13015 * @returns true if this snapshot has any children; else false.
13016 */
13017 DataSnapshot.prototype.hasChildren = function () {
13018 if (this._node.isLeafNode()) {
13019 return false;
13020 }
13021 else {
13022 return !this._node.isEmpty();
13023 }
13024 };
13025 /**
13026 * Returns a JSON-serializable representation of this object.
13027 */
13028 DataSnapshot.prototype.toJSON = function () {
13029 return this.exportVal();
13030 };
13031 /**
13032 * Extracts a JavaScript value from a `DataSnapshot`.
13033 *
13034 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
13035 * scalar type (string, number, or boolean), an array, or an object. It may
13036 * also return null, indicating that the `DataSnapshot` is empty (contains no
13037 * data).
13038 *
13039 * @returns The DataSnapshot's contents as a JavaScript value (Object,
13040 * Array, string, number, boolean, or `null`).
13041 */
13042 // eslint-disable-next-line @typescript-eslint/no-explicit-any
13043 DataSnapshot.prototype.val = function () {
13044 return this._node.val();
13045 };
13046 return DataSnapshot;
13047}());
13048/**
13049 *
13050 * Returns a `Reference` representing the location in the Database
13051 * corresponding to the provided path. If no path is provided, the `Reference`
13052 * will point to the root of the Database.
13053 *
13054 * @param db - The database instance to obtain a reference for.
13055 * @param path - Optional path representing the location the returned
13056 * `Reference` will point. If not provided, the returned `Reference` will
13057 * point to the root of the Database.
13058 * @returns If a path is provided, a `Reference`
13059 * pointing to the provided path. Otherwise, a `Reference` pointing to the
13060 * root of the Database.
13061 */
13062function ref(db, path) {
13063 db = util.getModularInstance(db);
13064 db._checkNotDeleted('ref');
13065 return path !== undefined ? child(db._root, path) : db._root;
13066}
13067/**
13068 * Returns a `Reference` representing the location in the Database
13069 * corresponding to the provided Firebase URL.
13070 *
13071 * An exception is thrown if the URL is not a valid Firebase Database URL or it
13072 * has a different domain than the current `Database` instance.
13073 *
13074 * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored
13075 * and are not applied to the returned `Reference`.
13076 *
13077 * @param db - The database instance to obtain a reference for.
13078 * @param url - The Firebase URL at which the returned `Reference` will
13079 * point.
13080 * @returns A `Reference` pointing to the provided
13081 * Firebase URL.
13082 */
13083function refFromURL(db, url) {
13084 db = util.getModularInstance(db);
13085 db._checkNotDeleted('refFromURL');
13086 var parsedURL = parseRepoInfo(url, db._repo.repoInfo_.nodeAdmin);
13087 validateUrl('refFromURL', parsedURL);
13088 var repoInfo = parsedURL.repoInfo;
13089 if (!db._repo.repoInfo_.isCustomHost() &&
13090 repoInfo.host !== db._repo.repoInfo_.host) {
13091 fatal('refFromURL' +
13092 ': Host name does not match the current database: ' +
13093 '(found ' +
13094 repoInfo.host +
13095 ' but expected ' +
13096 db._repo.repoInfo_.host +
13097 ')');
13098 }
13099 return ref(db, parsedURL.path.toString());
13100}
13101/**
13102 * Gets a `Reference` for the location at the specified relative path.
13103 *
13104 * The relative path can either be a simple child name (for example, "ada") or
13105 * a deeper slash-separated path (for example, "ada/name/first").
13106 *
13107 * @param parent - The parent location.
13108 * @param path - A relative path from this location to the desired child
13109 * location.
13110 * @returns The specified child location.
13111 */
13112function child(parent, path) {
13113 parent = util.getModularInstance(parent);
13114 if (pathGetFront(parent._path) === null) {
13115 validateRootPathString('child', 'path', path, false);
13116 }
13117 else {
13118 validatePathString('child', 'path', path, false);
13119 }
13120 return new ReferenceImpl(parent._repo, pathChild(parent._path, path));
13121}
13122/**
13123 * Returns an `OnDisconnect` object - see
13124 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
13125 * for more information on how to use it.
13126 *
13127 * @param ref - The reference to add OnDisconnect triggers for.
13128 */
13129function onDisconnect(ref) {
13130 ref = util.getModularInstance(ref);
13131 return new OnDisconnect(ref._repo, ref._path);
13132}
13133/**
13134 * Generates a new child location using a unique key and returns its
13135 * `Reference`.
13136 *
13137 * This is the most common pattern for adding data to a collection of items.
13138 *
13139 * If you provide a value to `push()`, the value is written to the
13140 * generated location. If you don't pass a value, nothing is written to the
13141 * database and the child remains empty (but you can use the `Reference`
13142 * elsewhere).
13143 *
13144 * The unique keys generated by `push()` are ordered by the current time, so the
13145 * resulting list of items is chronologically sorted. The keys are also
13146 * designed to be unguessable (they contain 72 random bits of entropy).
13147 *
13148 * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}
13149 * </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}
13150 *
13151 * @param parent - The parent location.
13152 * @param value - Optional value to be written at the generated location.
13153 * @returns Combined `Promise` and `Reference`; resolves when write is complete,
13154 * but can be used immediately as the `Reference` to the child location.
13155 */
13156function push(parent, value) {
13157 parent = util.getModularInstance(parent);
13158 validateWritablePath('push', parent._path);
13159 validateFirebaseDataArg('push', value, parent._path, true);
13160 var now = repoServerTime(parent._repo);
13161 var name = nextPushId(now);
13162 // push() returns a ThennableReference whose promise is fulfilled with a
13163 // regular Reference. We use child() to create handles to two different
13164 // references. The first is turned into a ThennableReference below by adding
13165 // then() and catch() methods and is used as the return value of push(). The
13166 // second remains a regular Reference and is used as the fulfilled value of
13167 // the first ThennableReference.
13168 var thennablePushRef = child(parent, name);
13169 var pushRef = child(parent, name);
13170 var promise;
13171 if (value != null) {
13172 promise = set(pushRef, value).then(function () { return pushRef; });
13173 }
13174 else {
13175 promise = Promise.resolve(pushRef);
13176 }
13177 thennablePushRef.then = promise.then.bind(promise);
13178 thennablePushRef.catch = promise.then.bind(promise, undefined);
13179 return thennablePushRef;
13180}
13181/**
13182 * Removes the data at this Database location.
13183 *
13184 * Any data at child locations will also be deleted.
13185 *
13186 * The effect of the remove will be visible immediately and the corresponding
13187 * event 'value' will be triggered. Synchronization of the remove to the
13188 * Firebase servers will also be started, and the returned Promise will resolve
13189 * when complete. If provided, the onComplete callback will be called
13190 * asynchronously after synchronization has finished.
13191 *
13192 * @param ref - The location to remove.
13193 * @returns Resolves when remove on server is complete.
13194 */
13195function remove(ref) {
13196 validateWritablePath('remove', ref._path);
13197 return set(ref, null);
13198}
13199/**
13200 * Writes data to this Database location.
13201 *
13202 * This will overwrite any data at this location and all child locations.
13203 *
13204 * The effect of the write will be visible immediately, and the corresponding
13205 * events ("value", "child_added", etc.) will be triggered. Synchronization of
13206 * the data to the Firebase servers will also be started, and the returned
13207 * Promise will resolve when complete. If provided, the `onComplete` callback
13208 * will be called asynchronously after synchronization has finished.
13209 *
13210 * Passing `null` for the new value is equivalent to calling `remove()`; namely,
13211 * all data at this location and all child locations will be deleted.
13212 *
13213 * `set()` will remove any priority stored at this location, so if priority is
13214 * meant to be preserved, you need to use `setWithPriority()` instead.
13215 *
13216 * Note that modifying data with `set()` will cancel any pending transactions
13217 * at that location, so extreme care should be taken if mixing `set()` and
13218 * `transaction()` to modify the same data.
13219 *
13220 * A single `set()` will generate a single "value" event at the location where
13221 * the `set()` was performed.
13222 *
13223 * @param ref - The location to write to.
13224 * @param value - The value to be written (string, number, boolean, object,
13225 * array, or null).
13226 * @returns Resolves when write to server is complete.
13227 */
13228function set(ref, value) {
13229 ref = util.getModularInstance(ref);
13230 validateWritablePath('set', ref._path);
13231 validateFirebaseDataArg('set', value, ref._path, false);
13232 var deferred = new util.Deferred();
13233 repoSetWithPriority(ref._repo, ref._path, value,
13234 /*priority=*/ null, deferred.wrapCallback(function () { }));
13235 return deferred.promise;
13236}
13237/**
13238 * Sets a priority for the data at this Database location.
13239 *
13240 * Applications need not use priority but can order collections by
13241 * ordinary properties (see
13242 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13243 * ).
13244 *
13245 * @param ref - The location to write to.
13246 * @param priority - The priority to be written (string, number, or null).
13247 * @returns Resolves when write to server is complete.
13248 */
13249function setPriority(ref, priority) {
13250 ref = util.getModularInstance(ref);
13251 validateWritablePath('setPriority', ref._path);
13252 validatePriority('setPriority', priority, false);
13253 var deferred = new util.Deferred();
13254 repoSetWithPriority(ref._repo, pathChild(ref._path, '.priority'), priority, null, deferred.wrapCallback(function () { }));
13255 return deferred.promise;
13256}
13257/**
13258 * Writes data the Database location. Like `set()` but also specifies the
13259 * priority for that data.
13260 *
13261 * Applications need not use priority but can order collections by
13262 * ordinary properties (see
13263 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13264 * ).
13265 *
13266 * @param ref - The location to write to.
13267 * @param value - The value to be written (string, number, boolean, object,
13268 * array, or null).
13269 * @param priority - The priority to be written (string, number, or null).
13270 * @returns Resolves when write to server is complete.
13271 */
13272function setWithPriority(ref, value, priority) {
13273 validateWritablePath('setWithPriority', ref._path);
13274 validateFirebaseDataArg('setWithPriority', value, ref._path, false);
13275 validatePriority('setWithPriority', priority, false);
13276 if (ref.key === '.length' || ref.key === '.keys') {
13277 throw 'setWithPriority failed: ' + ref.key + ' is a read-only object.';
13278 }
13279 var deferred = new util.Deferred();
13280 repoSetWithPriority(ref._repo, ref._path, value, priority, deferred.wrapCallback(function () { }));
13281 return deferred.promise;
13282}
13283/**
13284 * Writes multiple values to the Database at once.
13285 *
13286 * The `values` argument contains multiple property-value pairs that will be
13287 * written to the Database together. Each child property can either be a simple
13288 * property (for example, "name") or a relative path (for example,
13289 * "name/first") from the current location to the data to update.
13290 *
13291 * As opposed to the `set()` method, `update()` can be use to selectively update
13292 * only the referenced properties at the current location (instead of replacing
13293 * all the child properties at the current location).
13294 *
13295 * The effect of the write will be visible immediately, and the corresponding
13296 * events ('value', 'child_added', etc.) will be triggered. Synchronization of
13297 * the data to the Firebase servers will also be started, and the returned
13298 * Promise will resolve when complete. If provided, the `onComplete` callback
13299 * will be called asynchronously after synchronization has finished.
13300 *
13301 * A single `update()` will generate a single "value" event at the location
13302 * where the `update()` was performed, regardless of how many children were
13303 * modified.
13304 *
13305 * Note that modifying data with `update()` will cancel any pending
13306 * transactions at that location, so extreme care should be taken if mixing
13307 * `update()` and `transaction()` to modify the same data.
13308 *
13309 * Passing `null` to `update()` will remove the data at this location.
13310 *
13311 * See
13312 * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}.
13313 *
13314 * @param ref - The location to write to.
13315 * @param values - Object containing multiple values.
13316 * @returns Resolves when update on server is complete.
13317 */
13318function update(ref, values) {
13319 validateFirebaseMergeDataArg('update', values, ref._path, false);
13320 var deferred = new util.Deferred();
13321 repoUpdate(ref._repo, ref._path, values, deferred.wrapCallback(function () { }));
13322 return deferred.promise;
13323}
13324/**
13325 * Gets the most up-to-date result for this query.
13326 *
13327 * @param query - The query to run.
13328 * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is
13329 * available, or rejects if the client is unable to return a value (e.g., if the
13330 * server is unreachable and there is nothing cached).
13331 */
13332function get(query) {
13333 query = util.getModularInstance(query);
13334 return repoGetValue(query._repo, query).then(function (node) {
13335 return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
13336 });
13337}
13338/**
13339 * Represents registration for 'value' events.
13340 */
13341var ValueEventRegistration = /** @class */ (function () {
13342 function ValueEventRegistration(callbackContext) {
13343 this.callbackContext = callbackContext;
13344 }
13345 ValueEventRegistration.prototype.respondsTo = function (eventType) {
13346 return eventType === 'value';
13347 };
13348 ValueEventRegistration.prototype.createEvent = function (change, query) {
13349 var index = query._queryParams.getIndex();
13350 return new DataEvent('value', this, new DataSnapshot(change.snapshotNode, new ReferenceImpl(query._repo, query._path), index));
13351 };
13352 ValueEventRegistration.prototype.getEventRunner = function (eventData) {
13353 var _this = this;
13354 if (eventData.getEventType() === 'cancel') {
13355 return function () {
13356 return _this.callbackContext.onCancel(eventData.error);
13357 };
13358 }
13359 else {
13360 return function () {
13361 return _this.callbackContext.onValue(eventData.snapshot, null);
13362 };
13363 }
13364 };
13365 ValueEventRegistration.prototype.createCancelEvent = function (error, path) {
13366 if (this.callbackContext.hasCancelCallback) {
13367 return new CancelEvent(this, error, path);
13368 }
13369 else {
13370 return null;
13371 }
13372 };
13373 ValueEventRegistration.prototype.matches = function (other) {
13374 if (!(other instanceof ValueEventRegistration)) {
13375 return false;
13376 }
13377 else if (!other.callbackContext || !this.callbackContext) {
13378 // If no callback specified, we consider it to match any callback.
13379 return true;
13380 }
13381 else {
13382 return other.callbackContext.matches(this.callbackContext);
13383 }
13384 };
13385 ValueEventRegistration.prototype.hasAnyCallback = function () {
13386 return this.callbackContext !== null;
13387 };
13388 return ValueEventRegistration;
13389}());
13390/**
13391 * Represents the registration of a child_x event.
13392 */
13393var ChildEventRegistration = /** @class */ (function () {
13394 function ChildEventRegistration(eventType, callbackContext) {
13395 this.eventType = eventType;
13396 this.callbackContext = callbackContext;
13397 }
13398 ChildEventRegistration.prototype.respondsTo = function (eventType) {
13399 var eventToCheck = eventType === 'children_added' ? 'child_added' : eventType;
13400 eventToCheck =
13401 eventToCheck === 'children_removed' ? 'child_removed' : eventToCheck;
13402 return this.eventType === eventToCheck;
13403 };
13404 ChildEventRegistration.prototype.createCancelEvent = function (error, path) {
13405 if (this.callbackContext.hasCancelCallback) {
13406 return new CancelEvent(this, error, path);
13407 }
13408 else {
13409 return null;
13410 }
13411 };
13412 ChildEventRegistration.prototype.createEvent = function (change, query) {
13413 util.assert(change.childName != null, 'Child events should have a childName.');
13414 var childRef = child(new ReferenceImpl(query._repo, query._path), change.childName);
13415 var index = query._queryParams.getIndex();
13416 return new DataEvent(change.type, this, new DataSnapshot(change.snapshotNode, childRef, index), change.prevName);
13417 };
13418 ChildEventRegistration.prototype.getEventRunner = function (eventData) {
13419 var _this = this;
13420 if (eventData.getEventType() === 'cancel') {
13421 return function () {
13422 return _this.callbackContext.onCancel(eventData.error);
13423 };
13424 }
13425 else {
13426 return function () {
13427 return _this.callbackContext.onValue(eventData.snapshot, eventData.prevName);
13428 };
13429 }
13430 };
13431 ChildEventRegistration.prototype.matches = function (other) {
13432 if (other instanceof ChildEventRegistration) {
13433 return (this.eventType === other.eventType &&
13434 (!this.callbackContext ||
13435 !other.callbackContext ||
13436 this.callbackContext.matches(other.callbackContext)));
13437 }
13438 return false;
13439 };
13440 ChildEventRegistration.prototype.hasAnyCallback = function () {
13441 return !!this.callbackContext;
13442 };
13443 return ChildEventRegistration;
13444}());
13445function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) {
13446 var cancelCallback;
13447 if (typeof cancelCallbackOrListenOptions === 'object') {
13448 cancelCallback = undefined;
13449 options = cancelCallbackOrListenOptions;
13450 }
13451 if (typeof cancelCallbackOrListenOptions === 'function') {
13452 cancelCallback = cancelCallbackOrListenOptions;
13453 }
13454 if (options && options.onlyOnce) {
13455 var userCallback_1 = callback;
13456 var onceCallback = function (dataSnapshot, previousChildName) {
13457 repoRemoveEventCallbackForQuery(query._repo, query, container);
13458 userCallback_1(dataSnapshot, previousChildName);
13459 };
13460 onceCallback.userCallback = callback.userCallback;
13461 onceCallback.context = callback.context;
13462 callback = onceCallback;
13463 }
13464 var callbackContext = new CallbackContext(callback, cancelCallback || undefined);
13465 var container = eventType === 'value'
13466 ? new ValueEventRegistration(callbackContext)
13467 : new ChildEventRegistration(eventType, callbackContext);
13468 repoAddEventCallbackForQuery(query._repo, query, container);
13469 return function () { return repoRemoveEventCallbackForQuery(query._repo, query, container); };
13470}
13471function onValue(query, callback, cancelCallbackOrListenOptions, options) {
13472 return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options);
13473}
13474function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) {
13475 return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options);
13476}
13477function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) {
13478 return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options);
13479}
13480function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) {
13481 return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options);
13482}
13483function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) {
13484 return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options);
13485}
13486/**
13487 * Detaches a callback previously attached with `on()`.
13488 *
13489 * Detach a callback previously attached with `on()`. Note that if `on()` was
13490 * called multiple times with the same eventType and callback, the callback
13491 * will be called multiple times for each event, and `off()` must be called
13492 * multiple times to remove the callback. Calling `off()` on a parent listener
13493 * will not automatically remove listeners registered on child nodes, `off()`
13494 * must also be called on any child listeners to remove the callback.
13495 *
13496 * If a callback is not specified, all callbacks for the specified eventType
13497 * will be removed. Similarly, if no eventType is specified, all callbacks
13498 * for the `Reference` will be removed.
13499 *
13500 * Individual listeners can also be removed by invoking their unsubscribe
13501 * callbacks.
13502 *
13503 * @param query - The query that the listener was registered with.
13504 * @param eventType - One of the following strings: "value", "child_added",
13505 * "child_changed", "child_removed", or "child_moved." If omitted, all callbacks
13506 * for the `Reference` will be removed.
13507 * @param callback - The callback function that was passed to `on()` or
13508 * `undefined` to remove all callbacks.
13509 */
13510function off(query, eventType, callback) {
13511 var container = null;
13512 var expCallback = callback ? new CallbackContext(callback) : null;
13513 if (eventType === 'value') {
13514 container = new ValueEventRegistration(expCallback);
13515 }
13516 else if (eventType) {
13517 container = new ChildEventRegistration(eventType, expCallback);
13518 }
13519 repoRemoveEventCallbackForQuery(query._repo, query, container);
13520}
13521/**
13522 * A `QueryConstraint` is used to narrow the set of documents returned by a
13523 * Database query. `QueryConstraint`s are created by invoking {@link endAt},
13524 * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link
13525 * limitToFirst}, {@link limitToLast}, {@link orderByChild},
13526 * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} ,
13527 * {@link orderByValue} or {@link equalTo} and
13528 * can then be passed to {@link query} to create a new query instance that
13529 * also contains this `QueryConstraint`.
13530 */
13531var QueryConstraint = /** @class */ (function () {
13532 function QueryConstraint() {
13533 }
13534 return QueryConstraint;
13535}());
13536var QueryEndAtConstraint = /** @class */ (function (_super) {
13537 tslib.__extends(QueryEndAtConstraint, _super);
13538 function QueryEndAtConstraint(_value, _key) {
13539 var _this = _super.call(this) || this;
13540 _this._value = _value;
13541 _this._key = _key;
13542 return _this;
13543 }
13544 QueryEndAtConstraint.prototype._apply = function (query) {
13545 validateFirebaseDataArg('endAt', this._value, query._path, true);
13546 var newParams = queryParamsEndAt(query._queryParams, this._value, this._key);
13547 validateLimit(newParams);
13548 validateQueryEndpoints(newParams);
13549 if (query._queryParams.hasEnd()) {
13550 throw new Error('endAt: Starting point was already set (by another call to endAt, ' +
13551 'endBefore or equalTo).');
13552 }
13553 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13554 };
13555 return QueryEndAtConstraint;
13556}(QueryConstraint));
13557/**
13558 * Creates a `QueryConstraint` with the specified ending point.
13559 *
13560 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13561 * allows you to choose arbitrary starting and ending points for your queries.
13562 *
13563 * The ending point is inclusive, so children with exactly the specified value
13564 * will be included in the query. The optional key argument can be used to
13565 * further limit the range of the query. If it is specified, then children that
13566 * have exactly the specified value must also have a key name less than or equal
13567 * to the specified key.
13568 *
13569 * You can read more about `endAt()` in
13570 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13571 *
13572 * @param value - The value to end at. The argument type depends on which
13573 * `orderBy*()` function was used in this query. Specify a value that matches
13574 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13575 * value must be a string.
13576 * @param key - The child key to end at, among the children with the previously
13577 * specified priority. This argument is only allowed if ordering by child,
13578 * value, or priority.
13579 */
13580function endAt(value, key) {
13581 validateKey('endAt', 'key', key, true);
13582 return new QueryEndAtConstraint(value, key);
13583}
13584var QueryEndBeforeConstraint = /** @class */ (function (_super) {
13585 tslib.__extends(QueryEndBeforeConstraint, _super);
13586 function QueryEndBeforeConstraint(_value, _key) {
13587 var _this = _super.call(this) || this;
13588 _this._value = _value;
13589 _this._key = _key;
13590 return _this;
13591 }
13592 QueryEndBeforeConstraint.prototype._apply = function (query) {
13593 validateFirebaseDataArg('endBefore', this._value, query._path, false);
13594 var newParams = queryParamsEndBefore(query._queryParams, this._value, this._key);
13595 validateLimit(newParams);
13596 validateQueryEndpoints(newParams);
13597 if (query._queryParams.hasEnd()) {
13598 throw new Error('endBefore: Starting point was already set (by another call to endAt, ' +
13599 'endBefore or equalTo).');
13600 }
13601 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13602 };
13603 return QueryEndBeforeConstraint;
13604}(QueryConstraint));
13605/**
13606 * Creates a `QueryConstraint` with the specified ending point (exclusive).
13607 *
13608 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13609 * allows you to choose arbitrary starting and ending points for your queries.
13610 *
13611 * The ending point is exclusive. If only a value is provided, children
13612 * with a value less than the specified value will be included in the query.
13613 * If a key is specified, then children must have a value lesss than or equal
13614 * to the specified value and a a key name less than the specified key.
13615 *
13616 * @param value - The value to end before. The argument type depends on which
13617 * `orderBy*()` function was used in this query. Specify a value that matches
13618 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13619 * value must be a string.
13620 * @param key - The child key to end before, among the children with the
13621 * previously specified priority. This argument is only allowed if ordering by
13622 * child, value, or priority.
13623 */
13624function endBefore(value, key) {
13625 validateKey('endBefore', 'key', key, true);
13626 return new QueryEndBeforeConstraint(value, key);
13627}
13628var QueryStartAtConstraint = /** @class */ (function (_super) {
13629 tslib.__extends(QueryStartAtConstraint, _super);
13630 function QueryStartAtConstraint(_value, _key) {
13631 var _this = _super.call(this) || this;
13632 _this._value = _value;
13633 _this._key = _key;
13634 return _this;
13635 }
13636 QueryStartAtConstraint.prototype._apply = function (query) {
13637 validateFirebaseDataArg('startAt', this._value, query._path, true);
13638 var newParams = queryParamsStartAt(query._queryParams, this._value, this._key);
13639 validateLimit(newParams);
13640 validateQueryEndpoints(newParams);
13641 if (query._queryParams.hasStart()) {
13642 throw new Error('startAt: Starting point was already set (by another call to startAt, ' +
13643 'startBefore or equalTo).');
13644 }
13645 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13646 };
13647 return QueryStartAtConstraint;
13648}(QueryConstraint));
13649/**
13650 * Creates a `QueryConstraint` with the specified starting point.
13651 *
13652 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13653 * allows you to choose arbitrary starting and ending points for your queries.
13654 *
13655 * The starting point is inclusive, so children with exactly the specified value
13656 * will be included in the query. The optional key argument can be used to
13657 * further limit the range of the query. If it is specified, then children that
13658 * have exactly the specified value must also have a key name greater than or
13659 * equal to the specified key.
13660 *
13661 * You can read more about `startAt()` in
13662 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13663 *
13664 * @param value - The value to start at. The argument type depends on which
13665 * `orderBy*()` function was used in this query. Specify a value that matches
13666 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13667 * value must be a string.
13668 * @param key - The child key to start at. This argument is only allowed if
13669 * ordering by child, value, or priority.
13670 */
13671function startAt(value, key) {
13672 if (value === void 0) { value = null; }
13673 validateKey('startAt', 'key', key, true);
13674 return new QueryStartAtConstraint(value, key);
13675}
13676var QueryStartAfterConstraint = /** @class */ (function (_super) {
13677 tslib.__extends(QueryStartAfterConstraint, _super);
13678 function QueryStartAfterConstraint(_value, _key) {
13679 var _this = _super.call(this) || this;
13680 _this._value = _value;
13681 _this._key = _key;
13682 return _this;
13683 }
13684 QueryStartAfterConstraint.prototype._apply = function (query) {
13685 validateFirebaseDataArg('startAfter', this._value, query._path, false);
13686 var newParams = queryParamsStartAfter(query._queryParams, this._value, this._key);
13687 validateLimit(newParams);
13688 validateQueryEndpoints(newParams);
13689 if (query._queryParams.hasStart()) {
13690 throw new Error('startAfter: Starting point was already set (by another call to startAt, ' +
13691 'startAfter, or equalTo).');
13692 }
13693 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13694 };
13695 return QueryStartAfterConstraint;
13696}(QueryConstraint));
13697/**
13698 * Creates a `QueryConstraint` with the specified starting point (exclusive).
13699 *
13700 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13701 * allows you to choose arbitrary starting and ending points for your queries.
13702 *
13703 * The starting point is exclusive. If only a value is provided, children
13704 * with a value greater than the specified value will be included in the query.
13705 * If a key is specified, then children must have a value greater than or equal
13706 * to the specified value and a a key name greater than the specified key.
13707 *
13708 * @param value - The value to start after. The argument type depends on which
13709 * `orderBy*()` function was used in this query. Specify a value that matches
13710 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13711 * value must be a string.
13712 * @param key - The child key to start after. This argument is only allowed if
13713 * ordering by child, value, or priority.
13714 */
13715function startAfter(value, key) {
13716 validateKey('startAfter', 'key', key, true);
13717 return new QueryStartAfterConstraint(value, key);
13718}
13719var QueryLimitToFirstConstraint = /** @class */ (function (_super) {
13720 tslib.__extends(QueryLimitToFirstConstraint, _super);
13721 function QueryLimitToFirstConstraint(_limit) {
13722 var _this = _super.call(this) || this;
13723 _this._limit = _limit;
13724 return _this;
13725 }
13726 QueryLimitToFirstConstraint.prototype._apply = function (query) {
13727 if (query._queryParams.hasLimit()) {
13728 throw new Error('limitToFirst: Limit was already set (by another call to limitToFirst ' +
13729 'or limitToLast).');
13730 }
13731 return new QueryImpl(query._repo, query._path, queryParamsLimitToFirst(query._queryParams, this._limit), query._orderByCalled);
13732 };
13733 return QueryLimitToFirstConstraint;
13734}(QueryConstraint));
13735/**
13736 * Creates a new `QueryConstraint` that if limited to the first specific number
13737 * of children.
13738 *
13739 * The `limitToFirst()` method is used to set a maximum number of children to be
13740 * synced for a given callback. If we set a limit of 100, we will initially only
13741 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13742 * stored in our Database, a `child_added` event will fire for each message.
13743 * However, if we have over 100 messages, we will only receive a `child_added`
13744 * event for the first 100 ordered messages. As items change, we will receive
13745 * `child_removed` events for each item that drops out of the active list so
13746 * that the total number stays at 100.
13747 *
13748 * You can read more about `limitToFirst()` in
13749 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13750 *
13751 * @param limit - The maximum number of nodes to include in this query.
13752 */
13753function limitToFirst(limit) {
13754 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13755 throw new Error('limitToFirst: First argument must be a positive integer.');
13756 }
13757 return new QueryLimitToFirstConstraint(limit);
13758}
13759var QueryLimitToLastConstraint = /** @class */ (function (_super) {
13760 tslib.__extends(QueryLimitToLastConstraint, _super);
13761 function QueryLimitToLastConstraint(_limit) {
13762 var _this = _super.call(this) || this;
13763 _this._limit = _limit;
13764 return _this;
13765 }
13766 QueryLimitToLastConstraint.prototype._apply = function (query) {
13767 if (query._queryParams.hasLimit()) {
13768 throw new Error('limitToLast: Limit was already set (by another call to limitToFirst ' +
13769 'or limitToLast).');
13770 }
13771 return new QueryImpl(query._repo, query._path, queryParamsLimitToLast(query._queryParams, this._limit), query._orderByCalled);
13772 };
13773 return QueryLimitToLastConstraint;
13774}(QueryConstraint));
13775/**
13776 * Creates a new `QueryConstraint` that is limited to return only the last
13777 * specified number of children.
13778 *
13779 * The `limitToLast()` method is used to set a maximum number of children to be
13780 * synced for a given callback. If we set a limit of 100, we will initially only
13781 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13782 * stored in our Database, a `child_added` event will fire for each message.
13783 * However, if we have over 100 messages, we will only receive a `child_added`
13784 * event for the last 100 ordered messages. As items change, we will receive
13785 * `child_removed` events for each item that drops out of the active list so
13786 * that the total number stays at 100.
13787 *
13788 * You can read more about `limitToLast()` in
13789 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13790 *
13791 * @param limit - The maximum number of nodes to include in this query.
13792 */
13793function limitToLast(limit) {
13794 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13795 throw new Error('limitToLast: First argument must be a positive integer.');
13796 }
13797 return new QueryLimitToLastConstraint(limit);
13798}
13799var QueryOrderByChildConstraint = /** @class */ (function (_super) {
13800 tslib.__extends(QueryOrderByChildConstraint, _super);
13801 function QueryOrderByChildConstraint(_path) {
13802 var _this = _super.call(this) || this;
13803 _this._path = _path;
13804 return _this;
13805 }
13806 QueryOrderByChildConstraint.prototype._apply = function (query) {
13807 validateNoPreviousOrderByCall(query, 'orderByChild');
13808 var parsedPath = new Path(this._path);
13809 if (pathIsEmpty(parsedPath)) {
13810 throw new Error('orderByChild: cannot pass in empty path. Use orderByValue() instead.');
13811 }
13812 var index = new PathIndex(parsedPath);
13813 var newParams = queryParamsOrderBy(query._queryParams, index);
13814 validateQueryEndpoints(newParams);
13815 return new QueryImpl(query._repo, query._path, newParams,
13816 /*orderByCalled=*/ true);
13817 };
13818 return QueryOrderByChildConstraint;
13819}(QueryConstraint));
13820/**
13821 * Creates a new `QueryConstraint` that orders by the specified child key.
13822 *
13823 * Queries can only order by one key at a time. Calling `orderByChild()`
13824 * multiple times on the same query is an error.
13825 *
13826 * Firebase queries allow you to order your data by any child key on the fly.
13827 * However, if you know in advance what your indexes will be, you can define
13828 * them via the .indexOn rule in your Security Rules for better performance. See
13829 * the{@link https://firebase.google.com/docs/database/security/indexing-data}
13830 * rule for more information.
13831 *
13832 * You can read more about `orderByChild()` in
13833 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13834 *
13835 * @param path - The path to order by.
13836 */
13837function orderByChild(path) {
13838 if (path === '$key') {
13839 throw new Error('orderByChild: "$key" is invalid. Use orderByKey() instead.');
13840 }
13841 else if (path === '$priority') {
13842 throw new Error('orderByChild: "$priority" is invalid. Use orderByPriority() instead.');
13843 }
13844 else if (path === '$value') {
13845 throw new Error('orderByChild: "$value" is invalid. Use orderByValue() instead.');
13846 }
13847 validatePathString('orderByChild', 'path', path, false);
13848 return new QueryOrderByChildConstraint(path);
13849}
13850var QueryOrderByKeyConstraint = /** @class */ (function (_super) {
13851 tslib.__extends(QueryOrderByKeyConstraint, _super);
13852 function QueryOrderByKeyConstraint() {
13853 return _super !== null && _super.apply(this, arguments) || this;
13854 }
13855 QueryOrderByKeyConstraint.prototype._apply = function (query) {
13856 validateNoPreviousOrderByCall(query, 'orderByKey');
13857 var newParams = queryParamsOrderBy(query._queryParams, KEY_INDEX);
13858 validateQueryEndpoints(newParams);
13859 return new QueryImpl(query._repo, query._path, newParams,
13860 /*orderByCalled=*/ true);
13861 };
13862 return QueryOrderByKeyConstraint;
13863}(QueryConstraint));
13864/**
13865 * Creates a new `QueryConstraint` that orders by the key.
13866 *
13867 * Sorts the results of a query by their (ascending) key values.
13868 *
13869 * You can read more about `orderByKey()` in
13870 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13871 */
13872function orderByKey() {
13873 return new QueryOrderByKeyConstraint();
13874}
13875var QueryOrderByPriorityConstraint = /** @class */ (function (_super) {
13876 tslib.__extends(QueryOrderByPriorityConstraint, _super);
13877 function QueryOrderByPriorityConstraint() {
13878 return _super !== null && _super.apply(this, arguments) || this;
13879 }
13880 QueryOrderByPriorityConstraint.prototype._apply = function (query) {
13881 validateNoPreviousOrderByCall(query, 'orderByPriority');
13882 var newParams = queryParamsOrderBy(query._queryParams, PRIORITY_INDEX);
13883 validateQueryEndpoints(newParams);
13884 return new QueryImpl(query._repo, query._path, newParams,
13885 /*orderByCalled=*/ true);
13886 };
13887 return QueryOrderByPriorityConstraint;
13888}(QueryConstraint));
13889/**
13890 * Creates a new `QueryConstraint` that orders by priority.
13891 *
13892 * Applications need not use priority but can order collections by
13893 * ordinary properties (see
13894 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}
13895 * for alternatives to priority.
13896 */
13897function orderByPriority() {
13898 return new QueryOrderByPriorityConstraint();
13899}
13900var QueryOrderByValueConstraint = /** @class */ (function (_super) {
13901 tslib.__extends(QueryOrderByValueConstraint, _super);
13902 function QueryOrderByValueConstraint() {
13903 return _super !== null && _super.apply(this, arguments) || this;
13904 }
13905 QueryOrderByValueConstraint.prototype._apply = function (query) {
13906 validateNoPreviousOrderByCall(query, 'orderByValue');
13907 var newParams = queryParamsOrderBy(query._queryParams, VALUE_INDEX);
13908 validateQueryEndpoints(newParams);
13909 return new QueryImpl(query._repo, query._path, newParams,
13910 /*orderByCalled=*/ true);
13911 };
13912 return QueryOrderByValueConstraint;
13913}(QueryConstraint));
13914/**
13915 * Creates a new `QueryConstraint` that orders by value.
13916 *
13917 * If the children of a query are all scalar values (string, number, or
13918 * boolean), you can order the results by their (ascending) values.
13919 *
13920 * You can read more about `orderByValue()` in
13921 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13922 */
13923function orderByValue() {
13924 return new QueryOrderByValueConstraint();
13925}
13926var QueryEqualToValueConstraint = /** @class */ (function (_super) {
13927 tslib.__extends(QueryEqualToValueConstraint, _super);
13928 function QueryEqualToValueConstraint(_value, _key) {
13929 var _this = _super.call(this) || this;
13930 _this._value = _value;
13931 _this._key = _key;
13932 return _this;
13933 }
13934 QueryEqualToValueConstraint.prototype._apply = function (query) {
13935 validateFirebaseDataArg('equalTo', this._value, query._path, false);
13936 if (query._queryParams.hasStart()) {
13937 throw new Error('equalTo: Starting point was already set (by another call to startAt/startAfter or ' +
13938 'equalTo).');
13939 }
13940 if (query._queryParams.hasEnd()) {
13941 throw new Error('equalTo: Ending point was already set (by another call to endAt/endBefore or ' +
13942 'equalTo).');
13943 }
13944 return new QueryEndAtConstraint(this._value, this._key)._apply(new QueryStartAtConstraint(this._value, this._key)._apply(query));
13945 };
13946 return QueryEqualToValueConstraint;
13947}(QueryConstraint));
13948/**
13949 * Creates a `QueryConstraint` that includes children that match the specified
13950 * value.
13951 *
13952 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13953 * allows you to choose arbitrary starting and ending points for your queries.
13954 *
13955 * The optional key argument can be used to further limit the range of the
13956 * query. If it is specified, then children that have exactly the specified
13957 * value must also have exactly the specified key as their key name. This can be
13958 * used to filter result sets with many matches for the same value.
13959 *
13960 * You can read more about `equalTo()` in
13961 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13962 *
13963 * @param value - The value to match for. The argument type depends on which
13964 * `orderBy*()` function was used in this query. Specify a value that matches
13965 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13966 * value must be a string.
13967 * @param key - The child key to start at, among the children with the
13968 * previously specified priority. This argument is only allowed if ordering by
13969 * child, value, or priority.
13970 */
13971function equalTo(value, key) {
13972 validateKey('equalTo', 'key', key, true);
13973 return new QueryEqualToValueConstraint(value, key);
13974}
13975/**
13976 * Creates a new immutable instance of `Query` that is extended to also include
13977 * additional query constraints.
13978 *
13979 * @param query - The Query instance to use as a base for the new constraints.
13980 * @param queryConstraints - The list of `QueryConstraint`s to apply.
13981 * @throws if any of the provided query constraints cannot be combined with the
13982 * existing or new constraints.
13983 */
13984function query(query) {
13985 var e_1, _a;
13986 var queryConstraints = [];
13987 for (var _i = 1; _i < arguments.length; _i++) {
13988 queryConstraints[_i - 1] = arguments[_i];
13989 }
13990 var queryImpl = util.getModularInstance(query);
13991 try {
13992 for (var queryConstraints_1 = tslib.__values(queryConstraints), queryConstraints_1_1 = queryConstraints_1.next(); !queryConstraints_1_1.done; queryConstraints_1_1 = queryConstraints_1.next()) {
13993 var constraint = queryConstraints_1_1.value;
13994 queryImpl = constraint._apply(queryImpl);
13995 }
13996 }
13997 catch (e_1_1) { e_1 = { error: e_1_1 }; }
13998 finally {
13999 try {
14000 if (queryConstraints_1_1 && !queryConstraints_1_1.done && (_a = queryConstraints_1.return)) _a.call(queryConstraints_1);
14001 }
14002 finally { if (e_1) throw e_1.error; }
14003 }
14004 return queryImpl;
14005}
14006/**
14007 * Define reference constructor in various modules
14008 *
14009 * We are doing this here to avoid several circular
14010 * dependency issues
14011 */
14012syncPointSetReferenceConstructor(ReferenceImpl);
14013syncTreeSetReferenceConstructor(ReferenceImpl);
14014
14015/**
14016 * @license
14017 * Copyright 2020 Google LLC
14018 *
14019 * Licensed under the Apache License, Version 2.0 (the "License");
14020 * you may not use this file except in compliance with the License.
14021 * You may obtain a copy of the License at
14022 *
14023 * http://www.apache.org/licenses/LICENSE-2.0
14024 *
14025 * Unless required by applicable law or agreed to in writing, software
14026 * distributed under the License is distributed on an "AS IS" BASIS,
14027 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14028 * See the License for the specific language governing permissions and
14029 * limitations under the License.
14030 */
14031/**
14032 * This variable is also defined in the firebase Node.js Admin SDK. Before
14033 * modifying this definition, consult the definition in:
14034 *
14035 * https://github.com/firebase/firebase-admin-node
14036 *
14037 * and make sure the two are consistent.
14038 */
14039var FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST';
14040/**
14041 * Creates and caches `Repo` instances.
14042 */
14043var repos = {};
14044/**
14045 * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes).
14046 */
14047var useRestClient = false;
14048/**
14049 * Update an existing `Repo` in place to point to a new host/port.
14050 */
14051function repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider) {
14052 repo.repoInfo_ = new RepoInfo(host + ":" + port,
14053 /* secure= */ false, repo.repoInfo_.namespace, repo.repoInfo_.webSocketOnly, repo.repoInfo_.nodeAdmin, repo.repoInfo_.persistenceKey, repo.repoInfo_.includeNamespaceInQueryParams);
14054 if (tokenProvider) {
14055 repo.authTokenProvider_ = tokenProvider;
14056 }
14057}
14058/**
14059 * This function should only ever be called to CREATE a new database instance.
14060 * @internal
14061 */
14062function repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url, nodeAdmin) {
14063 var dbUrl = url || app.options.databaseURL;
14064 if (dbUrl === undefined) {
14065 if (!app.options.projectId) {
14066 fatal("Can't determine Firebase Database URL. Be sure to include " +
14067 ' a Project ID when calling firebase.initializeApp().');
14068 }
14069 log('Using default host for project ', app.options.projectId);
14070 dbUrl = app.options.projectId + "-default-rtdb.firebaseio.com";
14071 }
14072 var parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14073 var repoInfo = parsedUrl.repoInfo;
14074 var isEmulator;
14075 var dbEmulatorHost = undefined;
14076 if (typeof process !== 'undefined' && process.env) {
14077 dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR];
14078 }
14079 if (dbEmulatorHost) {
14080 isEmulator = true;
14081 dbUrl = "http://" + dbEmulatorHost + "?ns=" + repoInfo.namespace;
14082 parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14083 repoInfo = parsedUrl.repoInfo;
14084 }
14085 else {
14086 isEmulator = !parsedUrl.repoInfo.secure;
14087 }
14088 var authTokenProvider = nodeAdmin && isEmulator
14089 ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER)
14090 : new FirebaseAuthTokenProvider(app.name, app.options, authProvider);
14091 validateUrl('Invalid Firebase Database URL', parsedUrl);
14092 if (!pathIsEmpty(parsedUrl.path)) {
14093 fatal('Database URL must point to the root of a Firebase Database ' +
14094 '(not including a child path).');
14095 }
14096 var repo = repoManagerCreateRepo(repoInfo, app, authTokenProvider, new AppCheckTokenProvider(app.name, appCheckProvider));
14097 return new Database(repo, app);
14098}
14099/**
14100 * Remove the repo and make sure it is disconnected.
14101 *
14102 */
14103function repoManagerDeleteRepo(repo, appName) {
14104 var appRepos = repos[appName];
14105 // This should never happen...
14106 if (!appRepos || appRepos[repo.key] !== repo) {
14107 fatal("Database " + appName + "(" + repo.repoInfo_ + ") has already been deleted.");
14108 }
14109 repoInterrupt(repo);
14110 delete appRepos[repo.key];
14111}
14112/**
14113 * Ensures a repo doesn't already exist and then creates one using the
14114 * provided app.
14115 *
14116 * @param repoInfo - The metadata about the Repo
14117 * @returns The Repo object for the specified server / repoName.
14118 */
14119function repoManagerCreateRepo(repoInfo, app, authTokenProvider, appCheckProvider) {
14120 var appRepos = repos[app.name];
14121 if (!appRepos) {
14122 appRepos = {};
14123 repos[app.name] = appRepos;
14124 }
14125 var repo = appRepos[repoInfo.toURLString()];
14126 if (repo) {
14127 fatal('Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.');
14128 }
14129 repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider);
14130 appRepos[repoInfo.toURLString()] = repo;
14131 return repo;
14132}
14133/**
14134 * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos.
14135 */
14136function repoManagerForceRestClient(forceRestClient) {
14137 useRestClient = forceRestClient;
14138}
14139/**
14140 * Class representing a Firebase Realtime Database.
14141 */
14142var Database = /** @class */ (function () {
14143 /** @hideconstructor */
14144 function Database(_repoInternal,
14145 /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */
14146 app) {
14147 this._repoInternal = _repoInternal;
14148 this.app = app;
14149 /** Represents a `Database` instance. */
14150 this['type'] = 'database';
14151 /** Track if the instance has been used (root or repo accessed) */
14152 this._instanceStarted = false;
14153 }
14154 Object.defineProperty(Database.prototype, "_repo", {
14155 get: function () {
14156 if (!this._instanceStarted) {
14157 repoStart(this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride']);
14158 this._instanceStarted = true;
14159 }
14160 return this._repoInternal;
14161 },
14162 enumerable: false,
14163 configurable: true
14164 });
14165 Object.defineProperty(Database.prototype, "_root", {
14166 get: function () {
14167 if (!this._rootInternal) {
14168 this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath());
14169 }
14170 return this._rootInternal;
14171 },
14172 enumerable: false,
14173 configurable: true
14174 });
14175 Database.prototype._delete = function () {
14176 if (this._rootInternal !== null) {
14177 repoManagerDeleteRepo(this._repo, this.app.name);
14178 this._repoInternal = null;
14179 this._rootInternal = null;
14180 }
14181 return Promise.resolve();
14182 };
14183 Database.prototype._checkNotDeleted = function (apiName) {
14184 if (this._rootInternal === null) {
14185 fatal('Cannot call ' + apiName + ' on a deleted database.');
14186 }
14187 };
14188 return Database;
14189}());
14190function checkTransportInit() {
14191 if (TransportManager.IS_TRANSPORT_INITIALIZED) {
14192 warn('Transport has already been initialized. Please call this function before calling ref or setting up a listener');
14193 }
14194}
14195/**
14196 * Force the use of websockets instead of longPolling.
14197 */
14198function forceWebSockets() {
14199 checkTransportInit();
14200 BrowserPollConnection.forceDisallow();
14201}
14202/**
14203 * Force the use of longPolling instead of websockets. This will be ignored if websocket protocol is used in databaseURL.
14204 */
14205function forceLongPolling() {
14206 checkTransportInit();
14207 WebSocketConnection.forceDisallow();
14208 BrowserPollConnection.forceAllow();
14209}
14210/**
14211 * Modify the provided instance to communicate with the Realtime Database
14212 * emulator.
14213 *
14214 * <p>Note: This method must be called before performing any other operation.
14215 *
14216 * @param db - The instance to modify.
14217 * @param host - The emulator host (ex: localhost)
14218 * @param port - The emulator port (ex: 8080)
14219 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
14220 */
14221function connectDatabaseEmulator(db, host, port, options) {
14222 if (options === void 0) { options = {}; }
14223 db = util.getModularInstance(db);
14224 db._checkNotDeleted('useEmulator');
14225 if (db._instanceStarted) {
14226 fatal('Cannot call useEmulator() after instance has already been initialized.');
14227 }
14228 var repo = db._repoInternal;
14229 var tokenProvider = undefined;
14230 if (repo.repoInfo_.nodeAdmin) {
14231 if (options.mockUserToken) {
14232 fatal('mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".');
14233 }
14234 tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
14235 }
14236 else if (options.mockUserToken) {
14237 var token = typeof options.mockUserToken === 'string'
14238 ? options.mockUserToken
14239 : util.createMockUserToken(options.mockUserToken, db.app.options.projectId);
14240 tokenProvider = new EmulatorTokenProvider(token);
14241 }
14242 // Modify the repo to apply emulator settings
14243 repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider);
14244}
14245/**
14246 * Disconnects from the server (all Database operations will be completed
14247 * offline).
14248 *
14249 * The client automatically maintains a persistent connection to the Database
14250 * server, which will remain active indefinitely and reconnect when
14251 * disconnected. However, the `goOffline()` and `goOnline()` methods may be used
14252 * to control the client connection in cases where a persistent connection is
14253 * undesirable.
14254 *
14255 * While offline, the client will no longer receive data updates from the
14256 * Database. However, all Database operations performed locally will continue to
14257 * immediately fire events, allowing your application to continue behaving
14258 * normally. Additionally, each operation performed locally will automatically
14259 * be queued and retried upon reconnection to the Database server.
14260 *
14261 * To reconnect to the Database and begin receiving remote events, see
14262 * `goOnline()`.
14263 *
14264 * @param db - The instance to disconnect.
14265 */
14266function goOffline(db) {
14267 db = util.getModularInstance(db);
14268 db._checkNotDeleted('goOffline');
14269 repoInterrupt(db._repo);
14270}
14271/**
14272 * Reconnects to the server and synchronizes the offline Database state
14273 * with the server state.
14274 *
14275 * This method should be used after disabling the active connection with
14276 * `goOffline()`. Once reconnected, the client will transmit the proper data
14277 * and fire the appropriate events so that your client "catches up"
14278 * automatically.
14279 *
14280 * @param db - The instance to reconnect.
14281 */
14282function goOnline(db) {
14283 db = util.getModularInstance(db);
14284 db._checkNotDeleted('goOnline');
14285 repoResume(db._repo);
14286}
14287function enableLogging(logger, persistent) {
14288 enableLogging$1(logger, persistent);
14289}
14290
14291/**
14292 * @license
14293 * Copyright 2020 Google LLC
14294 *
14295 * Licensed under the Apache License, Version 2.0 (the "License");
14296 * you may not use this file except in compliance with the License.
14297 * You may obtain a copy of the License at
14298 *
14299 * http://www.apache.org/licenses/LICENSE-2.0
14300 *
14301 * Unless required by applicable law or agreed to in writing, software
14302 * distributed under the License is distributed on an "AS IS" BASIS,
14303 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14304 * See the License for the specific language governing permissions and
14305 * limitations under the License.
14306 */
14307var SERVER_TIMESTAMP = {
14308 '.sv': 'timestamp'
14309};
14310/**
14311 * Returns a placeholder value for auto-populating the current timestamp (time
14312 * since the Unix epoch, in milliseconds) as determined by the Firebase
14313 * servers.
14314 */
14315function serverTimestamp() {
14316 return SERVER_TIMESTAMP;
14317}
14318/**
14319 * Returns a placeholder value that can be used to atomically increment the
14320 * current database value by the provided delta.
14321 *
14322 * @param delta - the amount to modify the current value atomically.
14323 * @returns A placeholder value for modifying data atomically server-side.
14324 */
14325function increment(delta) {
14326 return {
14327 '.sv': {
14328 'increment': delta
14329 }
14330 };
14331}
14332
14333/**
14334 * @license
14335 * Copyright 2020 Google LLC
14336 *
14337 * Licensed under the Apache License, Version 2.0 (the "License");
14338 * you may not use this file except in compliance with the License.
14339 * You may obtain a copy of the License at
14340 *
14341 * http://www.apache.org/licenses/LICENSE-2.0
14342 *
14343 * Unless required by applicable law or agreed to in writing, software
14344 * distributed under the License is distributed on an "AS IS" BASIS,
14345 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14346 * See the License for the specific language governing permissions and
14347 * limitations under the License.
14348 */
14349/**
14350 * A type for the resolve value of {@link runTransaction}.
14351 */
14352var TransactionResult = /** @class */ (function () {
14353 /** @hideconstructor */
14354 function TransactionResult(
14355 /** Whether the transaction was successfully committed. */
14356 committed,
14357 /** The resulting data snapshot. */
14358 snapshot) {
14359 this.committed = committed;
14360 this.snapshot = snapshot;
14361 }
14362 /** Returns a JSON-serializable representation of this object. */
14363 TransactionResult.prototype.toJSON = function () {
14364 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14365 };
14366 return TransactionResult;
14367}());
14368/**
14369 * Atomically modifies the data at this location.
14370 *
14371 * Atomically modify the data at this location. Unlike a normal `set()`, which
14372 * just overwrites the data regardless of its previous value, `runTransaction()` is
14373 * used to modify the existing value to a new value, ensuring there are no
14374 * conflicts with other clients writing to the same location at the same time.
14375 *
14376 * To accomplish this, you pass `runTransaction()` an update function which is
14377 * used to transform the current value into a new value. If another client
14378 * writes to the location before your new value is successfully written, your
14379 * update function will be called again with the new current value, and the
14380 * write will be retried. This will happen repeatedly until your write succeeds
14381 * without conflict or you abort the transaction by not returning a value from
14382 * your update function.
14383 *
14384 * Note: Modifying data with `set()` will cancel any pending transactions at
14385 * that location, so extreme care should be taken if mixing `set()` and
14386 * `runTransaction()` to update the same data.
14387 *
14388 * Note: When using transactions with Security and Firebase Rules in place, be
14389 * aware that a client needs `.read` access in addition to `.write` access in
14390 * order to perform a transaction. This is because the client-side nature of
14391 * transactions requires the client to read the data in order to transactionally
14392 * update it.
14393 *
14394 * @param ref - The location to atomically modify.
14395 * @param transactionUpdate - A developer-supplied function which will be passed
14396 * the current data stored at this location (as a JavaScript object). The
14397 * function should return the new value it would like written (as a JavaScript
14398 * object). If `undefined` is returned (i.e. you return with no arguments) the
14399 * transaction will be aborted and the data at this location will not be
14400 * modified.
14401 * @param options - An options object to configure transactions.
14402 * @returns A `Promise` that can optionally be used instead of the `onComplete`
14403 * callback to handle success and failure.
14404 */
14405function runTransaction(ref,
14406// eslint-disable-next-line @typescript-eslint/no-explicit-any
14407transactionUpdate, options) {
14408 var _a;
14409 ref = util.getModularInstance(ref);
14410 validateWritablePath('Reference.transaction', ref._path);
14411 if (ref.key === '.length' || ref.key === '.keys') {
14412 throw ('Reference.transaction failed: ' + ref.key + ' is a read-only object.');
14413 }
14414 var applyLocally = (_a = options === null || options === void 0 ? void 0 : options.applyLocally) !== null && _a !== void 0 ? _a : true;
14415 var deferred = new util.Deferred();
14416 var promiseComplete = function (error, committed, node) {
14417 var dataSnapshot = null;
14418 if (error) {
14419 deferred.reject(error);
14420 }
14421 else {
14422 dataSnapshot = new DataSnapshot(node, new ReferenceImpl(ref._repo, ref._path), PRIORITY_INDEX);
14423 deferred.resolve(new TransactionResult(committed, dataSnapshot));
14424 }
14425 };
14426 // Add a watch to make sure we get server updates.
14427 var unwatcher = onValue(ref, function () { });
14428 repoStartTransaction(ref._repo, ref._path, transactionUpdate, promiseComplete, unwatcher, applyLocally);
14429 return deferred.promise;
14430}
14431
14432/**
14433 * @license
14434 * Copyright 2017 Google LLC
14435 *
14436 * Licensed under the Apache License, Version 2.0 (the "License");
14437 * you may not use this file except in compliance with the License.
14438 * You may obtain a copy of the License at
14439 *
14440 * http://www.apache.org/licenses/LICENSE-2.0
14441 *
14442 * Unless required by applicable law or agreed to in writing, software
14443 * distributed under the License is distributed on an "AS IS" BASIS,
14444 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14445 * See the License for the specific language governing permissions and
14446 * limitations under the License.
14447 */
14448// eslint-disable-next-line @typescript-eslint/no-explicit-any
14449PersistentConnection.prototype.simpleListen = function (pathString, onComplete) {
14450 this.sendRequest('q', { p: pathString }, onComplete);
14451};
14452// eslint-disable-next-line @typescript-eslint/no-explicit-any
14453PersistentConnection.prototype.echo = function (data, onEcho) {
14454 this.sendRequest('echo', { d: data }, onEcho);
14455};
14456/**
14457 * @internal
14458 */
14459var hijackHash = function (newHash) {
14460 var oldPut = PersistentConnection.prototype.put;
14461 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
14462 if (hash !== undefined) {
14463 hash = newHash();
14464 }
14465 oldPut.call(this, pathString, data, onComplete, hash);
14466 };
14467 return function () {
14468 PersistentConnection.prototype.put = oldPut;
14469 };
14470};
14471/**
14472 * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection.
14473 * @internal
14474 */
14475var forceRestClient = function (forceRestClient) {
14476 repoManagerForceRestClient(forceRestClient);
14477};
14478
14479/**
14480 * @license
14481 * Copyright 2021 Google LLC
14482 *
14483 * Licensed under the Apache License, Version 2.0 (the "License");
14484 * you may not use this file except in compliance with the License.
14485 * You may obtain a copy of the License at
14486 *
14487 * http://www.apache.org/licenses/LICENSE-2.0
14488 *
14489 * Unless required by applicable law or agreed to in writing, software
14490 * distributed under the License is distributed on an "AS IS" BASIS,
14491 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14492 * See the License for the specific language governing permissions and
14493 * limitations under the License.
14494 */
14495setWebSocketImpl(Websocket__default["default"].Client);
14496
14497exports.DataSnapshot = DataSnapshot;
14498exports.Database = Database;
14499exports.OnDisconnect = OnDisconnect;
14500exports.QueryConstraint = QueryConstraint;
14501exports.TransactionResult = TransactionResult;
14502exports._QueryImpl = QueryImpl;
14503exports._QueryParams = QueryParams;
14504exports._ReferenceImpl = ReferenceImpl;
14505exports._TEST_ACCESS_forceRestClient = forceRestClient;
14506exports._TEST_ACCESS_hijackHash = hijackHash;
14507exports._repoManagerDatabaseFromApp = repoManagerDatabaseFromApp;
14508exports._setSDKVersion = setSDKVersion;
14509exports._validatePathString = validatePathString;
14510exports._validateWritablePath = validateWritablePath;
14511exports.child = child;
14512exports.connectDatabaseEmulator = connectDatabaseEmulator;
14513exports.enableLogging = enableLogging;
14514exports.endAt = endAt;
14515exports.endBefore = endBefore;
14516exports.equalTo = equalTo;
14517exports.forceLongPolling = forceLongPolling;
14518exports.forceWebSockets = forceWebSockets;
14519exports.get = get;
14520exports.goOffline = goOffline;
14521exports.goOnline = goOnline;
14522exports.increment = increment;
14523exports.limitToFirst = limitToFirst;
14524exports.limitToLast = limitToLast;
14525exports.off = off;
14526exports.onChildAdded = onChildAdded;
14527exports.onChildChanged = onChildChanged;
14528exports.onChildMoved = onChildMoved;
14529exports.onChildRemoved = onChildRemoved;
14530exports.onDisconnect = onDisconnect;
14531exports.onValue = onValue;
14532exports.orderByChild = orderByChild;
14533exports.orderByKey = orderByKey;
14534exports.orderByPriority = orderByPriority;
14535exports.orderByValue = orderByValue;
14536exports.push = push;
14537exports.query = query;
14538exports.ref = ref;
14539exports.refFromURL = refFromURL;
14540exports.remove = remove;
14541exports.runTransaction = runTransaction;
14542exports.serverTimestamp = serverTimestamp;
14543exports.set = set;
14544exports.setPriority = setPriority;
14545exports.setWithPriority = setWithPriority;
14546exports.startAfter = startAfter;
14547exports.startAt = startAt;
14548exports.update = update;
14549//# sourceMappingURL=index.standalone.js.map