UNPKG

639 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var component = require('@firebase/component');
6var util = require('@firebase/util');
7var fayeWebsocket = require('faye-websocket');
8var tslib = require('tslib');
9var logger$1 = require('@firebase/logger');
10
11var name = "@firebase/database";
12var version = "0.10.3";
13
14/**
15 * @license
16 * Copyright 2019 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 */
30/** The semver (www.semver.org) version of the SDK. */
31var SDK_VERSION = '';
32// SDK_VERSION should be set before any database instance is created
33function setSDKVersion(version) {
34 SDK_VERSION = version;
35}
36
37/**
38 * @license
39 * Copyright 2017 Google LLC
40 *
41 * Licensed under the Apache License, Version 2.0 (the "License");
42 * you may not use this file except in compliance with the License.
43 * You may obtain a copy of the License at
44 *
45 * http://www.apache.org/licenses/LICENSE-2.0
46 *
47 * Unless required by applicable law or agreed to in writing, software
48 * distributed under the License is distributed on an "AS IS" BASIS,
49 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
50 * See the License for the specific language governing permissions and
51 * limitations under the License.
52 */
53/**
54 * Wraps a DOM Storage object and:
55 * - automatically encode objects as JSON strings before storing them to allow us to store arbitrary types.
56 * - prefixes names with "firebase:" to avoid collisions with app data.
57 *
58 * We automatically (see storage.js) create two such wrappers, one for sessionStorage,
59 * and one for localStorage.
60 *
61 */
62var DOMStorageWrapper = /** @class */ (function () {
63 /**
64 * @param domStorage_ - The underlying storage object (e.g. localStorage or sessionStorage)
65 */
66 function DOMStorageWrapper(domStorage_) {
67 this.domStorage_ = domStorage_;
68 // Use a prefix to avoid collisions with other stuff saved by the app.
69 this.prefix_ = 'firebase:';
70 }
71 /**
72 * @param key - The key to save the value under
73 * @param value - The value being stored, or null to remove the key.
74 */
75 DOMStorageWrapper.prototype.set = function (key, value) {
76 if (value == null) {
77 this.domStorage_.removeItem(this.prefixedName_(key));
78 }
79 else {
80 this.domStorage_.setItem(this.prefixedName_(key), util.stringify(value));
81 }
82 };
83 /**
84 * @returns The value that was stored under this key, or null
85 */
86 DOMStorageWrapper.prototype.get = function (key) {
87 var storedVal = this.domStorage_.getItem(this.prefixedName_(key));
88 if (storedVal == null) {
89 return null;
90 }
91 else {
92 return util.jsonEval(storedVal);
93 }
94 };
95 DOMStorageWrapper.prototype.remove = function (key) {
96 this.domStorage_.removeItem(this.prefixedName_(key));
97 };
98 DOMStorageWrapper.prototype.prefixedName_ = function (name) {
99 return this.prefix_ + name;
100 };
101 DOMStorageWrapper.prototype.toString = function () {
102 return this.domStorage_.toString();
103 };
104 return DOMStorageWrapper;
105}());
106
107/**
108 * @license
109 * Copyright 2017 Google LLC
110 *
111 * Licensed under the Apache License, Version 2.0 (the "License");
112 * you may not use this file except in compliance with the License.
113 * You may obtain a copy of the License at
114 *
115 * http://www.apache.org/licenses/LICENSE-2.0
116 *
117 * Unless required by applicable law or agreed to in writing, software
118 * distributed under the License is distributed on an "AS IS" BASIS,
119 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
120 * See the License for the specific language governing permissions and
121 * limitations under the License.
122 */
123/**
124 * An in-memory storage implementation that matches the API of DOMStorageWrapper
125 * (TODO: create interface for both to implement).
126 */
127var MemoryStorage = /** @class */ (function () {
128 function MemoryStorage() {
129 this.cache_ = {};
130 this.isInMemoryStorage = true;
131 }
132 MemoryStorage.prototype.set = function (key, value) {
133 if (value == null) {
134 delete this.cache_[key];
135 }
136 else {
137 this.cache_[key] = value;
138 }
139 };
140 MemoryStorage.prototype.get = function (key) {
141 if (util.contains(this.cache_, key)) {
142 return this.cache_[key];
143 }
144 return null;
145 };
146 MemoryStorage.prototype.remove = function (key) {
147 delete this.cache_[key];
148 };
149 return MemoryStorage;
150}());
151
152/**
153 * @license
154 * Copyright 2017 Google LLC
155 *
156 * Licensed under the Apache License, Version 2.0 (the "License");
157 * you may not use this file except in compliance with the License.
158 * You may obtain a copy of the License at
159 *
160 * http://www.apache.org/licenses/LICENSE-2.0
161 *
162 * Unless required by applicable law or agreed to in writing, software
163 * distributed under the License is distributed on an "AS IS" BASIS,
164 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
165 * See the License for the specific language governing permissions and
166 * limitations under the License.
167 */
168/**
169 * Helper to create a DOMStorageWrapper or else fall back to MemoryStorage.
170 * TODO: Once MemoryStorage and DOMStorageWrapper have a shared interface this method annotation should change
171 * to reflect this type
172 *
173 * @param domStorageName - Name of the underlying storage object
174 * (e.g. 'localStorage' or 'sessionStorage').
175 * @returns Turning off type information until a common interface is defined.
176 */
177var createStoragefor = function (domStorageName) {
178 try {
179 // NOTE: just accessing "localStorage" or "window['localStorage']" may throw a security exception,
180 // so it must be inside the try/catch.
181 if (typeof window !== 'undefined' &&
182 typeof window[domStorageName] !== 'undefined') {
183 // Need to test cache. Just because it's here doesn't mean it works
184 var domStorage = window[domStorageName];
185 domStorage.setItem('firebase:sentinel', 'cache');
186 domStorage.removeItem('firebase:sentinel');
187 return new DOMStorageWrapper(domStorage);
188 }
189 }
190 catch (e) { }
191 // Failed to create wrapper. Just return in-memory storage.
192 // TODO: log?
193 return new MemoryStorage();
194};
195/** A storage object that lasts across sessions */
196var PersistentStorage = createStoragefor('localStorage');
197/** A storage object that only lasts one session */
198var SessionStorage = createStoragefor('sessionStorage');
199
200/**
201 * @license
202 * Copyright 2017 Google LLC
203 *
204 * Licensed under the Apache License, Version 2.0 (the "License");
205 * you may not use this file except in compliance with the License.
206 * You may obtain a copy of the License at
207 *
208 * http://www.apache.org/licenses/LICENSE-2.0
209 *
210 * Unless required by applicable law or agreed to in writing, software
211 * distributed under the License is distributed on an "AS IS" BASIS,
212 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
213 * See the License for the specific language governing permissions and
214 * limitations under the License.
215 */
216var logClient = new logger$1.Logger('@firebase/database');
217/**
218 * Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
219 */
220var LUIDGenerator = (function () {
221 var id = 1;
222 return function () {
223 return id++;
224 };
225})();
226/**
227 * Sha1 hash of the input string
228 * @param str - The string to hash
229 * @returns {!string} The resulting hash
230 */
231var sha1 = function (str) {
232 var utf8Bytes = util.stringToByteArray(str);
233 var sha1 = new util.Sha1();
234 sha1.update(utf8Bytes);
235 var sha1Bytes = sha1.digest();
236 return util.base64.encodeByteArray(sha1Bytes);
237};
238var buildLogMessage_ = function () {
239 var varArgs = [];
240 for (var _i = 0; _i < arguments.length; _i++) {
241 varArgs[_i] = arguments[_i];
242 }
243 var message = '';
244 for (var i = 0; i < varArgs.length; i++) {
245 var arg = varArgs[i];
246 if (Array.isArray(arg) ||
247 (arg &&
248 typeof arg === 'object' &&
249 // eslint-disable-next-line @typescript-eslint/no-explicit-any
250 typeof arg.length === 'number')) {
251 message += buildLogMessage_.apply(null, arg);
252 }
253 else if (typeof arg === 'object') {
254 message += util.stringify(arg);
255 }
256 else {
257 message += arg;
258 }
259 message += ' ';
260 }
261 return message;
262};
263/**
264 * Use this for all debug messages in Firebase.
265 */
266var logger = null;
267/**
268 * Flag to check for log availability on first log message
269 */
270var firstLog_ = true;
271/**
272 * The implementation of Firebase.enableLogging (defined here to break dependencies)
273 * @param logger_ - A flag to turn on logging, or a custom logger
274 * @param persistent - Whether or not to persist logging settings across refreshes
275 */
276var enableLogging = function (logger_, persistent) {
277 util.assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
278 if (logger_ === true) {
279 logClient.logLevel = logger$1.LogLevel.VERBOSE;
280 logger = logClient.log.bind(logClient);
281 if (persistent) {
282 SessionStorage.set('logging_enabled', true);
283 }
284 }
285 else if (typeof logger_ === 'function') {
286 logger = logger_;
287 }
288 else {
289 logger = null;
290 SessionStorage.remove('logging_enabled');
291 }
292};
293var log = function () {
294 var varArgs = [];
295 for (var _i = 0; _i < arguments.length; _i++) {
296 varArgs[_i] = arguments[_i];
297 }
298 if (firstLog_ === true) {
299 firstLog_ = false;
300 if (logger === null && SessionStorage.get('logging_enabled') === true) {
301 enableLogging(true);
302 }
303 }
304 if (logger) {
305 var message = buildLogMessage_.apply(null, varArgs);
306 logger(message);
307 }
308};
309var logWrapper = function (prefix) {
310 return function () {
311 var varArgs = [];
312 for (var _i = 0; _i < arguments.length; _i++) {
313 varArgs[_i] = arguments[_i];
314 }
315 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
316 };
317};
318var error = function () {
319 var varArgs = [];
320 for (var _i = 0; _i < arguments.length; _i++) {
321 varArgs[_i] = arguments[_i];
322 }
323 var message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
324 logClient.error(message);
325};
326var fatal = function () {
327 var varArgs = [];
328 for (var _i = 0; _i < arguments.length; _i++) {
329 varArgs[_i] = arguments[_i];
330 }
331 var message = "FIREBASE FATAL ERROR: " + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
332 logClient.error(message);
333 throw new Error(message);
334};
335var warn = function () {
336 var varArgs = [];
337 for (var _i = 0; _i < arguments.length; _i++) {
338 varArgs[_i] = arguments[_i];
339 }
340 var message = 'FIREBASE WARNING: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
341 logClient.warn(message);
342};
343/**
344 * Logs a warning if the containing page uses https. Called when a call to new Firebase
345 * does not use https.
346 */
347var warnIfPageIsSecure = function () {
348 // Be very careful accessing browser globals. Who knows what may or may not exist.
349 if (typeof window !== 'undefined' &&
350 window.location &&
351 window.location.protocol &&
352 window.location.protocol.indexOf('https:') !== -1) {
353 warn('Insecure Firebase access from a secure page. ' +
354 'Please use https in calls to new Firebase().');
355 }
356};
357/**
358 * Returns true if data is NaN, or +/- Infinity.
359 */
360var isInvalidJSONNumber = function (data) {
361 return (typeof data === 'number' &&
362 (data !== data || // NaN
363 data === Number.POSITIVE_INFINITY ||
364 data === Number.NEGATIVE_INFINITY));
365};
366var executeWhenDOMReady = function (fn) {
367 if (util.isNodeSdk() || document.readyState === 'complete') {
368 fn();
369 }
370 else {
371 // Modeled after jQuery. Try DOMContentLoaded and onreadystatechange (which
372 // fire before onload), but fall back to onload.
373 var called_1 = false;
374 var wrappedFn_1 = function () {
375 if (!document.body) {
376 setTimeout(wrappedFn_1, Math.floor(10));
377 return;
378 }
379 if (!called_1) {
380 called_1 = true;
381 fn();
382 }
383 };
384 if (document.addEventListener) {
385 document.addEventListener('DOMContentLoaded', wrappedFn_1, false);
386 // fallback to onload.
387 window.addEventListener('load', wrappedFn_1, false);
388 // eslint-disable-next-line @typescript-eslint/no-explicit-any
389 }
390 else if (document.attachEvent) {
391 // IE.
392 // eslint-disable-next-line @typescript-eslint/no-explicit-any
393 document.attachEvent('onreadystatechange', function () {
394 if (document.readyState === 'complete') {
395 wrappedFn_1();
396 }
397 });
398 // fallback to onload.
399 // eslint-disable-next-line @typescript-eslint/no-explicit-any
400 window.attachEvent('onload', wrappedFn_1);
401 // jQuery has an extra hack for IE that we could employ (based on
402 // http://javascript.nwbox.com/IEContentLoaded/) But it looks really old.
403 // I'm hoping we don't need it.
404 }
405 }
406};
407/**
408 * Minimum key name. Invalid for actual data, used as a marker to sort before any valid names
409 */
410var MIN_NAME = '[MIN_NAME]';
411/**
412 * Maximum key name. Invalid for actual data, used as a marker to sort above any valid names
413 */
414var MAX_NAME = '[MAX_NAME]';
415/**
416 * Compares valid Firebase key names, plus min and max name
417 */
418var nameCompare = function (a, b) {
419 if (a === b) {
420 return 0;
421 }
422 else if (a === MIN_NAME || b === MAX_NAME) {
423 return -1;
424 }
425 else if (b === MIN_NAME || a === MAX_NAME) {
426 return 1;
427 }
428 else {
429 var aAsInt = tryParseInt(a), bAsInt = tryParseInt(b);
430 if (aAsInt !== null) {
431 if (bAsInt !== null) {
432 return aAsInt - bAsInt === 0 ? a.length - b.length : aAsInt - bAsInt;
433 }
434 else {
435 return -1;
436 }
437 }
438 else if (bAsInt !== null) {
439 return 1;
440 }
441 else {
442 return a < b ? -1 : 1;
443 }
444 }
445};
446/**
447 * @returns {!number} comparison result.
448 */
449var stringCompare = function (a, b) {
450 if (a === b) {
451 return 0;
452 }
453 else if (a < b) {
454 return -1;
455 }
456 else {
457 return 1;
458 }
459};
460var requireKey = function (key, obj) {
461 if (obj && key in obj) {
462 return obj[key];
463 }
464 else {
465 throw new Error('Missing required key (' + key + ') in object: ' + util.stringify(obj));
466 }
467};
468var ObjectToUniqueKey = function (obj) {
469 if (typeof obj !== 'object' || obj === null) {
470 return util.stringify(obj);
471 }
472 var keys = [];
473 // eslint-disable-next-line guard-for-in
474 for (var k in obj) {
475 keys.push(k);
476 }
477 // Export as json, but with the keys sorted.
478 keys.sort();
479 var key = '{';
480 for (var i = 0; i < keys.length; i++) {
481 if (i !== 0) {
482 key += ',';
483 }
484 key += util.stringify(keys[i]);
485 key += ':';
486 key += ObjectToUniqueKey(obj[keys[i]]);
487 }
488 key += '}';
489 return key;
490};
491/**
492 * Splits a string into a number of smaller segments of maximum size
493 * @param str - The string
494 * @param segsize - The maximum number of chars in the string.
495 * @returns The string, split into appropriately-sized chunks
496 */
497var splitStringBySize = function (str, segsize) {
498 var len = str.length;
499 if (len <= segsize) {
500 return [str];
501 }
502 var dataSegs = [];
503 for (var c = 0; c < len; c += segsize) {
504 if (c + segsize > len) {
505 dataSegs.push(str.substring(c, len));
506 }
507 else {
508 dataSegs.push(str.substring(c, c + segsize));
509 }
510 }
511 return dataSegs;
512};
513/**
514 * Apply a function to each (key, value) pair in an object or
515 * apply a function to each (index, value) pair in an array
516 * @param obj - The object or array to iterate over
517 * @param fn - The function to apply
518 */
519function each(obj, fn) {
520 for (var key in obj) {
521 if (obj.hasOwnProperty(key)) {
522 fn(key, obj[key]);
523 }
524 }
525}
526/**
527 * Borrowed from http://hg.secondlife.com/llsd/src/tip/js/typedarray.js (MIT License)
528 * I made one modification at the end and removed the NaN / Infinity
529 * handling (since it seemed broken [caused an overflow] and we don't need it). See MJL comments.
530 * @param v - A double
531 *
532 */
533var doubleToIEEE754String = function (v) {
534 util.assert(!isInvalidJSONNumber(v), 'Invalid JSON number'); // MJL
535 var ebits = 11, fbits = 52;
536 var bias = (1 << (ebits - 1)) - 1;
537 var s, e, f, ln, i;
538 // Compute sign, exponent, fraction
539 // Skip NaN / Infinity handling --MJL.
540 if (v === 0) {
541 e = 0;
542 f = 0;
543 s = 1 / v === -Infinity ? 1 : 0;
544 }
545 else {
546 s = v < 0;
547 v = Math.abs(v);
548 if (v >= Math.pow(2, 1 - bias)) {
549 // Normalized
550 ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
551 e = ln + bias;
552 f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
553 }
554 else {
555 // Denormalized
556 e = 0;
557 f = Math.round(v / Math.pow(2, 1 - bias - fbits));
558 }
559 }
560 // Pack sign, exponent, fraction
561 var bits = [];
562 for (i = fbits; i; i -= 1) {
563 bits.push(f % 2 ? 1 : 0);
564 f = Math.floor(f / 2);
565 }
566 for (i = ebits; i; i -= 1) {
567 bits.push(e % 2 ? 1 : 0);
568 e = Math.floor(e / 2);
569 }
570 bits.push(s ? 1 : 0);
571 bits.reverse();
572 var str = bits.join('');
573 // Return the data as a hex string. --MJL
574 var hexByteString = '';
575 for (i = 0; i < 64; i += 8) {
576 var hexByte = parseInt(str.substr(i, 8), 2).toString(16);
577 if (hexByte.length === 1) {
578 hexByte = '0' + hexByte;
579 }
580 hexByteString = hexByteString + hexByte;
581 }
582 return hexByteString.toLowerCase();
583};
584/**
585 * Used to detect if we're in a Chrome content script (which executes in an
586 * isolated environment where long-polling doesn't work).
587 */
588var isChromeExtensionContentScript = function () {
589 return !!(typeof window === 'object' &&
590 window['chrome'] &&
591 window['chrome']['extension'] &&
592 !/^chrome/.test(window.location.href));
593};
594/**
595 * Used to detect if we're in a Windows 8 Store app.
596 */
597var isWindowsStoreApp = function () {
598 // Check for the presence of a couple WinRT globals
599 return typeof Windows === 'object' && typeof Windows.UI === 'object';
600};
601/**
602 * Converts a server error code to a Javascript Error
603 */
604function errorForServerCode(code, query) {
605 var reason = 'Unknown Error';
606 if (code === 'too_big') {
607 reason =
608 'The data requested exceeds the maximum size ' +
609 'that can be accessed with a single request.';
610 }
611 else if (code === 'permission_denied') {
612 reason = "Client doesn't have permission to access the desired data.";
613 }
614 else if (code === 'unavailable') {
615 reason = 'The service is unavailable';
616 }
617 var error = new Error(code + ' at ' + query._path.toString() + ': ' + reason);
618 // eslint-disable-next-line @typescript-eslint/no-explicit-any
619 error.code = code.toUpperCase();
620 return error;
621}
622/**
623 * Used to test for integer-looking strings
624 */
625var INTEGER_REGEXP_ = new RegExp('^-?(0*)\\d{1,10}$');
626/**
627 * For use in keys, the minimum possible 32-bit integer.
628 */
629var INTEGER_32_MIN = -2147483648;
630/**
631 * For use in kyes, the maximum possible 32-bit integer.
632 */
633var INTEGER_32_MAX = 2147483647;
634/**
635 * If the string contains a 32-bit integer, return it. Else return null.
636 */
637var tryParseInt = function (str) {
638 if (INTEGER_REGEXP_.test(str)) {
639 var intVal = Number(str);
640 if (intVal >= INTEGER_32_MIN && intVal <= INTEGER_32_MAX) {
641 return intVal;
642 }
643 }
644 return null;
645};
646/**
647 * Helper to run some code but catch any exceptions and re-throw them later.
648 * Useful for preventing user callbacks from breaking internal code.
649 *
650 * Re-throwing the exception from a setTimeout is a little evil, but it's very
651 * convenient (we don't have to try to figure out when is a safe point to
652 * re-throw it), and the behavior seems reasonable:
653 *
654 * * If you aren't pausing on exceptions, you get an error in the console with
655 * the correct stack trace.
656 * * If you're pausing on all exceptions, the debugger will pause on your
657 * exception and then again when we rethrow it.
658 * * If you're only pausing on uncaught exceptions, the debugger will only pause
659 * on us re-throwing it.
660 *
661 * @param fn - The code to guard.
662 */
663var exceptionGuard = function (fn) {
664 try {
665 fn();
666 }
667 catch (e) {
668 // Re-throw exception when it's safe.
669 setTimeout(function () {
670 // It used to be that "throw e" would result in a good console error with
671 // relevant context, but as of Chrome 39, you just get the firebase.js
672 // file/line number where we re-throw it, which is useless. So we log
673 // e.stack explicitly.
674 var stack = e.stack || '';
675 warn('Exception was thrown by user callback.', stack);
676 throw e;
677 }, Math.floor(0));
678 }
679};
680/**
681 * @returns {boolean} true if we think we're currently being crawled.
682 */
683var beingCrawled = function () {
684 var userAgent = (typeof window === 'object' &&
685 window['navigator'] &&
686 window['navigator']['userAgent']) ||
687 '';
688 // For now we whitelist the most popular crawlers. We should refine this to be the set of crawlers we
689 // believe to support JavaScript/AJAX rendering.
690 // NOTE: Google Webmaster Tools doesn't really belong, but their "This is how a visitor to your website
691 // would have seen the page" is flaky if we don't treat it as a crawler.
692 return (userAgent.search(/googlebot|google webmaster tools|bingbot|yahoo! slurp|baiduspider|yandexbot|duckduckbot/i) >= 0);
693};
694/**
695 * Same as setTimeout() except on Node.JS it will /not/ prevent the process from exiting.
696 *
697 * It is removed with clearTimeout() as normal.
698 *
699 * @param fn - Function to run.
700 * @param time - Milliseconds to wait before running.
701 * @returns The setTimeout() return value.
702 */
703var setTimeoutNonBlocking = function (fn, time) {
704 var timeout = setTimeout(fn, time);
705 // eslint-disable-next-line @typescript-eslint/no-explicit-any
706 if (typeof timeout === 'object' && timeout['unref']) {
707 // eslint-disable-next-line @typescript-eslint/no-explicit-any
708 timeout['unref']();
709 }
710 return timeout;
711};
712
713/**
714 * @license
715 * Copyright 2021 Google LLC
716 *
717 * Licensed under the Apache License, Version 2.0 (the "License");
718 * you may not use this file except in compliance with the License.
719 * You may obtain a copy of the License at
720 *
721 * http://www.apache.org/licenses/LICENSE-2.0
722 *
723 * Unless required by applicable law or agreed to in writing, software
724 * distributed under the License is distributed on an "AS IS" BASIS,
725 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
726 * See the License for the specific language governing permissions and
727 * limitations under the License.
728 */
729/**
730 * Abstraction around AppCheck's token fetching capabilities.
731 */
732var AppCheckTokenProvider = /** @class */ (function () {
733 function AppCheckTokenProvider(appName_, appCheckProvider) {
734 var _this = this;
735 this.appName_ = appName_;
736 this.appCheckProvider = appCheckProvider;
737 this.appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
738 if (!this.appCheck) {
739 appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.get().then(function (appCheck) { return (_this.appCheck = appCheck); });
740 }
741 }
742 AppCheckTokenProvider.prototype.getToken = function (forceRefresh) {
743 var _this = this;
744 if (!this.appCheck) {
745 return new Promise(function (resolve, reject) {
746 // Support delayed initialization of FirebaseAppCheck. This allows our
747 // customers to initialize the RTDB SDK before initializing Firebase
748 // AppCheck and ensures that all requests are authenticated if a token
749 // becomes available before the timoeout below expires.
750 setTimeout(function () {
751 if (_this.appCheck) {
752 _this.getToken(forceRefresh).then(resolve, reject);
753 }
754 else {
755 resolve(null);
756 }
757 }, 0);
758 });
759 }
760 return this.appCheck.getToken(forceRefresh);
761 };
762 AppCheckTokenProvider.prototype.addTokenChangeListener = function (listener) {
763 var _a;
764 (_a = this.appCheckProvider) === null || _a === void 0 ? void 0 : _a.get().then(function (appCheck) { return appCheck.addTokenListener(listener); });
765 };
766 AppCheckTokenProvider.prototype.notifyForInvalidToken = function () {
767 warn("Provided AppCheck credentials for the app named \"" + this.appName_ + "\" " +
768 'are invalid. This usually indicates your app was not initialized correctly.');
769 };
770 return AppCheckTokenProvider;
771}());
772
773/**
774 * @license
775 * Copyright 2017 Google LLC
776 *
777 * Licensed under the Apache License, Version 2.0 (the "License");
778 * you may not use this file except in compliance with the License.
779 * You may obtain a copy of the License at
780 *
781 * http://www.apache.org/licenses/LICENSE-2.0
782 *
783 * Unless required by applicable law or agreed to in writing, software
784 * distributed under the License is distributed on an "AS IS" BASIS,
785 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
786 * See the License for the specific language governing permissions and
787 * limitations under the License.
788 */
789/**
790 * Abstraction around FirebaseApp's token fetching capabilities.
791 */
792var FirebaseAuthTokenProvider = /** @class */ (function () {
793 function FirebaseAuthTokenProvider(appName_, firebaseOptions_, authProvider_) {
794 var _this = this;
795 this.appName_ = appName_;
796 this.firebaseOptions_ = firebaseOptions_;
797 this.authProvider_ = authProvider_;
798 this.auth_ = null;
799 this.auth_ = authProvider_.getImmediate({ optional: true });
800 if (!this.auth_) {
801 authProvider_.onInit(function (auth) { return (_this.auth_ = auth); });
802 }
803 }
804 FirebaseAuthTokenProvider.prototype.getToken = function (forceRefresh) {
805 var _this = this;
806 if (!this.auth_) {
807 return new Promise(function (resolve, reject) {
808 // Support delayed initialization of FirebaseAuth. This allows our
809 // customers to initialize the RTDB SDK before initializing Firebase
810 // Auth and ensures that all requests are authenticated if a token
811 // becomes available before the timoeout below expires.
812 setTimeout(function () {
813 if (_this.auth_) {
814 _this.getToken(forceRefresh).then(resolve, reject);
815 }
816 else {
817 resolve(null);
818 }
819 }, 0);
820 });
821 }
822 return this.auth_.getToken(forceRefresh).catch(function (error) {
823 // TODO: Need to figure out all the cases this is raised and whether
824 // this makes sense.
825 if (error && error.code === 'auth/token-not-initialized') {
826 log('Got auth/token-not-initialized error. Treating as null token.');
827 return null;
828 }
829 else {
830 return Promise.reject(error);
831 }
832 });
833 };
834 FirebaseAuthTokenProvider.prototype.addTokenChangeListener = function (listener) {
835 // TODO: We might want to wrap the listener and call it with no args to
836 // avoid a leaky abstraction, but that makes removing the listener harder.
837 if (this.auth_) {
838 this.auth_.addAuthTokenListener(listener);
839 }
840 else {
841 this.authProvider_
842 .get()
843 .then(function (auth) { return auth.addAuthTokenListener(listener); });
844 }
845 };
846 FirebaseAuthTokenProvider.prototype.removeTokenChangeListener = function (listener) {
847 this.authProvider_
848 .get()
849 .then(function (auth) { return auth.removeAuthTokenListener(listener); });
850 };
851 FirebaseAuthTokenProvider.prototype.notifyForInvalidToken = function () {
852 var errorMessage = 'Provided authentication credentials for the app named "' +
853 this.appName_ +
854 '" are invalid. This usually indicates your app was not ' +
855 'initialized correctly. ';
856 if ('credential' in this.firebaseOptions_) {
857 errorMessage +=
858 'Make sure the "credential" property provided to initializeApp() ' +
859 'is authorized to access the specified "databaseURL" and is from the correct ' +
860 'project.';
861 }
862 else if ('serviceAccount' in this.firebaseOptions_) {
863 errorMessage +=
864 'Make sure the "serviceAccount" property provided to initializeApp() ' +
865 'is authorized to access the specified "databaseURL" and is from the correct ' +
866 'project.';
867 }
868 else {
869 errorMessage +=
870 'Make sure the "apiKey" and "databaseURL" properties provided to ' +
871 'initializeApp() match the values provided for your app at ' +
872 'https://console.firebase.google.com/.';
873 }
874 warn(errorMessage);
875 };
876 return FirebaseAuthTokenProvider;
877}());
878/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */
879var EmulatorTokenProvider = /** @class */ (function () {
880 function EmulatorTokenProvider(accessToken) {
881 this.accessToken = accessToken;
882 }
883 EmulatorTokenProvider.prototype.getToken = function (forceRefresh) {
884 return Promise.resolve({
885 accessToken: this.accessToken
886 });
887 };
888 EmulatorTokenProvider.prototype.addTokenChangeListener = function (listener) {
889 // Invoke the listener immediately to match the behavior in Firebase Auth
890 // (see packages/auth/src/auth.js#L1807)
891 listener(this.accessToken);
892 };
893 EmulatorTokenProvider.prototype.removeTokenChangeListener = function (listener) { };
894 EmulatorTokenProvider.prototype.notifyForInvalidToken = function () { };
895 /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */
896 EmulatorTokenProvider.OWNER = 'owner';
897 return EmulatorTokenProvider;
898}());
899
900/**
901 * @license
902 * Copyright 2017 Google LLC
903 *
904 * Licensed under the Apache License, Version 2.0 (the "License");
905 * you may not use this file except in compliance with the License.
906 * You may obtain a copy of the License at
907 *
908 * http://www.apache.org/licenses/LICENSE-2.0
909 *
910 * Unless required by applicable law or agreed to in writing, software
911 * distributed under the License is distributed on an "AS IS" BASIS,
912 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
913 * See the License for the specific language governing permissions and
914 * limitations under the License.
915 */
916var PROTOCOL_VERSION = '5';
917var VERSION_PARAM = 'v';
918var TRANSPORT_SESSION_PARAM = 's';
919var REFERER_PARAM = 'r';
920var FORGE_REF = 'f';
921// Matches console.firebase.google.com, firebase-console-*.corp.google.com and
922// firebase.corp.google.com
923var FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/;
924var LAST_SESSION_PARAM = 'ls';
925var APPLICATION_ID_PARAM = 'p';
926var APP_CHECK_TOKEN_PARAM = 'ac';
927var WEBSOCKET = 'websocket';
928var LONG_POLLING = 'long_polling';
929
930/**
931 * @license
932 * Copyright 2017 Google LLC
933 *
934 * Licensed under the Apache License, Version 2.0 (the "License");
935 * you may not use this file except in compliance with the License.
936 * You may obtain a copy of the License at
937 *
938 * http://www.apache.org/licenses/LICENSE-2.0
939 *
940 * Unless required by applicable law or agreed to in writing, software
941 * distributed under the License is distributed on an "AS IS" BASIS,
942 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
943 * See the License for the specific language governing permissions and
944 * limitations under the License.
945 */
946/**
947 * A class that holds metadata about a Repo object
948 */
949var RepoInfo = /** @class */ (function () {
950 /**
951 * @param host - Hostname portion of the url for the repo
952 * @param secure - Whether or not this repo is accessed over ssl
953 * @param namespace - The namespace represented by the repo
954 * @param webSocketOnly - Whether to prefer websockets over all other transports (used by Nest).
955 * @param nodeAdmin - Whether this instance uses Admin SDK credentials
956 * @param persistenceKey - Override the default session persistence storage key
957 */
958 function RepoInfo(host, secure, namespace, webSocketOnly, nodeAdmin, persistenceKey, includeNamespaceInQueryParams) {
959 if (nodeAdmin === void 0) { nodeAdmin = false; }
960 if (persistenceKey === void 0) { persistenceKey = ''; }
961 if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
962 this.secure = secure;
963 this.namespace = namespace;
964 this.webSocketOnly = webSocketOnly;
965 this.nodeAdmin = nodeAdmin;
966 this.persistenceKey = persistenceKey;
967 this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
968 this._host = host.toLowerCase();
969 this._domain = this._host.substr(this._host.indexOf('.') + 1);
970 this.internalHost =
971 PersistentStorage.get('host:' + host) || this._host;
972 }
973 RepoInfo.prototype.isCacheableHost = function () {
974 return this.internalHost.substr(0, 2) === 's-';
975 };
976 RepoInfo.prototype.isCustomHost = function () {
977 return (this._domain !== 'firebaseio.com' &&
978 this._domain !== 'firebaseio-demo.com');
979 };
980 Object.defineProperty(RepoInfo.prototype, "host", {
981 get: function () {
982 return this._host;
983 },
984 set: function (newHost) {
985 if (newHost !== this.internalHost) {
986 this.internalHost = newHost;
987 if (this.isCacheableHost()) {
988 PersistentStorage.set('host:' + this._host, this.internalHost);
989 }
990 }
991 },
992 enumerable: false,
993 configurable: true
994 });
995 RepoInfo.prototype.toString = function () {
996 var str = this.toURLString();
997 if (this.persistenceKey) {
998 str += '<' + this.persistenceKey + '>';
999 }
1000 return str;
1001 };
1002 RepoInfo.prototype.toURLString = function () {
1003 var protocol = this.secure ? 'https://' : 'http://';
1004 var query = this.includeNamespaceInQueryParams
1005 ? "?ns=" + this.namespace
1006 : '';
1007 return "" + protocol + this.host + "/" + query;
1008 };
1009 return RepoInfo;
1010}());
1011function repoInfoNeedsQueryParam(repoInfo) {
1012 return (repoInfo.host !== repoInfo.internalHost ||
1013 repoInfo.isCustomHost() ||
1014 repoInfo.includeNamespaceInQueryParams);
1015}
1016/**
1017 * Returns the websocket URL for this repo
1018 * @param repoInfo - RepoInfo object
1019 * @param type - of connection
1020 * @param params - list
1021 * @returns The URL for this repo
1022 */
1023function repoInfoConnectionURL(repoInfo, type, params) {
1024 util.assert(typeof type === 'string', 'typeof type must == string');
1025 util.assert(typeof params === 'object', 'typeof params must == object');
1026 var connURL;
1027 if (type === WEBSOCKET) {
1028 connURL =
1029 (repoInfo.secure ? 'wss://' : 'ws://') + repoInfo.internalHost + '/.ws?';
1030 }
1031 else if (type === LONG_POLLING) {
1032 connURL =
1033 (repoInfo.secure ? 'https://' : 'http://') +
1034 repoInfo.internalHost +
1035 '/.lp?';
1036 }
1037 else {
1038 throw new Error('Unknown connection type: ' + type);
1039 }
1040 if (repoInfoNeedsQueryParam(repoInfo)) {
1041 params['ns'] = repoInfo.namespace;
1042 }
1043 var pairs = [];
1044 each(params, function (key, value) {
1045 pairs.push(key + '=' + value);
1046 });
1047 return connURL + pairs.join('&');
1048}
1049
1050/**
1051 * @license
1052 * Copyright 2017 Google LLC
1053 *
1054 * Licensed under the Apache License, Version 2.0 (the "License");
1055 * you may not use this file except in compliance with the License.
1056 * You may obtain a copy of the License at
1057 *
1058 * http://www.apache.org/licenses/LICENSE-2.0
1059 *
1060 * Unless required by applicable law or agreed to in writing, software
1061 * distributed under the License is distributed on an "AS IS" BASIS,
1062 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1063 * See the License for the specific language governing permissions and
1064 * limitations under the License.
1065 */
1066/**
1067 * Tracks a collection of stats.
1068 */
1069var StatsCollection = /** @class */ (function () {
1070 function StatsCollection() {
1071 this.counters_ = {};
1072 }
1073 StatsCollection.prototype.incrementCounter = function (name, amount) {
1074 if (amount === void 0) { amount = 1; }
1075 if (!util.contains(this.counters_, name)) {
1076 this.counters_[name] = 0;
1077 }
1078 this.counters_[name] += amount;
1079 };
1080 StatsCollection.prototype.get = function () {
1081 return util.deepCopy(this.counters_);
1082 };
1083 return StatsCollection;
1084}());
1085
1086/**
1087 * @license
1088 * Copyright 2017 Google LLC
1089 *
1090 * Licensed under the Apache License, Version 2.0 (the "License");
1091 * you may not use this file except in compliance with the License.
1092 * You may obtain a copy of the License at
1093 *
1094 * http://www.apache.org/licenses/LICENSE-2.0
1095 *
1096 * Unless required by applicable law or agreed to in writing, software
1097 * distributed under the License is distributed on an "AS IS" BASIS,
1098 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1099 * See the License for the specific language governing permissions and
1100 * limitations under the License.
1101 */
1102var collections = {};
1103var reporters = {};
1104function statsManagerGetCollection(repoInfo) {
1105 var hashString = repoInfo.toString();
1106 if (!collections[hashString]) {
1107 collections[hashString] = new StatsCollection();
1108 }
1109 return collections[hashString];
1110}
1111function statsManagerGetOrCreateReporter(repoInfo, creatorFunction) {
1112 var hashString = repoInfo.toString();
1113 if (!reporters[hashString]) {
1114 reporters[hashString] = creatorFunction();
1115 }
1116 return reporters[hashString];
1117}
1118
1119/**
1120 * @license
1121 * Copyright 2017 Google LLC
1122 *
1123 * Licensed under the Apache License, Version 2.0 (the "License");
1124 * you may not use this file except in compliance with the License.
1125 * You may obtain a copy of the License at
1126 *
1127 * http://www.apache.org/licenses/LICENSE-2.0
1128 *
1129 * Unless required by applicable law or agreed to in writing, software
1130 * distributed under the License is distributed on an "AS IS" BASIS,
1131 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1132 * See the License for the specific language governing permissions and
1133 * limitations under the License.
1134 */
1135/**
1136 * This class ensures the packets from the server arrive in order
1137 * This class takes data from the server and ensures it gets passed into the callbacks in order.
1138 */
1139var PacketReceiver = /** @class */ (function () {
1140 /**
1141 * @param onMessage_
1142 */
1143 function PacketReceiver(onMessage_) {
1144 this.onMessage_ = onMessage_;
1145 this.pendingResponses = [];
1146 this.currentResponseNum = 0;
1147 this.closeAfterResponse = -1;
1148 this.onClose = null;
1149 }
1150 PacketReceiver.prototype.closeAfter = function (responseNum, callback) {
1151 this.closeAfterResponse = responseNum;
1152 this.onClose = callback;
1153 if (this.closeAfterResponse < this.currentResponseNum) {
1154 this.onClose();
1155 this.onClose = null;
1156 }
1157 };
1158 /**
1159 * Each message from the server comes with a response number, and an array of data. The responseNumber
1160 * allows us to ensure that we process them in the right order, since we can't be guaranteed that all
1161 * browsers will respond in the same order as the requests we sent
1162 */
1163 PacketReceiver.prototype.handleResponse = function (requestNum, data) {
1164 var _this = this;
1165 this.pendingResponses[requestNum] = data;
1166 var _loop_1 = function () {
1167 var toProcess = this_1.pendingResponses[this_1.currentResponseNum];
1168 delete this_1.pendingResponses[this_1.currentResponseNum];
1169 var _loop_2 = function (i) {
1170 if (toProcess[i]) {
1171 exceptionGuard(function () {
1172 _this.onMessage_(toProcess[i]);
1173 });
1174 }
1175 };
1176 for (var i = 0; i < toProcess.length; ++i) {
1177 _loop_2(i);
1178 }
1179 if (this_1.currentResponseNum === this_1.closeAfterResponse) {
1180 if (this_1.onClose) {
1181 this_1.onClose();
1182 this_1.onClose = null;
1183 }
1184 return "break";
1185 }
1186 this_1.currentResponseNum++;
1187 };
1188 var this_1 = this;
1189 while (this.pendingResponses[this.currentResponseNum]) {
1190 var state_1 = _loop_1();
1191 if (state_1 === "break")
1192 break;
1193 }
1194 };
1195 return PacketReceiver;
1196}());
1197
1198/**
1199 * @license
1200 * Copyright 2017 Google LLC
1201 *
1202 * Licensed under the Apache License, Version 2.0 (the "License");
1203 * you may not use this file except in compliance with the License.
1204 * You may obtain a copy of the License at
1205 *
1206 * http://www.apache.org/licenses/LICENSE-2.0
1207 *
1208 * Unless required by applicable law or agreed to in writing, software
1209 * distributed under the License is distributed on an "AS IS" BASIS,
1210 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1211 * See the License for the specific language governing permissions and
1212 * limitations under the License.
1213 */
1214// URL query parameters associated with longpolling
1215var FIREBASE_LONGPOLL_START_PARAM = 'start';
1216var FIREBASE_LONGPOLL_CLOSE_COMMAND = 'close';
1217var FIREBASE_LONGPOLL_COMMAND_CB_NAME = 'pLPCommand';
1218var FIREBASE_LONGPOLL_DATA_CB_NAME = 'pRTLPCB';
1219var FIREBASE_LONGPOLL_ID_PARAM = 'id';
1220var FIREBASE_LONGPOLL_PW_PARAM = 'pw';
1221var FIREBASE_LONGPOLL_SERIAL_PARAM = 'ser';
1222var FIREBASE_LONGPOLL_CALLBACK_ID_PARAM = 'cb';
1223var FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM = 'seg';
1224var FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET = 'ts';
1225var FIREBASE_LONGPOLL_DATA_PARAM = 'd';
1226var FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM = 'dframe';
1227//Data size constants.
1228//TODO: Perf: the maximum length actually differs from browser to browser.
1229// We should check what browser we're on and set accordingly.
1230var MAX_URL_DATA_SIZE = 1870;
1231var SEG_HEADER_SIZE = 30; //ie: &seg=8299234&ts=982389123&d=
1232var MAX_PAYLOAD_SIZE = MAX_URL_DATA_SIZE - SEG_HEADER_SIZE;
1233/**
1234 * Keepalive period
1235 * send a fresh request at minimum every 25 seconds. Opera has a maximum request
1236 * length of 30 seconds that we can't exceed.
1237 */
1238var KEEPALIVE_REQUEST_INTERVAL = 25000;
1239/**
1240 * How long to wait before aborting a long-polling connection attempt.
1241 */
1242var LP_CONNECT_TIMEOUT = 30000;
1243/**
1244 * This class manages a single long-polling connection.
1245 */
1246var BrowserPollConnection = /** @class */ (function () {
1247 /**
1248 * @param connId An identifier for this connection, used for logging
1249 * @param repoInfo The info for the endpoint to send data to.
1250 * @param applicationId The Firebase App ID for this project.
1251 * @param appCheckToken The AppCheck token for this client.
1252 * @param authToken The AuthToken to use for this connection.
1253 * @param transportSessionId Optional transportSessionid if we are
1254 * reconnecting for an existing transport session
1255 * @param lastSessionId Optional lastSessionId if the PersistentConnection has
1256 * already created a connection previously
1257 */
1258 function BrowserPollConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1259 var _this = this;
1260 this.connId = connId;
1261 this.repoInfo = repoInfo;
1262 this.applicationId = applicationId;
1263 this.appCheckToken = appCheckToken;
1264 this.authToken = authToken;
1265 this.transportSessionId = transportSessionId;
1266 this.lastSessionId = lastSessionId;
1267 this.bytesSent = 0;
1268 this.bytesReceived = 0;
1269 this.everConnected_ = false;
1270 this.log_ = logWrapper(connId);
1271 this.stats_ = statsManagerGetCollection(repoInfo);
1272 this.urlFn = function (params) {
1273 // Always add the token if we have one.
1274 if (_this.appCheckToken) {
1275 params[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1276 }
1277 return repoInfoConnectionURL(repoInfo, LONG_POLLING, params);
1278 };
1279 }
1280 /**
1281 * @param onMessage - Callback when messages arrive
1282 * @param onDisconnect - Callback with connection lost.
1283 */
1284 BrowserPollConnection.prototype.open = function (onMessage, onDisconnect) {
1285 var _this = this;
1286 this.curSegmentNum = 0;
1287 this.onDisconnect_ = onDisconnect;
1288 this.myPacketOrderer = new PacketReceiver(onMessage);
1289 this.isClosed_ = false;
1290 this.connectTimeoutTimer_ = setTimeout(function () {
1291 _this.log_('Timed out trying to connect.');
1292 // Make sure we clear the host cache
1293 _this.onClosed_();
1294 _this.connectTimeoutTimer_ = null;
1295 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1296 }, Math.floor(LP_CONNECT_TIMEOUT));
1297 // Ensure we delay the creation of the iframe until the DOM is loaded.
1298 executeWhenDOMReady(function () {
1299 if (_this.isClosed_) {
1300 return;
1301 }
1302 //Set up a callback that gets triggered once a connection is set up.
1303 _this.scriptTagHolder = new FirebaseIFrameScriptHolder(function () {
1304 var args = [];
1305 for (var _i = 0; _i < arguments.length; _i++) {
1306 args[_i] = arguments[_i];
1307 }
1308 var _a = tslib.__read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2], arg3 = _a[3], arg4 = _a[4];
1309 _this.incrementIncomingBytes_(args);
1310 if (!_this.scriptTagHolder) {
1311 return; // we closed the connection.
1312 }
1313 if (_this.connectTimeoutTimer_) {
1314 clearTimeout(_this.connectTimeoutTimer_);
1315 _this.connectTimeoutTimer_ = null;
1316 }
1317 _this.everConnected_ = true;
1318 if (command === FIREBASE_LONGPOLL_START_PARAM) {
1319 _this.id = arg1;
1320 _this.password = arg2;
1321 }
1322 else if (command === FIREBASE_LONGPOLL_CLOSE_COMMAND) {
1323 // Don't clear the host cache. We got a response from the server, so we know it's reachable
1324 if (arg1) {
1325 // We aren't expecting any more data (other than what the server's already in the process of sending us
1326 // through our already open polls), so don't send any more.
1327 _this.scriptTagHolder.sendNewPolls = false;
1328 // arg1 in this case is the last response number sent by the server. We should try to receive
1329 // all of the responses up to this one before closing
1330 _this.myPacketOrderer.closeAfter(arg1, function () {
1331 _this.onClosed_();
1332 });
1333 }
1334 else {
1335 _this.onClosed_();
1336 }
1337 }
1338 else {
1339 throw new Error('Unrecognized command received: ' + command);
1340 }
1341 }, function () {
1342 var args = [];
1343 for (var _i = 0; _i < arguments.length; _i++) {
1344 args[_i] = arguments[_i];
1345 }
1346 var _a = tslib.__read(args, 2), pN = _a[0], data = _a[1];
1347 _this.incrementIncomingBytes_(args);
1348 _this.myPacketOrderer.handleResponse(pN, data);
1349 }, function () {
1350 _this.onClosed_();
1351 }, _this.urlFn);
1352 //Send the initial request to connect. The serial number is simply to keep the browser from pulling previous results
1353 //from cache.
1354 var urlParams = {};
1355 urlParams[FIREBASE_LONGPOLL_START_PARAM] = 't';
1356 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = Math.floor(Math.random() * 100000000);
1357 if (_this.scriptTagHolder.uniqueCallbackIdentifier) {
1358 urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] = _this.scriptTagHolder.uniqueCallbackIdentifier;
1359 }
1360 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1361 if (_this.transportSessionId) {
1362 urlParams[TRANSPORT_SESSION_PARAM] = _this.transportSessionId;
1363 }
1364 if (_this.lastSessionId) {
1365 urlParams[LAST_SESSION_PARAM] = _this.lastSessionId;
1366 }
1367 if (_this.applicationId) {
1368 urlParams[APPLICATION_ID_PARAM] = _this.applicationId;
1369 }
1370 if (_this.appCheckToken) {
1371 urlParams[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1372 }
1373 if (typeof location !== 'undefined' &&
1374 location.hostname &&
1375 FORGE_DOMAIN_RE.test(location.hostname)) {
1376 urlParams[REFERER_PARAM] = FORGE_REF;
1377 }
1378 var connectURL = _this.urlFn(urlParams);
1379 _this.log_('Connecting via long-poll to ' + connectURL);
1380 _this.scriptTagHolder.addTag(connectURL, function () {
1381 /* do nothing */
1382 });
1383 });
1384 };
1385 /**
1386 * Call this when a handshake has completed successfully and we want to consider the connection established
1387 */
1388 BrowserPollConnection.prototype.start = function () {
1389 this.scriptTagHolder.startLongPoll(this.id, this.password);
1390 this.addDisconnectPingFrame(this.id, this.password);
1391 };
1392 /**
1393 * Forces long polling to be considered as a potential transport
1394 */
1395 BrowserPollConnection.forceAllow = function () {
1396 BrowserPollConnection.forceAllow_ = true;
1397 };
1398 /**
1399 * Forces longpolling to not be considered as a potential transport
1400 */
1401 BrowserPollConnection.forceDisallow = function () {
1402 BrowserPollConnection.forceDisallow_ = true;
1403 };
1404 // Static method, use string literal so it can be accessed in a generic way
1405 BrowserPollConnection.isAvailable = function () {
1406 if (util.isNodeSdk()) {
1407 return false;
1408 }
1409 else if (BrowserPollConnection.forceAllow_) {
1410 return true;
1411 }
1412 else {
1413 // NOTE: In React-Native there's normally no 'document', but if you debug a React-Native app in
1414 // the Chrome debugger, 'document' is defined, but document.createElement is null (2015/06/08).
1415 return (!BrowserPollConnection.forceDisallow_ &&
1416 typeof document !== 'undefined' &&
1417 document.createElement != null &&
1418 !isChromeExtensionContentScript() &&
1419 !isWindowsStoreApp());
1420 }
1421 };
1422 /**
1423 * No-op for polling
1424 */
1425 BrowserPollConnection.prototype.markConnectionHealthy = function () { };
1426 /**
1427 * Stops polling and cleans up the iframe
1428 */
1429 BrowserPollConnection.prototype.shutdown_ = function () {
1430 this.isClosed_ = true;
1431 if (this.scriptTagHolder) {
1432 this.scriptTagHolder.close();
1433 this.scriptTagHolder = null;
1434 }
1435 //remove the disconnect frame, which will trigger an XHR call to the server to tell it we're leaving.
1436 if (this.myDisconnFrame) {
1437 document.body.removeChild(this.myDisconnFrame);
1438 this.myDisconnFrame = null;
1439 }
1440 if (this.connectTimeoutTimer_) {
1441 clearTimeout(this.connectTimeoutTimer_);
1442 this.connectTimeoutTimer_ = null;
1443 }
1444 };
1445 /**
1446 * Triggered when this transport is closed
1447 */
1448 BrowserPollConnection.prototype.onClosed_ = function () {
1449 if (!this.isClosed_) {
1450 this.log_('Longpoll is closing itself');
1451 this.shutdown_();
1452 if (this.onDisconnect_) {
1453 this.onDisconnect_(this.everConnected_);
1454 this.onDisconnect_ = null;
1455 }
1456 }
1457 };
1458 /**
1459 * External-facing close handler. RealTime has requested we shut down. Kill our connection and tell the server
1460 * that we've left.
1461 */
1462 BrowserPollConnection.prototype.close = function () {
1463 if (!this.isClosed_) {
1464 this.log_('Longpoll is being closed.');
1465 this.shutdown_();
1466 }
1467 };
1468 /**
1469 * Send the JSON object down to the server. It will need to be stringified, base64 encoded, and then
1470 * broken into chunks (since URLs have a small maximum length).
1471 * @param data - The JSON data to transmit.
1472 */
1473 BrowserPollConnection.prototype.send = function (data) {
1474 var dataStr = util.stringify(data);
1475 this.bytesSent += dataStr.length;
1476 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1477 //first, lets get the base64-encoded data
1478 var base64data = util.base64Encode(dataStr);
1479 //We can only fit a certain amount in each URL, so we need to split this request
1480 //up into multiple pieces if it doesn't fit in one request.
1481 var dataSegs = splitStringBySize(base64data, MAX_PAYLOAD_SIZE);
1482 //Enqueue each segment for transmission. We assign each chunk a sequential ID and a total number
1483 //of segments so that we can reassemble the packet on the server.
1484 for (var i = 0; i < dataSegs.length; i++) {
1485 this.scriptTagHolder.enqueueSegment(this.curSegmentNum, dataSegs.length, dataSegs[i]);
1486 this.curSegmentNum++;
1487 }
1488 };
1489 /**
1490 * This is how we notify the server that we're leaving.
1491 * We aren't able to send requests with DHTML on a window close event, but we can
1492 * trigger XHR requests in some browsers (everything but Opera basically).
1493 */
1494 BrowserPollConnection.prototype.addDisconnectPingFrame = function (id, pw) {
1495 if (util.isNodeSdk()) {
1496 return;
1497 }
1498 this.myDisconnFrame = document.createElement('iframe');
1499 var urlParams = {};
1500 urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
1501 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = id;
1502 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = pw;
1503 this.myDisconnFrame.src = this.urlFn(urlParams);
1504 this.myDisconnFrame.style.display = 'none';
1505 document.body.appendChild(this.myDisconnFrame);
1506 };
1507 /**
1508 * Used to track the bytes received by this client
1509 */
1510 BrowserPollConnection.prototype.incrementIncomingBytes_ = function (args) {
1511 // TODO: This is an annoying perf hit just to track the number of incoming bytes. Maybe it should be opt-in.
1512 var bytesReceived = util.stringify(args).length;
1513 this.bytesReceived += bytesReceived;
1514 this.stats_.incrementCounter('bytes_received', bytesReceived);
1515 };
1516 return BrowserPollConnection;
1517}());
1518/*********************************************************************************************
1519 * A wrapper around an iframe that is used as a long-polling script holder.
1520 *********************************************************************************************/
1521var FirebaseIFrameScriptHolder = /** @class */ (function () {
1522 /**
1523 * @param commandCB - The callback to be called when control commands are recevied from the server.
1524 * @param onMessageCB - The callback to be triggered when responses arrive from the server.
1525 * @param onDisconnect - The callback to be triggered when this tag holder is closed
1526 * @param urlFn - A function that provides the URL of the endpoint to send data to.
1527 */
1528 function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnect, urlFn) {
1529 this.onDisconnect = onDisconnect;
1530 this.urlFn = urlFn;
1531 //We maintain a count of all of the outstanding requests, because if we have too many active at once it can cause
1532 //problems in some browsers.
1533 this.outstandingRequests = new Set();
1534 //A queue of the pending segments waiting for transmission to the server.
1535 this.pendingSegs = [];
1536 //A serial number. We use this for two things:
1537 // 1) A way to ensure the browser doesn't cache responses to polls
1538 // 2) A way to make the server aware when long-polls arrive in a different order than we started them. The
1539 // server needs to release both polls in this case or it will cause problems in Opera since Opera can only execute
1540 // JSONP code in the order it was added to the iframe.
1541 this.currentSerial = Math.floor(Math.random() * 100000000);
1542 // This gets set to false when we're "closing down" the connection (e.g. we're switching transports but there's still
1543 // incoming data from the server that we're waiting for).
1544 this.sendNewPolls = true;
1545 if (!util.isNodeSdk()) {
1546 //Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
1547 //iframes where we put the long-polling script tags. We have two callbacks:
1548 // 1) Command Callback - Triggered for control issues, like starting a connection.
1549 // 2) Message Callback - Triggered when new data arrives.
1550 this.uniqueCallbackIdentifier = LUIDGenerator();
1551 window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
1552 window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] = onMessageCB;
1553 //Create an iframe for us to add script tags to.
1554 this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_();
1555 // Set the iframe's contents.
1556 var script = '';
1557 // if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
1558 // for ie9, but ie8 needs to do it again in the document itself.
1559 if (this.myIFrame.src &&
1560 this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
1561 var currentDomain = document.domain;
1562 script = '<script>document.domain="' + currentDomain + '";</script>';
1563 }
1564 var iframeContents = '<html><body>' + script + '</body></html>';
1565 try {
1566 this.myIFrame.doc.open();
1567 this.myIFrame.doc.write(iframeContents);
1568 this.myIFrame.doc.close();
1569 }
1570 catch (e) {
1571 log('frame writing exception');
1572 if (e.stack) {
1573 log(e.stack);
1574 }
1575 log(e);
1576 }
1577 }
1578 else {
1579 this.commandCB = commandCB;
1580 this.onMessageCB = onMessageCB;
1581 }
1582 }
1583 /**
1584 * Each browser has its own funny way to handle iframes. Here we mush them all together into one object that I can
1585 * actually use.
1586 */
1587 FirebaseIFrameScriptHolder.createIFrame_ = function () {
1588 var iframe = document.createElement('iframe');
1589 iframe.style.display = 'none';
1590 // This is necessary in order to initialize the document inside the iframe
1591 if (document.body) {
1592 document.body.appendChild(iframe);
1593 try {
1594 // If document.domain has been modified in IE, this will throw an error, and we need to set the
1595 // domain of the iframe's document manually. We can do this via a javascript: url as the src attribute
1596 // Also note that we must do this *after* the iframe has been appended to the page. Otherwise it doesn't work.
1597 var a = iframe.contentWindow.document;
1598 if (!a) {
1599 // Apologies for the log-spam, I need to do something to keep closure from optimizing out the assignment above.
1600 log('No IE domain setting required');
1601 }
1602 }
1603 catch (e) {
1604 var domain = document.domain;
1605 iframe.src =
1606 "javascript:void((function(){document.open();document.domain='" +
1607 domain +
1608 "';document.close();})())";
1609 }
1610 }
1611 else {
1612 // LongPollConnection attempts to delay initialization until the document is ready, so hopefully this
1613 // never gets hit.
1614 throw 'Document body has not initialized. Wait to initialize Firebase until after the document is ready.';
1615 }
1616 // Get the document of the iframe in a browser-specific way.
1617 if (iframe.contentDocument) {
1618 iframe.doc = iframe.contentDocument; // Firefox, Opera, Safari
1619 }
1620 else if (iframe.contentWindow) {
1621 iframe.doc = iframe.contentWindow.document; // Internet Explorer
1622 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1623 }
1624 else if (iframe.document) {
1625 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1626 iframe.doc = iframe.document; //others?
1627 }
1628 return iframe;
1629 };
1630 /**
1631 * Cancel all outstanding queries and remove the frame.
1632 */
1633 FirebaseIFrameScriptHolder.prototype.close = function () {
1634 var _this = this;
1635 //Mark this iframe as dead, so no new requests are sent.
1636 this.alive = false;
1637 if (this.myIFrame) {
1638 //We have to actually remove all of the html inside this iframe before removing it from the
1639 //window, or IE will continue loading and executing the script tags we've already added, which
1640 //can lead to some errors being thrown. Setting innerHTML seems to be the easiest way to do this.
1641 this.myIFrame.doc.body.innerHTML = '';
1642 setTimeout(function () {
1643 if (_this.myIFrame !== null) {
1644 document.body.removeChild(_this.myIFrame);
1645 _this.myIFrame = null;
1646 }
1647 }, Math.floor(0));
1648 }
1649 // Protect from being called recursively.
1650 var onDisconnect = this.onDisconnect;
1651 if (onDisconnect) {
1652 this.onDisconnect = null;
1653 onDisconnect();
1654 }
1655 };
1656 /**
1657 * Actually start the long-polling session by adding the first script tag(s) to the iframe.
1658 * @param id - The ID of this connection
1659 * @param pw - The password for this connection
1660 */
1661 FirebaseIFrameScriptHolder.prototype.startLongPoll = function (id, pw) {
1662 this.myID = id;
1663 this.myPW = pw;
1664 this.alive = true;
1665 //send the initial request. If there are requests queued, make sure that we transmit as many as we are currently able to.
1666 while (this.newRequest_()) { }
1667 };
1668 /**
1669 * This is called any time someone might want a script tag to be added. It adds a script tag when there aren't
1670 * too many outstanding requests and we are still alive.
1671 *
1672 * If there are outstanding packet segments to send, it sends one. If there aren't, it sends a long-poll anyways if
1673 * needed.
1674 */
1675 FirebaseIFrameScriptHolder.prototype.newRequest_ = function () {
1676 // We keep one outstanding request open all the time to receive data, but if we need to send data
1677 // (pendingSegs.length > 0) then we create a new request to send the data. The server will automatically
1678 // close the old request.
1679 if (this.alive &&
1680 this.sendNewPolls &&
1681 this.outstandingRequests.size < (this.pendingSegs.length > 0 ? 2 : 1)) {
1682 //construct our url
1683 this.currentSerial++;
1684 var urlParams = {};
1685 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
1686 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
1687 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = this.currentSerial;
1688 var theURL = this.urlFn(urlParams);
1689 //Now add as much data as we can.
1690 var curDataString = '';
1691 var i = 0;
1692 while (this.pendingSegs.length > 0) {
1693 //first, lets see if the next segment will fit.
1694 var nextSeg = this.pendingSegs[0];
1695 if (nextSeg.d.length +
1696 SEG_HEADER_SIZE +
1697 curDataString.length <=
1698 MAX_URL_DATA_SIZE) {
1699 //great, the segment will fit. Lets append it.
1700 var theSeg = this.pendingSegs.shift();
1701 curDataString =
1702 curDataString +
1703 '&' +
1704 FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM +
1705 i +
1706 '=' +
1707 theSeg.seg +
1708 '&' +
1709 FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET +
1710 i +
1711 '=' +
1712 theSeg.ts +
1713 '&' +
1714 FIREBASE_LONGPOLL_DATA_PARAM +
1715 i +
1716 '=' +
1717 theSeg.d;
1718 i++;
1719 }
1720 else {
1721 break;
1722 }
1723 }
1724 theURL = theURL + curDataString;
1725 this.addLongPollTag_(theURL, this.currentSerial);
1726 return true;
1727 }
1728 else {
1729 return false;
1730 }
1731 };
1732 /**
1733 * Queue a packet for transmission to the server.
1734 * @param segnum - A sequential id for this packet segment used for reassembly
1735 * @param totalsegs - The total number of segments in this packet
1736 * @param data - The data for this segment.
1737 */
1738 FirebaseIFrameScriptHolder.prototype.enqueueSegment = function (segnum, totalsegs, data) {
1739 //add this to the queue of segments to send.
1740 this.pendingSegs.push({ seg: segnum, ts: totalsegs, d: data });
1741 //send the data immediately if there isn't already data being transmitted, unless
1742 //startLongPoll hasn't been called yet.
1743 if (this.alive) {
1744 this.newRequest_();
1745 }
1746 };
1747 /**
1748 * Add a script tag for a regular long-poll request.
1749 * @param url - The URL of the script tag.
1750 * @param serial - The serial number of the request.
1751 */
1752 FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function (url, serial) {
1753 var _this = this;
1754 //remember that we sent this request.
1755 this.outstandingRequests.add(serial);
1756 var doNewRequest = function () {
1757 _this.outstandingRequests.delete(serial);
1758 _this.newRequest_();
1759 };
1760 // If this request doesn't return on its own accord (by the server sending us some data), we'll
1761 // create a new one after the KEEPALIVE interval to make sure we always keep a fresh request open.
1762 var keepaliveTimeout = setTimeout(doNewRequest, Math.floor(KEEPALIVE_REQUEST_INTERVAL));
1763 var readyStateCB = function () {
1764 // Request completed. Cancel the keepalive.
1765 clearTimeout(keepaliveTimeout);
1766 // Trigger a new request so we can continue receiving data.
1767 doNewRequest();
1768 };
1769 this.addTag(url, readyStateCB);
1770 };
1771 /**
1772 * Add an arbitrary script tag to the iframe.
1773 * @param url - The URL for the script tag source.
1774 * @param loadCB - A callback to be triggered once the script has loaded.
1775 */
1776 FirebaseIFrameScriptHolder.prototype.addTag = function (url, loadCB) {
1777 var _this = this;
1778 if (util.isNodeSdk()) {
1779 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1780 this.doNodeLongPoll(url, loadCB);
1781 }
1782 else {
1783 setTimeout(function () {
1784 try {
1785 // if we're already closed, don't add this poll
1786 if (!_this.sendNewPolls) {
1787 return;
1788 }
1789 var newScript_1 = _this.myIFrame.doc.createElement('script');
1790 newScript_1.type = 'text/javascript';
1791 newScript_1.async = true;
1792 newScript_1.src = url;
1793 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1794 newScript_1.onload = newScript_1.onreadystatechange = function () {
1795 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1796 var rstate = newScript_1.readyState;
1797 if (!rstate || rstate === 'loaded' || rstate === 'complete') {
1798 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1799 newScript_1.onload = newScript_1.onreadystatechange = null;
1800 if (newScript_1.parentNode) {
1801 newScript_1.parentNode.removeChild(newScript_1);
1802 }
1803 loadCB();
1804 }
1805 };
1806 newScript_1.onerror = function () {
1807 log('Long-poll script failed to load: ' + url);
1808 _this.sendNewPolls = false;
1809 _this.close();
1810 };
1811 _this.myIFrame.doc.body.appendChild(newScript_1);
1812 }
1813 catch (e) {
1814 // TODO: we should make this error visible somehow
1815 }
1816 }, Math.floor(1));
1817 }
1818 };
1819 return FirebaseIFrameScriptHolder;
1820}());
1821
1822/**
1823 * @license
1824 * Copyright 2017 Google LLC
1825 *
1826 * Licensed under the Apache License, Version 2.0 (the "License");
1827 * you may not use this file except in compliance with the License.
1828 * You may obtain a copy of the License at
1829 *
1830 * http://www.apache.org/licenses/LICENSE-2.0
1831 *
1832 * Unless required by applicable law or agreed to in writing, software
1833 * distributed under the License is distributed on an "AS IS" BASIS,
1834 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1835 * See the License for the specific language governing permissions and
1836 * limitations under the License.
1837 */
1838var WEBSOCKET_MAX_FRAME_SIZE = 16384;
1839var WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
1840var WebSocketImpl = null;
1841if (typeof MozWebSocket !== 'undefined') {
1842 WebSocketImpl = MozWebSocket;
1843}
1844else if (typeof WebSocket !== 'undefined') {
1845 WebSocketImpl = WebSocket;
1846}
1847function setWebSocketImpl(impl) {
1848 WebSocketImpl = impl;
1849}
1850/**
1851 * Create a new websocket connection with the given callbacks.
1852 */
1853var WebSocketConnection = /** @class */ (function () {
1854 /**
1855 * @param connId identifier for this transport
1856 * @param repoInfo The info for the websocket endpoint.
1857 * @param applicationId The Firebase App ID for this project.
1858 * @param appCheckToken The App Check Token for this client.
1859 * @param authToken The Auth Token for this client.
1860 * @param transportSessionId Optional transportSessionId if this is connecting
1861 * to an existing transport session
1862 * @param lastSessionId Optional lastSessionId if there was a previous
1863 * connection
1864 */
1865 function WebSocketConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1866 this.connId = connId;
1867 this.applicationId = applicationId;
1868 this.appCheckToken = appCheckToken;
1869 this.authToken = authToken;
1870 this.keepaliveTimer = null;
1871 this.frames = null;
1872 this.totalFrames = 0;
1873 this.bytesSent = 0;
1874 this.bytesReceived = 0;
1875 this.log_ = logWrapper(this.connId);
1876 this.stats_ = statsManagerGetCollection(repoInfo);
1877 this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId, appCheckToken);
1878 this.nodeAdmin = repoInfo.nodeAdmin;
1879 }
1880 /**
1881 * @param repoInfo - The info for the websocket endpoint.
1882 * @param transportSessionId - Optional transportSessionId if this is connecting to an existing transport
1883 * session
1884 * @param lastSessionId - Optional lastSessionId if there was a previous connection
1885 * @returns connection url
1886 */
1887 WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId, appCheckToken) {
1888 var urlParams = {};
1889 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1890 if (!util.isNodeSdk() &&
1891 typeof location !== 'undefined' &&
1892 location.hostname &&
1893 FORGE_DOMAIN_RE.test(location.hostname)) {
1894 urlParams[REFERER_PARAM] = FORGE_REF;
1895 }
1896 if (transportSessionId) {
1897 urlParams[TRANSPORT_SESSION_PARAM] = transportSessionId;
1898 }
1899 if (lastSessionId) {
1900 urlParams[LAST_SESSION_PARAM] = lastSessionId;
1901 }
1902 if (appCheckToken) {
1903 urlParams[APP_CHECK_TOKEN_PARAM] = appCheckToken;
1904 }
1905 return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
1906 };
1907 /**
1908 * @param onMessage - Callback when messages arrive
1909 * @param onDisconnect - Callback with connection lost.
1910 */
1911 WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
1912 var _this = this;
1913 this.onDisconnect = onDisconnect;
1914 this.onMessage = onMessage;
1915 this.log_('Websocket connecting to ' + this.connURL);
1916 this.everConnected_ = false;
1917 // Assume failure until proven otherwise.
1918 PersistentStorage.set('previous_websocket_failure', true);
1919 try {
1920 if (util.isNodeSdk()) {
1921 var device = this.nodeAdmin ? 'AdminNode' : 'Node';
1922 // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
1923 var options = {
1924 headers: {
1925 'User-Agent': "Firebase/" + PROTOCOL_VERSION + "/" + SDK_VERSION + "/" + process.platform + "/" + device,
1926 'X-Firebase-GMPID': this.applicationId || ''
1927 }
1928 };
1929 // If using Node with admin creds, AppCheck-related checks are unnecessary.
1930 // It will send the authorization token.
1931 if (this.nodeAdmin) {
1932 options.headers['Authorization'] = this.authToken || '';
1933 }
1934 else {
1935 // If using Node without admin creds (which includes all uses of the
1936 // client-side Node SDK), it will send an AppCheck token if available.
1937 // Any other auth credentials will eventually be sent after the connection
1938 // is established, but aren't needed here as they don't effect the initial
1939 // request to establish a connection.
1940 options.headers['X-Firebase-AppCheck'] = this.appCheckToken || '';
1941 }
1942 // Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
1943 var env = process['env'];
1944 var proxy = this.connURL.indexOf('wss://') === 0
1945 ? env['HTTPS_PROXY'] || env['https_proxy']
1946 : env['HTTP_PROXY'] || env['http_proxy'];
1947 if (proxy) {
1948 options['proxy'] = { origin: proxy };
1949 }
1950 this.mySock = new WebSocketImpl(this.connURL, [], options);
1951 }
1952 else {
1953 var options = {
1954 headers: {
1955 'X-Firebase-GMPID': this.applicationId || '',
1956 'X-Firebase-AppCheck': this.appCheckToken || ''
1957 }
1958 };
1959 this.mySock = new WebSocketImpl(this.connURL, [], options);
1960 }
1961 }
1962 catch (e) {
1963 this.log_('Error instantiating WebSocket.');
1964 var error = e.message || e.data;
1965 if (error) {
1966 this.log_(error);
1967 }
1968 this.onClosed_();
1969 return;
1970 }
1971 this.mySock.onopen = function () {
1972 _this.log_('Websocket connected.');
1973 _this.everConnected_ = true;
1974 };
1975 this.mySock.onclose = function () {
1976 _this.log_('Websocket connection was disconnected.');
1977 _this.mySock = null;
1978 _this.onClosed_();
1979 };
1980 this.mySock.onmessage = function (m) {
1981 _this.handleIncomingFrame(m);
1982 };
1983 this.mySock.onerror = function (e) {
1984 _this.log_('WebSocket error. Closing connection.');
1985 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1986 var error = e.message || e.data;
1987 if (error) {
1988 _this.log_(error);
1989 }
1990 _this.onClosed_();
1991 };
1992 };
1993 /**
1994 * No-op for websockets, we don't need to do anything once the connection is confirmed as open
1995 */
1996 WebSocketConnection.prototype.start = function () { };
1997 WebSocketConnection.forceDisallow = function () {
1998 WebSocketConnection.forceDisallow_ = true;
1999 };
2000 WebSocketConnection.isAvailable = function () {
2001 var isOldAndroid = false;
2002 if (typeof navigator !== 'undefined' && navigator.userAgent) {
2003 var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
2004 var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
2005 if (oldAndroidMatch && oldAndroidMatch.length > 1) {
2006 if (parseFloat(oldAndroidMatch[1]) < 4.4) {
2007 isOldAndroid = true;
2008 }
2009 }
2010 }
2011 return (!isOldAndroid &&
2012 WebSocketImpl !== null &&
2013 !WebSocketConnection.forceDisallow_);
2014 };
2015 /**
2016 * Returns true if we previously failed to connect with this transport.
2017 */
2018 WebSocketConnection.previouslyFailed = function () {
2019 // If our persistent storage is actually only in-memory storage,
2020 // we default to assuming that it previously failed to be safe.
2021 return (PersistentStorage.isInMemoryStorage ||
2022 PersistentStorage.get('previous_websocket_failure') === true);
2023 };
2024 WebSocketConnection.prototype.markConnectionHealthy = function () {
2025 PersistentStorage.remove('previous_websocket_failure');
2026 };
2027 WebSocketConnection.prototype.appendFrame_ = function (data) {
2028 this.frames.push(data);
2029 if (this.frames.length === this.totalFrames) {
2030 var fullMess = this.frames.join('');
2031 this.frames = null;
2032 var jsonMess = util.jsonEval(fullMess);
2033 //handle the message
2034 this.onMessage(jsonMess);
2035 }
2036 };
2037 /**
2038 * @param frameCount - The number of frames we are expecting from the server
2039 */
2040 WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
2041 this.totalFrames = frameCount;
2042 this.frames = [];
2043 };
2044 /**
2045 * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
2046 * @returns Any remaining data to be process, or null if there is none
2047 */
2048 WebSocketConnection.prototype.extractFrameCount_ = function (data) {
2049 util.assert(this.frames === null, 'We already have a frame buffer');
2050 // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
2051 // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
2052 if (data.length <= 6) {
2053 var frameCount = Number(data);
2054 if (!isNaN(frameCount)) {
2055 this.handleNewFrameCount_(frameCount);
2056 return null;
2057 }
2058 }
2059 this.handleNewFrameCount_(1);
2060 return data;
2061 };
2062 /**
2063 * Process a websocket frame that has arrived from the server.
2064 * @param mess - The frame data
2065 */
2066 WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
2067 if (this.mySock === null) {
2068 return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
2069 }
2070 var data = mess['data'];
2071 this.bytesReceived += data.length;
2072 this.stats_.incrementCounter('bytes_received', data.length);
2073 this.resetKeepAlive();
2074 if (this.frames !== null) {
2075 // we're buffering
2076 this.appendFrame_(data);
2077 }
2078 else {
2079 // try to parse out a frame count, otherwise, assume 1 and process it
2080 var remainingData = this.extractFrameCount_(data);
2081 if (remainingData !== null) {
2082 this.appendFrame_(remainingData);
2083 }
2084 }
2085 };
2086 /**
2087 * Send a message to the server
2088 * @param data - The JSON object to transmit
2089 */
2090 WebSocketConnection.prototype.send = function (data) {
2091 this.resetKeepAlive();
2092 var dataStr = util.stringify(data);
2093 this.bytesSent += dataStr.length;
2094 this.stats_.incrementCounter('bytes_sent', dataStr.length);
2095 //We can only fit a certain amount in each websocket frame, so we need to split this request
2096 //up into multiple pieces if it doesn't fit in one request.
2097 var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
2098 //Send the length header
2099 if (dataSegs.length > 1) {
2100 this.sendString_(String(dataSegs.length));
2101 }
2102 //Send the actual data in segments.
2103 for (var i = 0; i < dataSegs.length; i++) {
2104 this.sendString_(dataSegs[i]);
2105 }
2106 };
2107 WebSocketConnection.prototype.shutdown_ = function () {
2108 this.isClosed_ = true;
2109 if (this.keepaliveTimer) {
2110 clearInterval(this.keepaliveTimer);
2111 this.keepaliveTimer = null;
2112 }
2113 if (this.mySock) {
2114 this.mySock.close();
2115 this.mySock = null;
2116 }
2117 };
2118 WebSocketConnection.prototype.onClosed_ = function () {
2119 if (!this.isClosed_) {
2120 this.log_('WebSocket is closing itself');
2121 this.shutdown_();
2122 // since this is an internal close, trigger the close listener
2123 if (this.onDisconnect) {
2124 this.onDisconnect(this.everConnected_);
2125 this.onDisconnect = null;
2126 }
2127 }
2128 };
2129 /**
2130 * External-facing close handler.
2131 * Close the websocket and kill the connection.
2132 */
2133 WebSocketConnection.prototype.close = function () {
2134 if (!this.isClosed_) {
2135 this.log_('WebSocket is being closed');
2136 this.shutdown_();
2137 }
2138 };
2139 /**
2140 * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
2141 * the last activity.
2142 */
2143 WebSocketConnection.prototype.resetKeepAlive = function () {
2144 var _this = this;
2145 clearInterval(this.keepaliveTimer);
2146 this.keepaliveTimer = setInterval(function () {
2147 //If there has been no websocket activity for a while, send a no-op
2148 if (_this.mySock) {
2149 _this.sendString_('0');
2150 }
2151 _this.resetKeepAlive();
2152 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2153 }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
2154 };
2155 /**
2156 * Send a string over the websocket.
2157 *
2158 * @param str - String to send.
2159 */
2160 WebSocketConnection.prototype.sendString_ = function (str) {
2161 // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
2162 // calls for some unknown reason. We treat these as an error and disconnect.
2163 // See https://app.asana.com/0/58926111402292/68021340250410
2164 try {
2165 this.mySock.send(str);
2166 }
2167 catch (e) {
2168 this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
2169 setTimeout(this.onClosed_.bind(this), 0);
2170 }
2171 };
2172 /**
2173 * Number of response before we consider the connection "healthy."
2174 */
2175 WebSocketConnection.responsesRequiredToBeHealthy = 2;
2176 /**
2177 * Time to wait for the connection te become healthy before giving up.
2178 */
2179 WebSocketConnection.healthyTimeout = 30000;
2180 return WebSocketConnection;
2181}());
2182
2183/**
2184 * @license
2185 * Copyright 2017 Google LLC
2186 *
2187 * Licensed under the Apache License, Version 2.0 (the "License");
2188 * you may not use this file except in compliance with the License.
2189 * You may obtain a copy of the License at
2190 *
2191 * http://www.apache.org/licenses/LICENSE-2.0
2192 *
2193 * Unless required by applicable law or agreed to in writing, software
2194 * distributed under the License is distributed on an "AS IS" BASIS,
2195 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2196 * See the License for the specific language governing permissions and
2197 * limitations under the License.
2198 */
2199/**
2200 * Currently simplistic, this class manages what transport a Connection should use at various stages of its
2201 * lifecycle.
2202 *
2203 * It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
2204 * they are available.
2205 */
2206var TransportManager = /** @class */ (function () {
2207 /**
2208 * @param repoInfo - Metadata around the namespace we're connecting to
2209 */
2210 function TransportManager(repoInfo) {
2211 this.initTransports_(repoInfo);
2212 }
2213 Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
2214 get: function () {
2215 return [BrowserPollConnection, WebSocketConnection];
2216 },
2217 enumerable: false,
2218 configurable: true
2219 });
2220 TransportManager.prototype.initTransports_ = function (repoInfo) {
2221 var e_1, _a;
2222 var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
2223 var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
2224 if (repoInfo.webSocketOnly) {
2225 if (!isWebSocketsAvailable) {
2226 warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
2227 }
2228 isSkipPollConnection = true;
2229 }
2230 if (isSkipPollConnection) {
2231 this.transports_ = [WebSocketConnection];
2232 }
2233 else {
2234 var transports = (this.transports_ = []);
2235 try {
2236 for (var _b = tslib.__values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
2237 var transport = _c.value;
2238 if (transport && transport['isAvailable']()) {
2239 transports.push(transport);
2240 }
2241 }
2242 }
2243 catch (e_1_1) { e_1 = { error: e_1_1 }; }
2244 finally {
2245 try {
2246 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
2247 }
2248 finally { if (e_1) throw e_1.error; }
2249 }
2250 }
2251 };
2252 /**
2253 * @returns The constructor for the initial transport to use
2254 */
2255 TransportManager.prototype.initialTransport = function () {
2256 if (this.transports_.length > 0) {
2257 return this.transports_[0];
2258 }
2259 else {
2260 throw new Error('No transports available');
2261 }
2262 };
2263 /**
2264 * @returns The constructor for the next transport, or null
2265 */
2266 TransportManager.prototype.upgradeTransport = function () {
2267 if (this.transports_.length > 1) {
2268 return this.transports_[1];
2269 }
2270 else {
2271 return null;
2272 }
2273 };
2274 return TransportManager;
2275}());
2276
2277/**
2278 * @license
2279 * Copyright 2017 Google LLC
2280 *
2281 * Licensed under the Apache License, Version 2.0 (the "License");
2282 * you may not use this file except in compliance with the License.
2283 * You may obtain a copy of the License at
2284 *
2285 * http://www.apache.org/licenses/LICENSE-2.0
2286 *
2287 * Unless required by applicable law or agreed to in writing, software
2288 * distributed under the License is distributed on an "AS IS" BASIS,
2289 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2290 * See the License for the specific language governing permissions and
2291 * limitations under the License.
2292 */
2293// Abort upgrade attempt if it takes longer than 60s.
2294var UPGRADE_TIMEOUT = 60000;
2295// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
2296// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
2297var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
2298// 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)
2299// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
2300// but we've sent/received enough bytes, we don't cancel the connection.
2301var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
2302var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
2303var MESSAGE_TYPE = 't';
2304var MESSAGE_DATA = 'd';
2305var CONTROL_SHUTDOWN = 's';
2306var CONTROL_RESET = 'r';
2307var CONTROL_ERROR = 'e';
2308var CONTROL_PONG = 'o';
2309var SWITCH_ACK = 'a';
2310var END_TRANSMISSION = 'n';
2311var PING = 'p';
2312var SERVER_HELLO = 'h';
2313/**
2314 * Creates a new real-time connection to the server using whichever method works
2315 * best in the current browser.
2316 */
2317var Connection = /** @class */ (function () {
2318 /**
2319 * @param id - an id for this connection
2320 * @param repoInfo_ - the info for the endpoint to connect to
2321 * @param applicationId_ - the Firebase App ID for this project
2322 * @param appCheckToken_ - The App Check Token for this device.
2323 * @param authToken_ - The auth token for this session.
2324 * @param onMessage_ - the callback to be triggered when a server-push message arrives
2325 * @param onReady_ - the callback to be triggered when this connection is ready to send messages.
2326 * @param onDisconnect_ - the callback to be triggered when a connection was lost
2327 * @param onKill_ - the callback to be triggered when this connection has permanently shut down.
2328 * @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
2329 */
2330 function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
2331 this.id = id;
2332 this.repoInfo_ = repoInfo_;
2333 this.applicationId_ = applicationId_;
2334 this.appCheckToken_ = appCheckToken_;
2335 this.authToken_ = authToken_;
2336 this.onMessage_ = onMessage_;
2337 this.onReady_ = onReady_;
2338 this.onDisconnect_ = onDisconnect_;
2339 this.onKill_ = onKill_;
2340 this.lastSessionId = lastSessionId;
2341 this.connectionCount = 0;
2342 this.pendingDataMessages = [];
2343 this.state_ = 0 /* CONNECTING */;
2344 this.log_ = logWrapper('c:' + this.id + ':');
2345 this.transportManager_ = new TransportManager(repoInfo_);
2346 this.log_('Connection created');
2347 this.start_();
2348 }
2349 /**
2350 * Starts a connection attempt
2351 */
2352 Connection.prototype.start_ = function () {
2353 var _this = this;
2354 var conn = this.transportManager_.initialTransport();
2355 this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.lastSessionId);
2356 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2357 // can consider the transport healthy.
2358 this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
2359 var onMessageReceived = this.connReceiver_(this.conn_);
2360 var onConnectionLost = this.disconnReceiver_(this.conn_);
2361 this.tx_ = this.conn_;
2362 this.rx_ = this.conn_;
2363 this.secondaryConn_ = null;
2364 this.isHealthy_ = false;
2365 /*
2366 * Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
2367 * This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
2368 * Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
2369 * still have the context of your originating frame.
2370 */
2371 setTimeout(function () {
2372 // this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
2373 _this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
2374 }, Math.floor(0));
2375 var healthyTimeoutMS = conn['healthyTimeout'] || 0;
2376 if (healthyTimeoutMS > 0) {
2377 this.healthyTimeout_ = setTimeoutNonBlocking(function () {
2378 _this.healthyTimeout_ = null;
2379 if (!_this.isHealthy_) {
2380 if (_this.conn_ &&
2381 _this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
2382 _this.log_('Connection exceeded healthy timeout but has received ' +
2383 _this.conn_.bytesReceived +
2384 ' bytes. Marking connection healthy.');
2385 _this.isHealthy_ = true;
2386 _this.conn_.markConnectionHealthy();
2387 }
2388 else if (_this.conn_ &&
2389 _this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
2390 _this.log_('Connection exceeded healthy timeout but has sent ' +
2391 _this.conn_.bytesSent +
2392 ' bytes. Leaving connection alive.');
2393 // NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
2394 // the server.
2395 }
2396 else {
2397 _this.log_('Closing unhealthy connection after timeout.');
2398 _this.close();
2399 }
2400 }
2401 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2402 }, Math.floor(healthyTimeoutMS));
2403 }
2404 };
2405 Connection.prototype.nextTransportId_ = function () {
2406 return 'c:' + this.id + ':' + this.connectionCount++;
2407 };
2408 Connection.prototype.disconnReceiver_ = function (conn) {
2409 var _this = this;
2410 return function (everConnected) {
2411 if (conn === _this.conn_) {
2412 _this.onConnectionLost_(everConnected);
2413 }
2414 else if (conn === _this.secondaryConn_) {
2415 _this.log_('Secondary connection lost.');
2416 _this.onSecondaryConnectionLost_();
2417 }
2418 else {
2419 _this.log_('closing an old connection');
2420 }
2421 };
2422 };
2423 Connection.prototype.connReceiver_ = function (conn) {
2424 var _this = this;
2425 return function (message) {
2426 if (_this.state_ !== 2 /* DISCONNECTED */) {
2427 if (conn === _this.rx_) {
2428 _this.onPrimaryMessageReceived_(message);
2429 }
2430 else if (conn === _this.secondaryConn_) {
2431 _this.onSecondaryMessageReceived_(message);
2432 }
2433 else {
2434 _this.log_('message on old connection');
2435 }
2436 }
2437 };
2438 };
2439 /**
2440 * @param dataMsg - An arbitrary data message to be sent to the server
2441 */
2442 Connection.prototype.sendRequest = function (dataMsg) {
2443 // wrap in a data message envelope and send it on
2444 var msg = { t: 'd', d: dataMsg };
2445 this.sendData_(msg);
2446 };
2447 Connection.prototype.tryCleanupConnection = function () {
2448 if (this.tx_ === this.secondaryConn_ && this.rx_ === this.secondaryConn_) {
2449 this.log_('cleaning up and promoting a connection: ' + this.secondaryConn_.connId);
2450 this.conn_ = this.secondaryConn_;
2451 this.secondaryConn_ = null;
2452 // the server will shutdown the old connection
2453 }
2454 };
2455 Connection.prototype.onSecondaryControl_ = function (controlData) {
2456 if (MESSAGE_TYPE in controlData) {
2457 var cmd = controlData[MESSAGE_TYPE];
2458 if (cmd === SWITCH_ACK) {
2459 this.upgradeIfSecondaryHealthy_();
2460 }
2461 else if (cmd === CONTROL_RESET) {
2462 // Most likely the session wasn't valid. Abandon the switch attempt
2463 this.log_('Got a reset on secondary, closing it');
2464 this.secondaryConn_.close();
2465 // If we were already using this connection for something, than we need to fully close
2466 if (this.tx_ === this.secondaryConn_ ||
2467 this.rx_ === this.secondaryConn_) {
2468 this.close();
2469 }
2470 }
2471 else if (cmd === CONTROL_PONG) {
2472 this.log_('got pong on secondary.');
2473 this.secondaryResponsesRequired_--;
2474 this.upgradeIfSecondaryHealthy_();
2475 }
2476 }
2477 };
2478 Connection.prototype.onSecondaryMessageReceived_ = function (parsedData) {
2479 var layer = requireKey('t', parsedData);
2480 var data = requireKey('d', parsedData);
2481 if (layer === 'c') {
2482 this.onSecondaryControl_(data);
2483 }
2484 else if (layer === 'd') {
2485 // got a data message, but we're still second connection. Need to buffer it up
2486 this.pendingDataMessages.push(data);
2487 }
2488 else {
2489 throw new Error('Unknown protocol layer: ' + layer);
2490 }
2491 };
2492 Connection.prototype.upgradeIfSecondaryHealthy_ = function () {
2493 if (this.secondaryResponsesRequired_ <= 0) {
2494 this.log_('Secondary connection is healthy.');
2495 this.isHealthy_ = true;
2496 this.secondaryConn_.markConnectionHealthy();
2497 this.proceedWithUpgrade_();
2498 }
2499 else {
2500 // Send a ping to make sure the connection is healthy.
2501 this.log_('sending ping on secondary.');
2502 this.secondaryConn_.send({ t: 'c', d: { t: PING, d: {} } });
2503 }
2504 };
2505 Connection.prototype.proceedWithUpgrade_ = function () {
2506 // tell this connection to consider itself open
2507 this.secondaryConn_.start();
2508 // send ack
2509 this.log_('sending client ack on secondary');
2510 this.secondaryConn_.send({ t: 'c', d: { t: SWITCH_ACK, d: {} } });
2511 // send end packet on primary transport, switch to sending on this one
2512 // can receive on this one, buffer responses until end received on primary transport
2513 this.log_('Ending transmission on primary');
2514 this.conn_.send({ t: 'c', d: { t: END_TRANSMISSION, d: {} } });
2515 this.tx_ = this.secondaryConn_;
2516 this.tryCleanupConnection();
2517 };
2518 Connection.prototype.onPrimaryMessageReceived_ = function (parsedData) {
2519 // Must refer to parsedData properties in quotes, so closure doesn't touch them.
2520 var layer = requireKey('t', parsedData);
2521 var data = requireKey('d', parsedData);
2522 if (layer === 'c') {
2523 this.onControl_(data);
2524 }
2525 else if (layer === 'd') {
2526 this.onDataMessage_(data);
2527 }
2528 };
2529 Connection.prototype.onDataMessage_ = function (message) {
2530 this.onPrimaryResponse_();
2531 // We don't do anything with data messages, just kick them up a level
2532 this.onMessage_(message);
2533 };
2534 Connection.prototype.onPrimaryResponse_ = function () {
2535 if (!this.isHealthy_) {
2536 this.primaryResponsesRequired_--;
2537 if (this.primaryResponsesRequired_ <= 0) {
2538 this.log_('Primary connection is healthy.');
2539 this.isHealthy_ = true;
2540 this.conn_.markConnectionHealthy();
2541 }
2542 }
2543 };
2544 Connection.prototype.onControl_ = function (controlData) {
2545 var cmd = requireKey(MESSAGE_TYPE, controlData);
2546 if (MESSAGE_DATA in controlData) {
2547 var payload = controlData[MESSAGE_DATA];
2548 if (cmd === SERVER_HELLO) {
2549 this.onHandshake_(payload);
2550 }
2551 else if (cmd === END_TRANSMISSION) {
2552 this.log_('recvd end transmission on primary');
2553 this.rx_ = this.secondaryConn_;
2554 for (var i = 0; i < this.pendingDataMessages.length; ++i) {
2555 this.onDataMessage_(this.pendingDataMessages[i]);
2556 }
2557 this.pendingDataMessages = [];
2558 this.tryCleanupConnection();
2559 }
2560 else if (cmd === CONTROL_SHUTDOWN) {
2561 // This was previously the 'onKill' callback passed to the lower-level connection
2562 // payload in this case is the reason for the shutdown. Generally a human-readable error
2563 this.onConnectionShutdown_(payload);
2564 }
2565 else if (cmd === CONTROL_RESET) {
2566 // payload in this case is the host we should contact
2567 this.onReset_(payload);
2568 }
2569 else if (cmd === CONTROL_ERROR) {
2570 error('Server Error: ' + payload);
2571 }
2572 else if (cmd === CONTROL_PONG) {
2573 this.log_('got pong on primary.');
2574 this.onPrimaryResponse_();
2575 this.sendPingOnPrimaryIfNecessary_();
2576 }
2577 else {
2578 error('Unknown control packet command: ' + cmd);
2579 }
2580 }
2581 };
2582 /**
2583 * @param handshake - The handshake data returned from the server
2584 */
2585 Connection.prototype.onHandshake_ = function (handshake) {
2586 var timestamp = handshake.ts;
2587 var version = handshake.v;
2588 var host = handshake.h;
2589 this.sessionId = handshake.s;
2590 this.repoInfo_.host = host;
2591 // if we've already closed the connection, then don't bother trying to progress further
2592 if (this.state_ === 0 /* CONNECTING */) {
2593 this.conn_.start();
2594 this.onConnectionEstablished_(this.conn_, timestamp);
2595 if (PROTOCOL_VERSION !== version) {
2596 warn('Protocol version mismatch detected');
2597 }
2598 // TODO: do we want to upgrade? when? maybe a delay?
2599 this.tryStartUpgrade_();
2600 }
2601 };
2602 Connection.prototype.tryStartUpgrade_ = function () {
2603 var conn = this.transportManager_.upgradeTransport();
2604 if (conn) {
2605 this.startUpgrade_(conn);
2606 }
2607 };
2608 Connection.prototype.startUpgrade_ = function (conn) {
2609 var _this = this;
2610 this.secondaryConn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, this.sessionId);
2611 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2612 // can consider the transport healthy.
2613 this.secondaryResponsesRequired_ =
2614 conn['responsesRequiredToBeHealthy'] || 0;
2615 var onMessage = this.connReceiver_(this.secondaryConn_);
2616 var onDisconnect = this.disconnReceiver_(this.secondaryConn_);
2617 this.secondaryConn_.open(onMessage, onDisconnect);
2618 // If we haven't successfully upgraded after UPGRADE_TIMEOUT, give up and kill the secondary.
2619 setTimeoutNonBlocking(function () {
2620 if (_this.secondaryConn_) {
2621 _this.log_('Timed out trying to upgrade.');
2622 _this.secondaryConn_.close();
2623 }
2624 }, Math.floor(UPGRADE_TIMEOUT));
2625 };
2626 Connection.prototype.onReset_ = function (host) {
2627 this.log_('Reset packet received. New host: ' + host);
2628 this.repoInfo_.host = host;
2629 // TODO: if we're already "connected", we need to trigger a disconnect at the next layer up.
2630 // We don't currently support resets after the connection has already been established
2631 if (this.state_ === 1 /* CONNECTED */) {
2632 this.close();
2633 }
2634 else {
2635 // Close whatever connections we have open and start again.
2636 this.closeConnections_();
2637 this.start_();
2638 }
2639 };
2640 Connection.prototype.onConnectionEstablished_ = function (conn, timestamp) {
2641 var _this = this;
2642 this.log_('Realtime connection established.');
2643 this.conn_ = conn;
2644 this.state_ = 1 /* CONNECTED */;
2645 if (this.onReady_) {
2646 this.onReady_(timestamp, this.sessionId);
2647 this.onReady_ = null;
2648 }
2649 // If after 5 seconds we haven't sent enough requests to the server to get the connection healthy,
2650 // send some pings.
2651 if (this.primaryResponsesRequired_ === 0) {
2652 this.log_('Primary connection is healthy.');
2653 this.isHealthy_ = true;
2654 }
2655 else {
2656 setTimeoutNonBlocking(function () {
2657 _this.sendPingOnPrimaryIfNecessary_();
2658 }, Math.floor(DELAY_BEFORE_SENDING_EXTRA_REQUESTS));
2659 }
2660 };
2661 Connection.prototype.sendPingOnPrimaryIfNecessary_ = function () {
2662 // If the connection isn't considered healthy yet, we'll send a noop ping packet request.
2663 if (!this.isHealthy_ && this.state_ === 1 /* CONNECTED */) {
2664 this.log_('sending ping on primary.');
2665 this.sendData_({ t: 'c', d: { t: PING, d: {} } });
2666 }
2667 };
2668 Connection.prototype.onSecondaryConnectionLost_ = function () {
2669 var conn = this.secondaryConn_;
2670 this.secondaryConn_ = null;
2671 if (this.tx_ === conn || this.rx_ === conn) {
2672 // we are relying on this connection already in some capacity. Therefore, a failure is real
2673 this.close();
2674 }
2675 };
2676 /**
2677 * @param everConnected - Whether or not the connection ever reached a server. Used to determine if
2678 * we should flush the host cache
2679 */
2680 Connection.prototype.onConnectionLost_ = function (everConnected) {
2681 this.conn_ = null;
2682 // NOTE: IF you're seeing a Firefox error for this line, I think it might be because it's getting
2683 // called on window close and RealtimeState.CONNECTING is no longer defined. Just a guess.
2684 if (!everConnected && this.state_ === 0 /* CONNECTING */) {
2685 this.log_('Realtime connection failed.');
2686 // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
2687 if (this.repoInfo_.isCacheableHost()) {
2688 PersistentStorage.remove('host:' + this.repoInfo_.host);
2689 // reset the internal host to what we would show the user, i.e. <ns>.firebaseio.com
2690 this.repoInfo_.internalHost = this.repoInfo_.host;
2691 }
2692 }
2693 else if (this.state_ === 1 /* CONNECTED */) {
2694 this.log_('Realtime connection lost.');
2695 }
2696 this.close();
2697 };
2698 Connection.prototype.onConnectionShutdown_ = function (reason) {
2699 this.log_('Connection shutdown command received. Shutting down...');
2700 if (this.onKill_) {
2701 this.onKill_(reason);
2702 this.onKill_ = null;
2703 }
2704 // We intentionally don't want to fire onDisconnect (kill is a different case),
2705 // so clear the callback.
2706 this.onDisconnect_ = null;
2707 this.close();
2708 };
2709 Connection.prototype.sendData_ = function (data) {
2710 if (this.state_ !== 1 /* CONNECTED */) {
2711 throw 'Connection is not connected';
2712 }
2713 else {
2714 this.tx_.send(data);
2715 }
2716 };
2717 /**
2718 * Cleans up this connection, calling the appropriate callbacks
2719 */
2720 Connection.prototype.close = function () {
2721 if (this.state_ !== 2 /* DISCONNECTED */) {
2722 this.log_('Closing realtime connection.');
2723 this.state_ = 2 /* DISCONNECTED */;
2724 this.closeConnections_();
2725 if (this.onDisconnect_) {
2726 this.onDisconnect_();
2727 this.onDisconnect_ = null;
2728 }
2729 }
2730 };
2731 Connection.prototype.closeConnections_ = function () {
2732 this.log_('Shutting down all connections');
2733 if (this.conn_) {
2734 this.conn_.close();
2735 this.conn_ = null;
2736 }
2737 if (this.secondaryConn_) {
2738 this.secondaryConn_.close();
2739 this.secondaryConn_ = null;
2740 }
2741 if (this.healthyTimeout_) {
2742 clearTimeout(this.healthyTimeout_);
2743 this.healthyTimeout_ = null;
2744 }
2745 };
2746 return Connection;
2747}());
2748
2749/**
2750 * @license
2751 * Copyright 2017 Google LLC
2752 *
2753 * Licensed under the Apache License, Version 2.0 (the "License");
2754 * you may not use this file except in compliance with the License.
2755 * You may obtain a copy of the License at
2756 *
2757 * http://www.apache.org/licenses/LICENSE-2.0
2758 *
2759 * Unless required by applicable law or agreed to in writing, software
2760 * distributed under the License is distributed on an "AS IS" BASIS,
2761 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2762 * See the License for the specific language governing permissions and
2763 * limitations under the License.
2764 */
2765/**
2766 * Interface defining the set of actions that can be performed against the Firebase server
2767 * (basically corresponds to our wire protocol).
2768 *
2769 * @interface
2770 */
2771var ServerActions = /** @class */ (function () {
2772 function ServerActions() {
2773 }
2774 ServerActions.prototype.put = function (pathString, data, onComplete, hash) { };
2775 ServerActions.prototype.merge = function (pathString, data, onComplete, hash) { };
2776 /**
2777 * Refreshes the auth token for the current connection.
2778 * @param token - The authentication token
2779 */
2780 ServerActions.prototype.refreshAuthToken = function (token) { };
2781 /**
2782 * Refreshes the app check token for the current connection.
2783 * @param token The app check token
2784 */
2785 ServerActions.prototype.refreshAppCheckToken = function (token) { };
2786 ServerActions.prototype.onDisconnectPut = function (pathString, data, onComplete) { };
2787 ServerActions.prototype.onDisconnectMerge = function (pathString, data, onComplete) { };
2788 ServerActions.prototype.onDisconnectCancel = function (pathString, onComplete) { };
2789 ServerActions.prototype.reportStats = function (stats) { };
2790 return ServerActions;
2791}());
2792
2793/**
2794 * @license
2795 * Copyright 2017 Google LLC
2796 *
2797 * Licensed under the Apache License, Version 2.0 (the "License");
2798 * you may not use this file except in compliance with the License.
2799 * You may obtain a copy of the License at
2800 *
2801 * http://www.apache.org/licenses/LICENSE-2.0
2802 *
2803 * Unless required by applicable law or agreed to in writing, software
2804 * distributed under the License is distributed on an "AS IS" BASIS,
2805 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2806 * See the License for the specific language governing permissions and
2807 * limitations under the License.
2808 */
2809/**
2810 * Base class to be used if you want to emit events. Call the constructor with
2811 * the set of allowed event names.
2812 */
2813var EventEmitter = /** @class */ (function () {
2814 function EventEmitter(allowedEvents_) {
2815 this.allowedEvents_ = allowedEvents_;
2816 this.listeners_ = {};
2817 util.assert(Array.isArray(allowedEvents_) && allowedEvents_.length > 0, 'Requires a non-empty array');
2818 }
2819 /**
2820 * To be called by derived classes to trigger events.
2821 */
2822 EventEmitter.prototype.trigger = function (eventType) {
2823 var varArgs = [];
2824 for (var _i = 1; _i < arguments.length; _i++) {
2825 varArgs[_i - 1] = arguments[_i];
2826 }
2827 if (Array.isArray(this.listeners_[eventType])) {
2828 // Clone the list, since callbacks could add/remove listeners.
2829 var listeners = tslib.__spreadArray([], tslib.__read(this.listeners_[eventType]));
2830 for (var i = 0; i < listeners.length; i++) {
2831 listeners[i].callback.apply(listeners[i].context, varArgs);
2832 }
2833 }
2834 };
2835 EventEmitter.prototype.on = function (eventType, callback, context) {
2836 this.validateEventType_(eventType);
2837 this.listeners_[eventType] = this.listeners_[eventType] || [];
2838 this.listeners_[eventType].push({ callback: callback, context: context });
2839 var eventData = this.getInitialEvent(eventType);
2840 if (eventData) {
2841 callback.apply(context, eventData);
2842 }
2843 };
2844 EventEmitter.prototype.off = function (eventType, callback, context) {
2845 this.validateEventType_(eventType);
2846 var listeners = this.listeners_[eventType] || [];
2847 for (var i = 0; i < listeners.length; i++) {
2848 if (listeners[i].callback === callback &&
2849 (!context || context === listeners[i].context)) {
2850 listeners.splice(i, 1);
2851 return;
2852 }
2853 }
2854 };
2855 EventEmitter.prototype.validateEventType_ = function (eventType) {
2856 util.assert(this.allowedEvents_.find(function (et) {
2857 return et === eventType;
2858 }), 'Unknown event: ' + eventType);
2859 };
2860 return EventEmitter;
2861}());
2862
2863/**
2864 * @license
2865 * Copyright 2017 Google LLC
2866 *
2867 * Licensed under the Apache License, Version 2.0 (the "License");
2868 * you may not use this file except in compliance with the License.
2869 * You may obtain a copy of the License at
2870 *
2871 * http://www.apache.org/licenses/LICENSE-2.0
2872 *
2873 * Unless required by applicable law or agreed to in writing, software
2874 * distributed under the License is distributed on an "AS IS" BASIS,
2875 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2876 * See the License for the specific language governing permissions and
2877 * limitations under the License.
2878 */
2879/**
2880 * Monitors online state (as reported by window.online/offline events).
2881 *
2882 * The expectation is that this could have many false positives (thinks we are online
2883 * when we're not), but no false negatives. So we can safely use it to determine when
2884 * we definitely cannot reach the internet.
2885 */
2886var OnlineMonitor = /** @class */ (function (_super) {
2887 tslib.__extends(OnlineMonitor, _super);
2888 function OnlineMonitor() {
2889 var _this = _super.call(this, ['online']) || this;
2890 _this.online_ = true;
2891 // We've had repeated complaints that Cordova apps can get stuck "offline", e.g.
2892 // https://forum.ionicframework.com/t/firebase-connection-is-lost-and-never-come-back/43810
2893 // It would seem that the 'online' event does not always fire consistently. So we disable it
2894 // for Cordova.
2895 if (typeof window !== 'undefined' &&
2896 typeof window.addEventListener !== 'undefined' &&
2897 !util.isMobileCordova()) {
2898 window.addEventListener('online', function () {
2899 if (!_this.online_) {
2900 _this.online_ = true;
2901 _this.trigger('online', true);
2902 }
2903 }, false);
2904 window.addEventListener('offline', function () {
2905 if (_this.online_) {
2906 _this.online_ = false;
2907 _this.trigger('online', false);
2908 }
2909 }, false);
2910 }
2911 return _this;
2912 }
2913 OnlineMonitor.getInstance = function () {
2914 return new OnlineMonitor();
2915 };
2916 OnlineMonitor.prototype.getInitialEvent = function (eventType) {
2917 util.assert(eventType === 'online', 'Unknown event type: ' + eventType);
2918 return [this.online_];
2919 };
2920 OnlineMonitor.prototype.currentlyOnline = function () {
2921 return this.online_;
2922 };
2923 return OnlineMonitor;
2924}(EventEmitter));
2925
2926/**
2927 * @license
2928 * Copyright 2017 Google LLC
2929 *
2930 * Licensed under the Apache License, Version 2.0 (the "License");
2931 * you may not use this file except in compliance with the License.
2932 * You may obtain a copy of the License at
2933 *
2934 * http://www.apache.org/licenses/LICENSE-2.0
2935 *
2936 * Unless required by applicable law or agreed to in writing, software
2937 * distributed under the License is distributed on an "AS IS" BASIS,
2938 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2939 * See the License for the specific language governing permissions and
2940 * limitations under the License.
2941 */
2942/** Maximum key depth. */
2943var MAX_PATH_DEPTH = 32;
2944/** Maximum number of (UTF8) bytes in a Firebase path. */
2945var MAX_PATH_LENGTH_BYTES = 768;
2946/**
2947 * An immutable object representing a parsed path. It's immutable so that you
2948 * can pass them around to other functions without worrying about them changing
2949 * it.
2950 */
2951var Path = /** @class */ (function () {
2952 /**
2953 * @param pathOrString - Path string to parse, or another path, or the raw
2954 * tokens array
2955 */
2956 function Path(pathOrString, pieceNum) {
2957 if (pieceNum === void 0) {
2958 this.pieces_ = pathOrString.split('/');
2959 // Remove empty pieces.
2960 var copyTo = 0;
2961 for (var i = 0; i < this.pieces_.length; i++) {
2962 if (this.pieces_[i].length > 0) {
2963 this.pieces_[copyTo] = this.pieces_[i];
2964 copyTo++;
2965 }
2966 }
2967 this.pieces_.length = copyTo;
2968 this.pieceNum_ = 0;
2969 }
2970 else {
2971 this.pieces_ = pathOrString;
2972 this.pieceNum_ = pieceNum;
2973 }
2974 }
2975 Path.prototype.toString = function () {
2976 var pathString = '';
2977 for (var i = this.pieceNum_; i < this.pieces_.length; i++) {
2978 if (this.pieces_[i] !== '') {
2979 pathString += '/' + this.pieces_[i];
2980 }
2981 }
2982 return pathString || '/';
2983 };
2984 return Path;
2985}());
2986function newEmptyPath() {
2987 return new Path('');
2988}
2989function pathGetFront(path) {
2990 if (path.pieceNum_ >= path.pieces_.length) {
2991 return null;
2992 }
2993 return path.pieces_[path.pieceNum_];
2994}
2995/**
2996 * @returns The number of segments in this path
2997 */
2998function pathGetLength(path) {
2999 return path.pieces_.length - path.pieceNum_;
3000}
3001function pathPopFront(path) {
3002 var pieceNum = path.pieceNum_;
3003 if (pieceNum < path.pieces_.length) {
3004 pieceNum++;
3005 }
3006 return new Path(path.pieces_, pieceNum);
3007}
3008function pathGetBack(path) {
3009 if (path.pieceNum_ < path.pieces_.length) {
3010 return path.pieces_[path.pieces_.length - 1];
3011 }
3012 return null;
3013}
3014function pathToUrlEncodedString(path) {
3015 var pathString = '';
3016 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3017 if (path.pieces_[i] !== '') {
3018 pathString += '/' + encodeURIComponent(String(path.pieces_[i]));
3019 }
3020 }
3021 return pathString || '/';
3022}
3023/**
3024 * Shallow copy of the parts of the path.
3025 *
3026 */
3027function pathSlice(path, begin) {
3028 if (begin === void 0) { begin = 0; }
3029 return path.pieces_.slice(path.pieceNum_ + begin);
3030}
3031function pathParent(path) {
3032 if (path.pieceNum_ >= path.pieces_.length) {
3033 return null;
3034 }
3035 var pieces = [];
3036 for (var i = path.pieceNum_; i < path.pieces_.length - 1; i++) {
3037 pieces.push(path.pieces_[i]);
3038 }
3039 return new Path(pieces, 0);
3040}
3041function pathChild(path, childPathObj) {
3042 var pieces = [];
3043 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3044 pieces.push(path.pieces_[i]);
3045 }
3046 if (childPathObj instanceof Path) {
3047 for (var i = childPathObj.pieceNum_; i < childPathObj.pieces_.length; i++) {
3048 pieces.push(childPathObj.pieces_[i]);
3049 }
3050 }
3051 else {
3052 var childPieces = childPathObj.split('/');
3053 for (var i = 0; i < childPieces.length; i++) {
3054 if (childPieces[i].length > 0) {
3055 pieces.push(childPieces[i]);
3056 }
3057 }
3058 }
3059 return new Path(pieces, 0);
3060}
3061/**
3062 * @returns True if there are no segments in this path
3063 */
3064function pathIsEmpty(path) {
3065 return path.pieceNum_ >= path.pieces_.length;
3066}
3067/**
3068 * @returns The path from outerPath to innerPath
3069 */
3070function newRelativePath(outerPath, innerPath) {
3071 var outer = pathGetFront(outerPath), inner = pathGetFront(innerPath);
3072 if (outer === null) {
3073 return innerPath;
3074 }
3075 else if (outer === inner) {
3076 return newRelativePath(pathPopFront(outerPath), pathPopFront(innerPath));
3077 }
3078 else {
3079 throw new Error('INTERNAL ERROR: innerPath (' +
3080 innerPath +
3081 ') is not within ' +
3082 'outerPath (' +
3083 outerPath +
3084 ')');
3085 }
3086}
3087/**
3088 * @returns -1, 0, 1 if left is less, equal, or greater than the right.
3089 */
3090function pathCompare(left, right) {
3091 var leftKeys = pathSlice(left, 0);
3092 var rightKeys = pathSlice(right, 0);
3093 for (var i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
3094 var cmp = nameCompare(leftKeys[i], rightKeys[i]);
3095 if (cmp !== 0) {
3096 return cmp;
3097 }
3098 }
3099 if (leftKeys.length === rightKeys.length) {
3100 return 0;
3101 }
3102 return leftKeys.length < rightKeys.length ? -1 : 1;
3103}
3104/**
3105 * @returns true if paths are the same.
3106 */
3107function pathEquals(path, other) {
3108 if (pathGetLength(path) !== pathGetLength(other)) {
3109 return false;
3110 }
3111 for (var i = path.pieceNum_, j = other.pieceNum_; i <= path.pieces_.length; i++, j++) {
3112 if (path.pieces_[i] !== other.pieces_[j]) {
3113 return false;
3114 }
3115 }
3116 return true;
3117}
3118/**
3119 * @returns True if this path is a parent (or the same as) other
3120 */
3121function pathContains(path, other) {
3122 var i = path.pieceNum_;
3123 var j = other.pieceNum_;
3124 if (pathGetLength(path) > pathGetLength(other)) {
3125 return false;
3126 }
3127 while (i < path.pieces_.length) {
3128 if (path.pieces_[i] !== other.pieces_[j]) {
3129 return false;
3130 }
3131 ++i;
3132 ++j;
3133 }
3134 return true;
3135}
3136/**
3137 * Dynamic (mutable) path used to count path lengths.
3138 *
3139 * This class is used to efficiently check paths for valid
3140 * length (in UTF8 bytes) and depth (used in path validation).
3141 *
3142 * Throws Error exception if path is ever invalid.
3143 *
3144 * The definition of a path always begins with '/'.
3145 */
3146var ValidationPath = /** @class */ (function () {
3147 /**
3148 * @param path - Initial Path.
3149 * @param errorPrefix_ - Prefix for any error messages.
3150 */
3151 function ValidationPath(path, errorPrefix_) {
3152 this.errorPrefix_ = errorPrefix_;
3153 this.parts_ = pathSlice(path, 0);
3154 /** Initialize to number of '/' chars needed in path. */
3155 this.byteLength_ = Math.max(1, this.parts_.length);
3156 for (var i = 0; i < this.parts_.length; i++) {
3157 this.byteLength_ += util.stringLength(this.parts_[i]);
3158 }
3159 validationPathCheckValid(this);
3160 }
3161 return ValidationPath;
3162}());
3163function validationPathPush(validationPath, child) {
3164 // Count the needed '/'
3165 if (validationPath.parts_.length > 0) {
3166 validationPath.byteLength_ += 1;
3167 }
3168 validationPath.parts_.push(child);
3169 validationPath.byteLength_ += util.stringLength(child);
3170 validationPathCheckValid(validationPath);
3171}
3172function validationPathPop(validationPath) {
3173 var last = validationPath.parts_.pop();
3174 validationPath.byteLength_ -= util.stringLength(last);
3175 // Un-count the previous '/'
3176 if (validationPath.parts_.length > 0) {
3177 validationPath.byteLength_ -= 1;
3178 }
3179}
3180function validationPathCheckValid(validationPath) {
3181 if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
3182 throw new Error(validationPath.errorPrefix_ +
3183 'has a key path longer than ' +
3184 MAX_PATH_LENGTH_BYTES +
3185 ' bytes (' +
3186 validationPath.byteLength_ +
3187 ').');
3188 }
3189 if (validationPath.parts_.length > MAX_PATH_DEPTH) {
3190 throw new Error(validationPath.errorPrefix_ +
3191 'path specified exceeds the maximum depth that can be written (' +
3192 MAX_PATH_DEPTH +
3193 ') or object contains a cycle ' +
3194 validationPathToErrorString(validationPath));
3195 }
3196}
3197/**
3198 * String for use in error messages - uses '.' notation for path.
3199 */
3200function validationPathToErrorString(validationPath) {
3201 if (validationPath.parts_.length === 0) {
3202 return '';
3203 }
3204 return "in property '" + validationPath.parts_.join('.') + "'";
3205}
3206
3207/**
3208 * @license
3209 * Copyright 2017 Google LLC
3210 *
3211 * Licensed under the Apache License, Version 2.0 (the "License");
3212 * you may not use this file except in compliance with the License.
3213 * You may obtain a copy of the License at
3214 *
3215 * http://www.apache.org/licenses/LICENSE-2.0
3216 *
3217 * Unless required by applicable law or agreed to in writing, software
3218 * distributed under the License is distributed on an "AS IS" BASIS,
3219 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3220 * See the License for the specific language governing permissions and
3221 * limitations under the License.
3222 */
3223var VisibilityMonitor = /** @class */ (function (_super) {
3224 tslib.__extends(VisibilityMonitor, _super);
3225 function VisibilityMonitor() {
3226 var _this = _super.call(this, ['visible']) || this;
3227 var hidden;
3228 var visibilityChange;
3229 if (typeof document !== 'undefined' &&
3230 typeof document.addEventListener !== 'undefined') {
3231 if (typeof document['hidden'] !== 'undefined') {
3232 // Opera 12.10 and Firefox 18 and later support
3233 visibilityChange = 'visibilitychange';
3234 hidden = 'hidden';
3235 }
3236 else if (typeof document['mozHidden'] !== 'undefined') {
3237 visibilityChange = 'mozvisibilitychange';
3238 hidden = 'mozHidden';
3239 }
3240 else if (typeof document['msHidden'] !== 'undefined') {
3241 visibilityChange = 'msvisibilitychange';
3242 hidden = 'msHidden';
3243 }
3244 else if (typeof document['webkitHidden'] !== 'undefined') {
3245 visibilityChange = 'webkitvisibilitychange';
3246 hidden = 'webkitHidden';
3247 }
3248 }
3249 // Initially, we always assume we are visible. This ensures that in browsers
3250 // without page visibility support or in cases where we are never visible
3251 // (e.g. chrome extension), we act as if we are visible, i.e. don't delay
3252 // reconnects
3253 _this.visible_ = true;
3254 if (visibilityChange) {
3255 document.addEventListener(visibilityChange, function () {
3256 var visible = !document[hidden];
3257 if (visible !== _this.visible_) {
3258 _this.visible_ = visible;
3259 _this.trigger('visible', visible);
3260 }
3261 }, false);
3262 }
3263 return _this;
3264 }
3265 VisibilityMonitor.getInstance = function () {
3266 return new VisibilityMonitor();
3267 };
3268 VisibilityMonitor.prototype.getInitialEvent = function (eventType) {
3269 util.assert(eventType === 'visible', 'Unknown event type: ' + eventType);
3270 return [this.visible_];
3271 };
3272 return VisibilityMonitor;
3273}(EventEmitter));
3274
3275/**
3276 * @license
3277 * Copyright 2017 Google LLC
3278 *
3279 * Licensed under the Apache License, Version 2.0 (the "License");
3280 * you may not use this file except in compliance with the License.
3281 * You may obtain a copy of the License at
3282 *
3283 * http://www.apache.org/licenses/LICENSE-2.0
3284 *
3285 * Unless required by applicable law or agreed to in writing, software
3286 * distributed under the License is distributed on an "AS IS" BASIS,
3287 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3288 * See the License for the specific language governing permissions and
3289 * limitations under the License.
3290 */
3291var RECONNECT_MIN_DELAY = 1000;
3292var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
3293var GET_CONNECT_TIMEOUT = 3 * 1000;
3294var RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
3295var RECONNECT_DELAY_MULTIPLIER = 1.3;
3296var RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
3297var SERVER_KILL_INTERRUPT_REASON = 'server_kill';
3298// If auth fails repeatedly, we'll assume something is wrong and log a warning / back off.
3299var INVALID_TOKEN_THRESHOLD = 3;
3300/**
3301 * Firebase connection. Abstracts wire protocol and handles reconnecting.
3302 *
3303 * NOTE: All JSON objects sent to the realtime connection must have property names enclosed
3304 * in quotes to make sure the closure compiler does not minify them.
3305 */
3306var PersistentConnection = /** @class */ (function (_super) {
3307 tslib.__extends(PersistentConnection, _super);
3308 /**
3309 * @param repoInfo_ - Data about the namespace we are connecting to
3310 * @param applicationId_ - The Firebase App ID for this project
3311 * @param onDataUpdate_ - A callback for new data from the server
3312 */
3313 function PersistentConnection(repoInfo_, applicationId_, onDataUpdate_, onConnectStatus_, onServerInfoUpdate_, authTokenProvider_, appCheckTokenProvider_, authOverride_) {
3314 var _this = _super.call(this) || this;
3315 _this.repoInfo_ = repoInfo_;
3316 _this.applicationId_ = applicationId_;
3317 _this.onDataUpdate_ = onDataUpdate_;
3318 _this.onConnectStatus_ = onConnectStatus_;
3319 _this.onServerInfoUpdate_ = onServerInfoUpdate_;
3320 _this.authTokenProvider_ = authTokenProvider_;
3321 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
3322 _this.authOverride_ = authOverride_;
3323 // Used for diagnostic logging.
3324 _this.id = PersistentConnection.nextPersistentConnectionId_++;
3325 _this.log_ = logWrapper('p:' + _this.id + ':');
3326 _this.interruptReasons_ = {};
3327 _this.listens = new Map();
3328 _this.outstandingPuts_ = [];
3329 _this.outstandingGets_ = [];
3330 _this.outstandingPutCount_ = 0;
3331 _this.outstandingGetCount_ = 0;
3332 _this.onDisconnectRequestQueue_ = [];
3333 _this.connected_ = false;
3334 _this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3335 _this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_DEFAULT;
3336 _this.securityDebugCallback_ = null;
3337 _this.lastSessionId = null;
3338 _this.establishConnectionTimer_ = null;
3339 _this.visible_ = false;
3340 // Before we get connected, we keep a queue of pending messages to send.
3341 _this.requestCBHash_ = {};
3342 _this.requestNumber_ = 0;
3343 _this.realtime_ = null;
3344 _this.authToken_ = null;
3345 _this.appCheckToken_ = null;
3346 _this.forceTokenRefresh_ = false;
3347 _this.invalidAuthTokenCount_ = 0;
3348 _this.invalidAppCheckTokenCount_ = 0;
3349 _this.firstConnection_ = true;
3350 _this.lastConnectionAttemptTime_ = null;
3351 _this.lastConnectionEstablishedTime_ = null;
3352 if (authOverride_ && !util.isNodeSdk()) {
3353 throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
3354 }
3355 VisibilityMonitor.getInstance().on('visible', _this.onVisible_, _this);
3356 if (repoInfo_.host.indexOf('fblocal') === -1) {
3357 OnlineMonitor.getInstance().on('online', _this.onOnline_, _this);
3358 }
3359 return _this;
3360 }
3361 PersistentConnection.prototype.sendRequest = function (action, body, onResponse) {
3362 var curReqNum = ++this.requestNumber_;
3363 var msg = { r: curReqNum, a: action, b: body };
3364 this.log_(util.stringify(msg));
3365 util.assert(this.connected_, "sendRequest call when we're not connected not allowed.");
3366 this.realtime_.sendRequest(msg);
3367 if (onResponse) {
3368 this.requestCBHash_[curReqNum] = onResponse;
3369 }
3370 };
3371 PersistentConnection.prototype.get = function (query) {
3372 var _this = this;
3373 this.initConnection_();
3374 var deferred = new util.Deferred();
3375 var request = {
3376 p: query._path.toString(),
3377 q: query._queryObject
3378 };
3379 var outstandingGet = {
3380 action: 'g',
3381 request: request,
3382 onComplete: function (message) {
3383 var payload = message['d'];
3384 if (message['s'] === 'ok') {
3385 _this.onDataUpdate_(request['p'], payload,
3386 /*isMerge*/ false,
3387 /*tag*/ null);
3388 deferred.resolve(payload);
3389 }
3390 else {
3391 deferred.reject(payload);
3392 }
3393 }
3394 };
3395 this.outstandingGets_.push(outstandingGet);
3396 this.outstandingGetCount_++;
3397 var index = this.outstandingGets_.length - 1;
3398 if (!this.connected_) {
3399 setTimeout(function () {
3400 var get = _this.outstandingGets_[index];
3401 if (get === undefined || outstandingGet !== get) {
3402 return;
3403 }
3404 delete _this.outstandingGets_[index];
3405 _this.outstandingGetCount_--;
3406 if (_this.outstandingGetCount_ === 0) {
3407 _this.outstandingGets_ = [];
3408 }
3409 _this.log_('get ' + index + ' timed out on connection');
3410 deferred.reject(new Error('Client is offline.'));
3411 }, GET_CONNECT_TIMEOUT);
3412 }
3413 if (this.connected_) {
3414 this.sendGet_(index);
3415 }
3416 return deferred.promise;
3417 };
3418 PersistentConnection.prototype.listen = function (query, currentHashFn, tag, onComplete) {
3419 this.initConnection_();
3420 var queryId = query._queryIdentifier;
3421 var pathString = query._path.toString();
3422 this.log_('Listen called for ' + pathString + ' ' + queryId);
3423 if (!this.listens.has(pathString)) {
3424 this.listens.set(pathString, new Map());
3425 }
3426 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'listen() called for non-default but complete query');
3427 util.assert(!this.listens.get(pathString).has(queryId), 'listen() called twice for same path/queryId.');
3428 var listenSpec = {
3429 onComplete: onComplete,
3430 hashFn: currentHashFn,
3431 query: query,
3432 tag: tag
3433 };
3434 this.listens.get(pathString).set(queryId, listenSpec);
3435 if (this.connected_) {
3436 this.sendListen_(listenSpec);
3437 }
3438 };
3439 PersistentConnection.prototype.sendGet_ = function (index) {
3440 var _this = this;
3441 var get = this.outstandingGets_[index];
3442 this.sendRequest('g', get.request, function (message) {
3443 delete _this.outstandingGets_[index];
3444 _this.outstandingGetCount_--;
3445 if (_this.outstandingGetCount_ === 0) {
3446 _this.outstandingGets_ = [];
3447 }
3448 if (get.onComplete) {
3449 get.onComplete(message);
3450 }
3451 });
3452 };
3453 PersistentConnection.prototype.sendListen_ = function (listenSpec) {
3454 var _this = this;
3455 var query = listenSpec.query;
3456 var pathString = query._path.toString();
3457 var queryId = query._queryIdentifier;
3458 this.log_('Listen on ' + pathString + ' for ' + queryId);
3459 var req = { /*path*/ p: pathString };
3460 var action = 'q';
3461 // Only bother to send query if it's non-default.
3462 if (listenSpec.tag) {
3463 req['q'] = query._queryObject;
3464 req['t'] = listenSpec.tag;
3465 }
3466 req[ /*hash*/'h'] = listenSpec.hashFn();
3467 this.sendRequest(action, req, function (message) {
3468 var payload = message[ /*data*/'d'];
3469 var status = message[ /*status*/'s'];
3470 // print warnings in any case...
3471 PersistentConnection.warnOnListenWarnings_(payload, query);
3472 var currentListenSpec = _this.listens.get(pathString) &&
3473 _this.listens.get(pathString).get(queryId);
3474 // only trigger actions if the listen hasn't been removed and readded
3475 if (currentListenSpec === listenSpec) {
3476 _this.log_('listen response', message);
3477 if (status !== 'ok') {
3478 _this.removeListen_(pathString, queryId);
3479 }
3480 if (listenSpec.onComplete) {
3481 listenSpec.onComplete(status, payload);
3482 }
3483 }
3484 });
3485 };
3486 PersistentConnection.warnOnListenWarnings_ = function (payload, query) {
3487 if (payload && typeof payload === 'object' && util.contains(payload, 'w')) {
3488 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3489 var warnings = util.safeGet(payload, 'w');
3490 if (Array.isArray(warnings) && ~warnings.indexOf('no_index')) {
3491 var indexSpec = '".indexOn": "' + query._queryParams.getIndex().toString() + '"';
3492 var indexPath = query._path.toString();
3493 warn("Using an unspecified index. Your data will be downloaded and " +
3494 ("filtered on the client. Consider adding " + indexSpec + " at ") +
3495 (indexPath + " to your security rules for better performance."));
3496 }
3497 }
3498 };
3499 PersistentConnection.prototype.refreshAuthToken = function (token) {
3500 this.authToken_ = token;
3501 this.log_('Auth token refreshed');
3502 if (this.authToken_) {
3503 this.tryAuth();
3504 }
3505 else {
3506 //If we're connected we want to let the server know to unauthenticate us. If we're not connected, simply delete
3507 //the credential so we dont become authenticated next time we connect.
3508 if (this.connected_) {
3509 this.sendRequest('unauth', {}, function () { });
3510 }
3511 }
3512 this.reduceReconnectDelayIfAdminCredential_(token);
3513 };
3514 PersistentConnection.prototype.reduceReconnectDelayIfAdminCredential_ = function (credential) {
3515 // NOTE: This isn't intended to be bulletproof (a malicious developer can always just modify the client).
3516 // Additionally, we don't bother resetting the max delay back to the default if auth fails / expires.
3517 var isFirebaseSecret = credential && credential.length === 40;
3518 if (isFirebaseSecret || util.isAdmin(credential)) {
3519 this.log_('Admin auth credential detected. Reducing max reconnect time.');
3520 this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
3521 }
3522 };
3523 PersistentConnection.prototype.refreshAppCheckToken = function (token) {
3524 this.appCheckToken_ = token;
3525 this.log_('App check token refreshed');
3526 if (this.appCheckToken_) {
3527 this.tryAppCheck();
3528 }
3529 else {
3530 //If we're connected we want to let the server know to unauthenticate us.
3531 //If we're not connected, simply delete the credential so we dont become
3532 // authenticated next time we connect.
3533 if (this.connected_) {
3534 this.sendRequest('unappeck', {}, function () { });
3535 }
3536 }
3537 };
3538 /**
3539 * Attempts to authenticate with the given credentials. If the authentication attempt fails, it's triggered like
3540 * a auth revoked (the connection is closed).
3541 */
3542 PersistentConnection.prototype.tryAuth = function () {
3543 var _this = this;
3544 if (this.connected_ && this.authToken_) {
3545 var token_1 = this.authToken_;
3546 var authMethod = util.isValidFormat(token_1) ? 'auth' : 'gauth';
3547 var requestData = { cred: token_1 };
3548 if (this.authOverride_ === null) {
3549 requestData['noauth'] = true;
3550 }
3551 else if (typeof this.authOverride_ === 'object') {
3552 requestData['authvar'] = this.authOverride_;
3553 }
3554 this.sendRequest(authMethod, requestData, function (res) {
3555 var status = res[ /*status*/'s'];
3556 var data = res[ /*data*/'d'] || 'error';
3557 if (_this.authToken_ === token_1) {
3558 if (status === 'ok') {
3559 _this.invalidAuthTokenCount_ = 0;
3560 }
3561 else {
3562 // Triggers reconnect and force refresh for auth token
3563 _this.onAuthRevoked_(status, data);
3564 }
3565 }
3566 });
3567 }
3568 };
3569 /**
3570 * Attempts to authenticate with the given token. If the authentication
3571 * attempt fails, it's triggered like the token was revoked (the connection is
3572 * closed).
3573 */
3574 PersistentConnection.prototype.tryAppCheck = function () {
3575 var _this = this;
3576 if (this.connected_ && this.appCheckToken_) {
3577 this.sendRequest('appcheck', { 'token': this.appCheckToken_ }, function (res) {
3578 var status = res[ /*status*/'s'];
3579 var data = res[ /*data*/'d'] || 'error';
3580 if (status === 'ok') {
3581 _this.invalidAppCheckTokenCount_ = 0;
3582 }
3583 else {
3584 _this.onAppCheckRevoked_(status, data);
3585 }
3586 });
3587 }
3588 };
3589 /**
3590 * @inheritDoc
3591 */
3592 PersistentConnection.prototype.unlisten = function (query, tag) {
3593 var pathString = query._path.toString();
3594 var queryId = query._queryIdentifier;
3595 this.log_('Unlisten called for ' + pathString + ' ' + queryId);
3596 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'unlisten() called for non-default but complete query');
3597 var listen = this.removeListen_(pathString, queryId);
3598 if (listen && this.connected_) {
3599 this.sendUnlisten_(pathString, queryId, query._queryObject, tag);
3600 }
3601 };
3602 PersistentConnection.prototype.sendUnlisten_ = function (pathString, queryId, queryObj, tag) {
3603 this.log_('Unlisten on ' + pathString + ' for ' + queryId);
3604 var req = { /*path*/ p: pathString };
3605 var action = 'n';
3606 // Only bother sending queryId if it's non-default.
3607 if (tag) {
3608 req['q'] = queryObj;
3609 req['t'] = tag;
3610 }
3611 this.sendRequest(action, req);
3612 };
3613 PersistentConnection.prototype.onDisconnectPut = function (pathString, data, onComplete) {
3614 this.initConnection_();
3615 if (this.connected_) {
3616 this.sendOnDisconnect_('o', pathString, data, onComplete);
3617 }
3618 else {
3619 this.onDisconnectRequestQueue_.push({
3620 pathString: pathString,
3621 action: 'o',
3622 data: data,
3623 onComplete: onComplete
3624 });
3625 }
3626 };
3627 PersistentConnection.prototype.onDisconnectMerge = function (pathString, data, onComplete) {
3628 this.initConnection_();
3629 if (this.connected_) {
3630 this.sendOnDisconnect_('om', pathString, data, onComplete);
3631 }
3632 else {
3633 this.onDisconnectRequestQueue_.push({
3634 pathString: pathString,
3635 action: 'om',
3636 data: data,
3637 onComplete: onComplete
3638 });
3639 }
3640 };
3641 PersistentConnection.prototype.onDisconnectCancel = function (pathString, onComplete) {
3642 this.initConnection_();
3643 if (this.connected_) {
3644 this.sendOnDisconnect_('oc', pathString, null, onComplete);
3645 }
3646 else {
3647 this.onDisconnectRequestQueue_.push({
3648 pathString: pathString,
3649 action: 'oc',
3650 data: null,
3651 onComplete: onComplete
3652 });
3653 }
3654 };
3655 PersistentConnection.prototype.sendOnDisconnect_ = function (action, pathString, data, onComplete) {
3656 var request = { /*path*/ p: pathString, /*data*/ d: data };
3657 this.log_('onDisconnect ' + action, request);
3658 this.sendRequest(action, request, function (response) {
3659 if (onComplete) {
3660 setTimeout(function () {
3661 onComplete(response[ /*status*/'s'], response[ /* data */'d']);
3662 }, Math.floor(0));
3663 }
3664 });
3665 };
3666 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
3667 this.putInternal('p', pathString, data, onComplete, hash);
3668 };
3669 PersistentConnection.prototype.merge = function (pathString, data, onComplete, hash) {
3670 this.putInternal('m', pathString, data, onComplete, hash);
3671 };
3672 PersistentConnection.prototype.putInternal = function (action, pathString, data, onComplete, hash) {
3673 this.initConnection_();
3674 var request = {
3675 /*path*/ p: pathString,
3676 /*data*/ d: data
3677 };
3678 if (hash !== undefined) {
3679 request[ /*hash*/'h'] = hash;
3680 }
3681 // TODO: Only keep track of the most recent put for a given path?
3682 this.outstandingPuts_.push({
3683 action: action,
3684 request: request,
3685 onComplete: onComplete
3686 });
3687 this.outstandingPutCount_++;
3688 var index = this.outstandingPuts_.length - 1;
3689 if (this.connected_) {
3690 this.sendPut_(index);
3691 }
3692 else {
3693 this.log_('Buffering put: ' + pathString);
3694 }
3695 };
3696 PersistentConnection.prototype.sendPut_ = function (index) {
3697 var _this = this;
3698 var action = this.outstandingPuts_[index].action;
3699 var request = this.outstandingPuts_[index].request;
3700 var onComplete = this.outstandingPuts_[index].onComplete;
3701 this.outstandingPuts_[index].queued = this.connected_;
3702 this.sendRequest(action, request, function (message) {
3703 _this.log_(action + ' response', message);
3704 delete _this.outstandingPuts_[index];
3705 _this.outstandingPutCount_--;
3706 // Clean up array occasionally.
3707 if (_this.outstandingPutCount_ === 0) {
3708 _this.outstandingPuts_ = [];
3709 }
3710 if (onComplete) {
3711 onComplete(message[ /*status*/'s'], message[ /* data */'d']);
3712 }
3713 });
3714 };
3715 PersistentConnection.prototype.reportStats = function (stats) {
3716 var _this = this;
3717 // If we're not connected, we just drop the stats.
3718 if (this.connected_) {
3719 var request = { /*counters*/ c: stats };
3720 this.log_('reportStats', request);
3721 this.sendRequest(/*stats*/ 's', request, function (result) {
3722 var status = result[ /*status*/'s'];
3723 if (status !== 'ok') {
3724 var errorReason = result[ /* data */'d'];
3725 _this.log_('reportStats', 'Error sending stats: ' + errorReason);
3726 }
3727 });
3728 }
3729 };
3730 PersistentConnection.prototype.onDataMessage_ = function (message) {
3731 if ('r' in message) {
3732 // this is a response
3733 this.log_('from server: ' + util.stringify(message));
3734 var reqNum = message['r'];
3735 var onResponse = this.requestCBHash_[reqNum];
3736 if (onResponse) {
3737 delete this.requestCBHash_[reqNum];
3738 onResponse(message[ /*body*/'b']);
3739 }
3740 }
3741 else if ('error' in message) {
3742 throw 'A server-side error has occurred: ' + message['error'];
3743 }
3744 else if ('a' in message) {
3745 // a and b are action and body, respectively
3746 this.onDataPush_(message['a'], message['b']);
3747 }
3748 };
3749 PersistentConnection.prototype.onDataPush_ = function (action, body) {
3750 this.log_('handleServerMessage', action, body);
3751 if (action === 'd') {
3752 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3753 /*isMerge*/ false, body['t']);
3754 }
3755 else if (action === 'm') {
3756 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3757 /*isMerge=*/ true, body['t']);
3758 }
3759 else if (action === 'c') {
3760 this.onListenRevoked_(body[ /*path*/'p'], body[ /*query*/'q']);
3761 }
3762 else if (action === 'ac') {
3763 this.onAuthRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3764 }
3765 else if (action === 'apc') {
3766 this.onAppCheckRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3767 }
3768 else if (action === 'sd') {
3769 this.onSecurityDebugPacket_(body);
3770 }
3771 else {
3772 error('Unrecognized action received from server: ' +
3773 util.stringify(action) +
3774 '\nAre you using the latest client?');
3775 }
3776 };
3777 PersistentConnection.prototype.onReady_ = function (timestamp, sessionId) {
3778 this.log_('connection ready');
3779 this.connected_ = true;
3780 this.lastConnectionEstablishedTime_ = new Date().getTime();
3781 this.handleTimestamp_(timestamp);
3782 this.lastSessionId = sessionId;
3783 if (this.firstConnection_) {
3784 this.sendConnectStats_();
3785 }
3786 this.restoreState_();
3787 this.firstConnection_ = false;
3788 this.onConnectStatus_(true);
3789 };
3790 PersistentConnection.prototype.scheduleConnect_ = function (timeout) {
3791 var _this = this;
3792 util.assert(!this.realtime_, "Scheduling a connect when we're already connected/ing?");
3793 if (this.establishConnectionTimer_) {
3794 clearTimeout(this.establishConnectionTimer_);
3795 }
3796 // NOTE: Even when timeout is 0, it's important to do a setTimeout to work around an infuriating "Security Error" in
3797 // Firefox when trying to write to our long-polling iframe in some scenarios (e.g. Forge or our unit tests).
3798 this.establishConnectionTimer_ = setTimeout(function () {
3799 _this.establishConnectionTimer_ = null;
3800 _this.establishConnection_();
3801 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3802 }, Math.floor(timeout));
3803 };
3804 PersistentConnection.prototype.initConnection_ = function () {
3805 if (!this.realtime_ && this.firstConnection_) {
3806 this.scheduleConnect_(0);
3807 }
3808 };
3809 PersistentConnection.prototype.onVisible_ = function (visible) {
3810 // NOTE: Tabbing away and back to a window will defeat our reconnect backoff, but I think that's fine.
3811 if (visible &&
3812 !this.visible_ &&
3813 this.reconnectDelay_ === this.maxReconnectDelay_) {
3814 this.log_('Window became visible. Reducing delay.');
3815 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3816 if (!this.realtime_) {
3817 this.scheduleConnect_(0);
3818 }
3819 }
3820 this.visible_ = visible;
3821 };
3822 PersistentConnection.prototype.onOnline_ = function (online) {
3823 if (online) {
3824 this.log_('Browser went online.');
3825 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3826 if (!this.realtime_) {
3827 this.scheduleConnect_(0);
3828 }
3829 }
3830 else {
3831 this.log_('Browser went offline. Killing connection.');
3832 if (this.realtime_) {
3833 this.realtime_.close();
3834 }
3835 }
3836 };
3837 PersistentConnection.prototype.onRealtimeDisconnect_ = function () {
3838 this.log_('data client disconnected');
3839 this.connected_ = false;
3840 this.realtime_ = null;
3841 // Since we don't know if our sent transactions succeeded or not, we need to cancel them.
3842 this.cancelSentTransactions_();
3843 // Clear out the pending requests.
3844 this.requestCBHash_ = {};
3845 if (this.shouldReconnect_()) {
3846 if (!this.visible_) {
3847 this.log_("Window isn't visible. Delaying reconnect.");
3848 this.reconnectDelay_ = this.maxReconnectDelay_;
3849 this.lastConnectionAttemptTime_ = new Date().getTime();
3850 }
3851 else if (this.lastConnectionEstablishedTime_) {
3852 // If we've been connected long enough, reset reconnect delay to minimum.
3853 var timeSinceLastConnectSucceeded = new Date().getTime() - this.lastConnectionEstablishedTime_;
3854 if (timeSinceLastConnectSucceeded > RECONNECT_DELAY_RESET_TIMEOUT) {
3855 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3856 }
3857 this.lastConnectionEstablishedTime_ = null;
3858 }
3859 var timeSinceLastConnectAttempt = new Date().getTime() - this.lastConnectionAttemptTime_;
3860 var reconnectDelay = Math.max(0, this.reconnectDelay_ - timeSinceLastConnectAttempt);
3861 reconnectDelay = Math.random() * reconnectDelay;
3862 this.log_('Trying to reconnect in ' + reconnectDelay + 'ms');
3863 this.scheduleConnect_(reconnectDelay);
3864 // Adjust reconnect delay for next time.
3865 this.reconnectDelay_ = Math.min(this.maxReconnectDelay_, this.reconnectDelay_ * RECONNECT_DELAY_MULTIPLIER);
3866 }
3867 this.onConnectStatus_(false);
3868 };
3869 PersistentConnection.prototype.establishConnection_ = function () {
3870 return tslib.__awaiter(this, void 0, void 0, function () {
3871 var onDataMessage, onReady, onDisconnect_1, connId, lastSessionId, canceled_1, connection_1, closeFn, sendRequestFn, forceRefresh, _a, authToken, appCheckToken, error_1;
3872 var _this = this;
3873 return tslib.__generator(this, function (_b) {
3874 switch (_b.label) {
3875 case 0:
3876 if (!this.shouldReconnect_()) return [3 /*break*/, 4];
3877 this.log_('Making a connection attempt');
3878 this.lastConnectionAttemptTime_ = new Date().getTime();
3879 this.lastConnectionEstablishedTime_ = null;
3880 onDataMessage = this.onDataMessage_.bind(this);
3881 onReady = this.onReady_.bind(this);
3882 onDisconnect_1 = this.onRealtimeDisconnect_.bind(this);
3883 connId = this.id + ':' + PersistentConnection.nextConnectionId_++;
3884 lastSessionId = this.lastSessionId;
3885 canceled_1 = false;
3886 connection_1 = null;
3887 closeFn = function () {
3888 if (connection_1) {
3889 connection_1.close();
3890 }
3891 else {
3892 canceled_1 = true;
3893 onDisconnect_1();
3894 }
3895 };
3896 sendRequestFn = function (msg) {
3897 util.assert(connection_1, "sendRequest call when we're not connected not allowed.");
3898 connection_1.sendRequest(msg);
3899 };
3900 this.realtime_ = {
3901 close: closeFn,
3902 sendRequest: sendRequestFn
3903 };
3904 forceRefresh = this.forceTokenRefresh_;
3905 this.forceTokenRefresh_ = false;
3906 _b.label = 1;
3907 case 1:
3908 _b.trys.push([1, 3, , 4]);
3909 return [4 /*yield*/, Promise.all([
3910 this.authTokenProvider_.getToken(forceRefresh),
3911 this.appCheckTokenProvider_.getToken(forceRefresh)
3912 ])];
3913 case 2:
3914 _a = tslib.__read.apply(void 0, [_b.sent(), 2]), authToken = _a[0], appCheckToken = _a[1];
3915 if (!canceled_1) {
3916 log('getToken() completed. Creating connection.');
3917 this.authToken_ = authToken && authToken.accessToken;
3918 this.appCheckToken_ = appCheckToken && appCheckToken.token;
3919 connection_1 = new Connection(connId, this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, onDataMessage, onReady, onDisconnect_1,
3920 /* onKill= */ function (reason) {
3921 warn(reason + ' (' + _this.repoInfo_.toString() + ')');
3922 _this.interrupt(SERVER_KILL_INTERRUPT_REASON);
3923 }, lastSessionId);
3924 }
3925 else {
3926 log('getToken() completed but was canceled');
3927 }
3928 return [3 /*break*/, 4];
3929 case 3:
3930 error_1 = _b.sent();
3931 this.log_('Failed to get token: ' + error_1);
3932 if (!canceled_1) {
3933 if (this.repoInfo_.nodeAdmin) {
3934 // This may be a critical error for the Admin Node.js SDK, so log a warning.
3935 // But getToken() may also just have temporarily failed, so we still want to
3936 // continue retrying.
3937 warn(error_1);
3938 }
3939 closeFn();
3940 }
3941 return [3 /*break*/, 4];
3942 case 4: return [2 /*return*/];
3943 }
3944 });
3945 });
3946 };
3947 PersistentConnection.prototype.interrupt = function (reason) {
3948 log('Interrupting connection for reason: ' + reason);
3949 this.interruptReasons_[reason] = true;
3950 if (this.realtime_) {
3951 this.realtime_.close();
3952 }
3953 else {
3954 if (this.establishConnectionTimer_) {
3955 clearTimeout(this.establishConnectionTimer_);
3956 this.establishConnectionTimer_ = null;
3957 }
3958 if (this.connected_) {
3959 this.onRealtimeDisconnect_();
3960 }
3961 }
3962 };
3963 PersistentConnection.prototype.resume = function (reason) {
3964 log('Resuming connection for reason: ' + reason);
3965 delete this.interruptReasons_[reason];
3966 if (util.isEmpty(this.interruptReasons_)) {
3967 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3968 if (!this.realtime_) {
3969 this.scheduleConnect_(0);
3970 }
3971 }
3972 };
3973 PersistentConnection.prototype.handleTimestamp_ = function (timestamp) {
3974 var delta = timestamp - new Date().getTime();
3975 this.onServerInfoUpdate_({ serverTimeOffset: delta });
3976 };
3977 PersistentConnection.prototype.cancelSentTransactions_ = function () {
3978 for (var i = 0; i < this.outstandingPuts_.length; i++) {
3979 var put = this.outstandingPuts_[i];
3980 if (put && /*hash*/ 'h' in put.request && put.queued) {
3981 if (put.onComplete) {
3982 put.onComplete('disconnect');
3983 }
3984 delete this.outstandingPuts_[i];
3985 this.outstandingPutCount_--;
3986 }
3987 }
3988 // Clean up array occasionally.
3989 if (this.outstandingPutCount_ === 0) {
3990 this.outstandingPuts_ = [];
3991 }
3992 };
3993 PersistentConnection.prototype.onListenRevoked_ = function (pathString, query) {
3994 // Remove the listen and manufacture a "permission_denied" error for the failed listen.
3995 var queryId;
3996 if (!query) {
3997 queryId = 'default';
3998 }
3999 else {
4000 queryId = query.map(function (q) { return ObjectToUniqueKey(q); }).join('$');
4001 }
4002 var listen = this.removeListen_(pathString, queryId);
4003 if (listen && listen.onComplete) {
4004 listen.onComplete('permission_denied');
4005 }
4006 };
4007 PersistentConnection.prototype.removeListen_ = function (pathString, queryId) {
4008 var normalizedPathString = new Path(pathString).toString(); // normalize path.
4009 var listen;
4010 if (this.listens.has(normalizedPathString)) {
4011 var map = this.listens.get(normalizedPathString);
4012 listen = map.get(queryId);
4013 map.delete(queryId);
4014 if (map.size === 0) {
4015 this.listens.delete(normalizedPathString);
4016 }
4017 }
4018 else {
4019 // all listens for this path has already been removed
4020 listen = undefined;
4021 }
4022 return listen;
4023 };
4024 PersistentConnection.prototype.onAuthRevoked_ = function (statusCode, explanation) {
4025 log('Auth token revoked: ' + statusCode + '/' + explanation);
4026 this.authToken_ = null;
4027 this.forceTokenRefresh_ = true;
4028 this.realtime_.close();
4029 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4030 // We'll wait a couple times before logging the warning / increasing the
4031 // retry period since oauth tokens will report as "invalid" if they're
4032 // just expired. Plus there may be transient issues that resolve themselves.
4033 this.invalidAuthTokenCount_++;
4034 if (this.invalidAuthTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4035 // Set a long reconnect delay because recovery is unlikely
4036 this.reconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
4037 // Notify the auth token provider that the token is invalid, which will log
4038 // a warning
4039 this.authTokenProvider_.notifyForInvalidToken();
4040 }
4041 }
4042 };
4043 PersistentConnection.prototype.onAppCheckRevoked_ = function (statusCode, explanation) {
4044 log('App check token revoked: ' + statusCode + '/' + explanation);
4045 this.appCheckToken_ = null;
4046 this.forceTokenRefresh_ = true;
4047 // Note: We don't close the connection as the developer may not have
4048 // enforcement enabled. The backend closes connections with enforcements.
4049 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4050 // We'll wait a couple times before logging the warning / increasing the
4051 // retry period since oauth tokens will report as "invalid" if they're
4052 // just expired. Plus there may be transient issues that resolve themselves.
4053 this.invalidAppCheckTokenCount_++;
4054 if (this.invalidAppCheckTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4055 this.appCheckTokenProvider_.notifyForInvalidToken();
4056 }
4057 }
4058 };
4059 PersistentConnection.prototype.onSecurityDebugPacket_ = function (body) {
4060 if (this.securityDebugCallback_) {
4061 this.securityDebugCallback_(body);
4062 }
4063 else {
4064 if ('msg' in body) {
4065 console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
4066 }
4067 }
4068 };
4069 PersistentConnection.prototype.restoreState_ = function () {
4070 var e_1, _a, e_2, _b;
4071 //Re-authenticate ourselves if we have a credential stored.
4072 this.tryAuth();
4073 this.tryAppCheck();
4074 try {
4075 // Puts depend on having received the corresponding data update from the server before they complete, so we must
4076 // make sure to send listens before puts.
4077 for (var _c = tslib.__values(this.listens.values()), _d = _c.next(); !_d.done; _d = _c.next()) {
4078 var queries = _d.value;
4079 try {
4080 for (var _e = (e_2 = void 0, tslib.__values(queries.values())), _f = _e.next(); !_f.done; _f = _e.next()) {
4081 var listenSpec = _f.value;
4082 this.sendListen_(listenSpec);
4083 }
4084 }
4085 catch (e_2_1) { e_2 = { error: e_2_1 }; }
4086 finally {
4087 try {
4088 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
4089 }
4090 finally { if (e_2) throw e_2.error; }
4091 }
4092 }
4093 }
4094 catch (e_1_1) { e_1 = { error: e_1_1 }; }
4095 finally {
4096 try {
4097 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
4098 }
4099 finally { if (e_1) throw e_1.error; }
4100 }
4101 for (var i = 0; i < this.outstandingPuts_.length; i++) {
4102 if (this.outstandingPuts_[i]) {
4103 this.sendPut_(i);
4104 }
4105 }
4106 while (this.onDisconnectRequestQueue_.length) {
4107 var request = this.onDisconnectRequestQueue_.shift();
4108 this.sendOnDisconnect_(request.action, request.pathString, request.data, request.onComplete);
4109 }
4110 for (var i = 0; i < this.outstandingGets_.length; i++) {
4111 if (this.outstandingGets_[i]) {
4112 this.sendGet_(i);
4113 }
4114 }
4115 };
4116 /**
4117 * Sends client stats for first connection
4118 */
4119 PersistentConnection.prototype.sendConnectStats_ = function () {
4120 var stats = {};
4121 var clientName = 'js';
4122 if (util.isNodeSdk()) {
4123 if (this.repoInfo_.nodeAdmin) {
4124 clientName = 'admin_node';
4125 }
4126 else {
4127 clientName = 'node';
4128 }
4129 }
4130 stats['sdk.' + clientName + '.' + SDK_VERSION.replace(/\./g, '-')] = 1;
4131 if (util.isMobileCordova()) {
4132 stats['framework.cordova'] = 1;
4133 }
4134 else if (util.isReactNative()) {
4135 stats['framework.reactnative'] = 1;
4136 }
4137 this.reportStats(stats);
4138 };
4139 PersistentConnection.prototype.shouldReconnect_ = function () {
4140 var online = OnlineMonitor.getInstance().currentlyOnline();
4141 return util.isEmpty(this.interruptReasons_) && online;
4142 };
4143 PersistentConnection.nextPersistentConnectionId_ = 0;
4144 /**
4145 * Counter for number of connections created. Mainly used for tagging in the logs
4146 */
4147 PersistentConnection.nextConnectionId_ = 0;
4148 return PersistentConnection;
4149}(ServerActions));
4150
4151/**
4152 * @license
4153 * Copyright 2017 Google LLC
4154 *
4155 * Licensed under the Apache License, Version 2.0 (the "License");
4156 * you may not use this file except in compliance with the License.
4157 * You may obtain a copy of the License at
4158 *
4159 * http://www.apache.org/licenses/LICENSE-2.0
4160 *
4161 * Unless required by applicable law or agreed to in writing, software
4162 * distributed under the License is distributed on an "AS IS" BASIS,
4163 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4164 * See the License for the specific language governing permissions and
4165 * limitations under the License.
4166 */
4167var NamedNode = /** @class */ (function () {
4168 function NamedNode(name, node) {
4169 this.name = name;
4170 this.node = node;
4171 }
4172 NamedNode.Wrap = function (name, node) {
4173 return new NamedNode(name, node);
4174 };
4175 return NamedNode;
4176}());
4177
4178/**
4179 * @license
4180 * Copyright 2017 Google LLC
4181 *
4182 * Licensed under the Apache License, Version 2.0 (the "License");
4183 * you may not use this file except in compliance with the License.
4184 * You may obtain a copy of the License at
4185 *
4186 * http://www.apache.org/licenses/LICENSE-2.0
4187 *
4188 * Unless required by applicable law or agreed to in writing, software
4189 * distributed under the License is distributed on an "AS IS" BASIS,
4190 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4191 * See the License for the specific language governing permissions and
4192 * limitations under the License.
4193 */
4194var Index = /** @class */ (function () {
4195 function Index() {
4196 }
4197 /**
4198 * @returns A standalone comparison function for
4199 * this index
4200 */
4201 Index.prototype.getCompare = function () {
4202 return this.compare.bind(this);
4203 };
4204 /**
4205 * Given a before and after value for a node, determine if the indexed value has changed. Even if they are different,
4206 * it's possible that the changes are isolated to parts of the snapshot that are not indexed.
4207 *
4208 *
4209 * @returns True if the portion of the snapshot being indexed changed between oldNode and newNode
4210 */
4211 Index.prototype.indexedValueChanged = function (oldNode, newNode) {
4212 var oldWrapped = new NamedNode(MIN_NAME, oldNode);
4213 var newWrapped = new NamedNode(MIN_NAME, newNode);
4214 return this.compare(oldWrapped, newWrapped) !== 0;
4215 };
4216 /**
4217 * @returns a node wrapper that will sort equal to or less than
4218 * any other node wrapper, using this index
4219 */
4220 Index.prototype.minPost = function () {
4221 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4222 return NamedNode.MIN;
4223 };
4224 return Index;
4225}());
4226
4227/**
4228 * @license
4229 * Copyright 2017 Google LLC
4230 *
4231 * Licensed under the Apache License, Version 2.0 (the "License");
4232 * you may not use this file except in compliance with the License.
4233 * You may obtain a copy of the License at
4234 *
4235 * http://www.apache.org/licenses/LICENSE-2.0
4236 *
4237 * Unless required by applicable law or agreed to in writing, software
4238 * distributed under the License is distributed on an "AS IS" BASIS,
4239 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4240 * See the License for the specific language governing permissions and
4241 * limitations under the License.
4242 */
4243var __EMPTY_NODE;
4244var KeyIndex = /** @class */ (function (_super) {
4245 tslib.__extends(KeyIndex, _super);
4246 function KeyIndex() {
4247 return _super !== null && _super.apply(this, arguments) || this;
4248 }
4249 Object.defineProperty(KeyIndex, "__EMPTY_NODE", {
4250 get: function () {
4251 return __EMPTY_NODE;
4252 },
4253 set: function (val) {
4254 __EMPTY_NODE = val;
4255 },
4256 enumerable: false,
4257 configurable: true
4258 });
4259 KeyIndex.prototype.compare = function (a, b) {
4260 return nameCompare(a.name, b.name);
4261 };
4262 KeyIndex.prototype.isDefinedOn = function (node) {
4263 // We could probably return true here (since every node has a key), but it's never called
4264 // so just leaving unimplemented for now.
4265 throw util.assertionError('KeyIndex.isDefinedOn not expected to be called.');
4266 };
4267 KeyIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
4268 return false; // The key for a node never changes.
4269 };
4270 KeyIndex.prototype.minPost = function () {
4271 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4272 return NamedNode.MIN;
4273 };
4274 KeyIndex.prototype.maxPost = function () {
4275 // TODO: This should really be created once and cached in a static property, but
4276 // NamedNode isn't defined yet, so I can't use it in a static. Bleh.
4277 return new NamedNode(MAX_NAME, __EMPTY_NODE);
4278 };
4279 KeyIndex.prototype.makePost = function (indexValue, name) {
4280 util.assert(typeof indexValue === 'string', 'KeyIndex indexValue must always be a string.');
4281 // We just use empty node, but it'll never be compared, since our comparator only looks at name.
4282 return new NamedNode(indexValue, __EMPTY_NODE);
4283 };
4284 /**
4285 * @returns String representation for inclusion in a query spec
4286 */
4287 KeyIndex.prototype.toString = function () {
4288 return '.key';
4289 };
4290 return KeyIndex;
4291}(Index));
4292var KEY_INDEX = new KeyIndex();
4293
4294/**
4295 * @license
4296 * Copyright 2017 Google LLC
4297 *
4298 * Licensed under the Apache License, Version 2.0 (the "License");
4299 * you may not use this file except in compliance with the License.
4300 * You may obtain a copy of the License at
4301 *
4302 * http://www.apache.org/licenses/LICENSE-2.0
4303 *
4304 * Unless required by applicable law or agreed to in writing, software
4305 * distributed under the License is distributed on an "AS IS" BASIS,
4306 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4307 * See the License for the specific language governing permissions and
4308 * limitations under the License.
4309 */
4310/**
4311 * An iterator over an LLRBNode.
4312 */
4313var SortedMapIterator = /** @class */ (function () {
4314 /**
4315 * @param node - Node to iterate.
4316 * @param isReverse_ - Whether or not to iterate in reverse
4317 */
4318 function SortedMapIterator(node, startKey, comparator, isReverse_, resultGenerator_) {
4319 if (resultGenerator_ === void 0) { resultGenerator_ = null; }
4320 this.isReverse_ = isReverse_;
4321 this.resultGenerator_ = resultGenerator_;
4322 this.nodeStack_ = [];
4323 var cmp = 1;
4324 while (!node.isEmpty()) {
4325 node = node;
4326 cmp = startKey ? comparator(node.key, startKey) : 1;
4327 // flip the comparison if we're going in reverse
4328 if (isReverse_) {
4329 cmp *= -1;
4330 }
4331 if (cmp < 0) {
4332 // This node is less than our start key. ignore it
4333 if (this.isReverse_) {
4334 node = node.left;
4335 }
4336 else {
4337 node = node.right;
4338 }
4339 }
4340 else if (cmp === 0) {
4341 // This node is exactly equal to our start key. Push it on the stack, but stop iterating;
4342 this.nodeStack_.push(node);
4343 break;
4344 }
4345 else {
4346 // This node is greater than our start key, add it to the stack and move to the next one
4347 this.nodeStack_.push(node);
4348 if (this.isReverse_) {
4349 node = node.right;
4350 }
4351 else {
4352 node = node.left;
4353 }
4354 }
4355 }
4356 }
4357 SortedMapIterator.prototype.getNext = function () {
4358 if (this.nodeStack_.length === 0) {
4359 return null;
4360 }
4361 var node = this.nodeStack_.pop();
4362 var result;
4363 if (this.resultGenerator_) {
4364 result = this.resultGenerator_(node.key, node.value);
4365 }
4366 else {
4367 result = { key: node.key, value: node.value };
4368 }
4369 if (this.isReverse_) {
4370 node = node.left;
4371 while (!node.isEmpty()) {
4372 this.nodeStack_.push(node);
4373 node = node.right;
4374 }
4375 }
4376 else {
4377 node = node.right;
4378 while (!node.isEmpty()) {
4379 this.nodeStack_.push(node);
4380 node = node.left;
4381 }
4382 }
4383 return result;
4384 };
4385 SortedMapIterator.prototype.hasNext = function () {
4386 return this.nodeStack_.length > 0;
4387 };
4388 SortedMapIterator.prototype.peek = function () {
4389 if (this.nodeStack_.length === 0) {
4390 return null;
4391 }
4392 var node = this.nodeStack_[this.nodeStack_.length - 1];
4393 if (this.resultGenerator_) {
4394 return this.resultGenerator_(node.key, node.value);
4395 }
4396 else {
4397 return { key: node.key, value: node.value };
4398 }
4399 };
4400 return SortedMapIterator;
4401}());
4402/**
4403 * Represents a node in a Left-leaning Red-Black tree.
4404 */
4405var LLRBNode = /** @class */ (function () {
4406 /**
4407 * @param key - Key associated with this node.
4408 * @param value - Value associated with this node.
4409 * @param color - Whether this node is red.
4410 * @param left - Left child.
4411 * @param right - Right child.
4412 */
4413 function LLRBNode(key, value, color, left, right) {
4414 this.key = key;
4415 this.value = value;
4416 this.color = color != null ? color : LLRBNode.RED;
4417 this.left =
4418 left != null ? left : SortedMap.EMPTY_NODE;
4419 this.right =
4420 right != null ? right : SortedMap.EMPTY_NODE;
4421 }
4422 /**
4423 * Returns a copy of the current node, optionally replacing pieces of it.
4424 *
4425 * @param key - New key for the node, or null.
4426 * @param value - New value for the node, or null.
4427 * @param color - New color for the node, or null.
4428 * @param left - New left child for the node, or null.
4429 * @param right - New right child for the node, or null.
4430 * @returns The node copy.
4431 */
4432 LLRBNode.prototype.copy = function (key, value, color, left, right) {
4433 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);
4434 };
4435 /**
4436 * @returns The total number of nodes in the tree.
4437 */
4438 LLRBNode.prototype.count = function () {
4439 return this.left.count() + 1 + this.right.count();
4440 };
4441 /**
4442 * @returns True if the tree is empty.
4443 */
4444 LLRBNode.prototype.isEmpty = function () {
4445 return false;
4446 };
4447 /**
4448 * Traverses the tree in key order and calls the specified action function
4449 * for each node.
4450 *
4451 * @param action - Callback function to be called for each
4452 * node. If it returns true, traversal is aborted.
4453 * @returns The first truthy value returned by action, or the last falsey
4454 * value returned by action
4455 */
4456 LLRBNode.prototype.inorderTraversal = function (action) {
4457 return (this.left.inorderTraversal(action) ||
4458 !!action(this.key, this.value) ||
4459 this.right.inorderTraversal(action));
4460 };
4461 /**
4462 * Traverses the tree in reverse key order and calls the specified action function
4463 * for each node.
4464 *
4465 * @param action - Callback function to be called for each
4466 * node. If it returns true, traversal is aborted.
4467 * @returns True if traversal was aborted.
4468 */
4469 LLRBNode.prototype.reverseTraversal = function (action) {
4470 return (this.right.reverseTraversal(action) ||
4471 action(this.key, this.value) ||
4472 this.left.reverseTraversal(action));
4473 };
4474 /**
4475 * @returns The minimum node in the tree.
4476 */
4477 LLRBNode.prototype.min_ = function () {
4478 if (this.left.isEmpty()) {
4479 return this;
4480 }
4481 else {
4482 return this.left.min_();
4483 }
4484 };
4485 /**
4486 * @returns The maximum key in the tree.
4487 */
4488 LLRBNode.prototype.minKey = function () {
4489 return this.min_().key;
4490 };
4491 /**
4492 * @returns The maximum key in the tree.
4493 */
4494 LLRBNode.prototype.maxKey = function () {
4495 if (this.right.isEmpty()) {
4496 return this.key;
4497 }
4498 else {
4499 return this.right.maxKey();
4500 }
4501 };
4502 /**
4503 * @param key - Key to insert.
4504 * @param value - Value to insert.
4505 * @param comparator - Comparator.
4506 * @returns New tree, with the key/value added.
4507 */
4508 LLRBNode.prototype.insert = function (key, value, comparator) {
4509 var n = this;
4510 var cmp = comparator(key, n.key);
4511 if (cmp < 0) {
4512 n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
4513 }
4514 else if (cmp === 0) {
4515 n = n.copy(null, value, null, null, null);
4516 }
4517 else {
4518 n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
4519 }
4520 return n.fixUp_();
4521 };
4522 /**
4523 * @returns New tree, with the minimum key removed.
4524 */
4525 LLRBNode.prototype.removeMin_ = function () {
4526 if (this.left.isEmpty()) {
4527 return SortedMap.EMPTY_NODE;
4528 }
4529 var n = this;
4530 if (!n.left.isRed_() && !n.left.left.isRed_()) {
4531 n = n.moveRedLeft_();
4532 }
4533 n = n.copy(null, null, null, n.left.removeMin_(), null);
4534 return n.fixUp_();
4535 };
4536 /**
4537 * @param key - The key of the item to remove.
4538 * @param comparator - Comparator.
4539 * @returns New tree, with the specified item removed.
4540 */
4541 LLRBNode.prototype.remove = function (key, comparator) {
4542 var n, smallest;
4543 n = this;
4544 if (comparator(key, n.key) < 0) {
4545 if (!n.left.isEmpty() && !n.left.isRed_() && !n.left.left.isRed_()) {
4546 n = n.moveRedLeft_();
4547 }
4548 n = n.copy(null, null, null, n.left.remove(key, comparator), null);
4549 }
4550 else {
4551 if (n.left.isRed_()) {
4552 n = n.rotateRight_();
4553 }
4554 if (!n.right.isEmpty() && !n.right.isRed_() && !n.right.left.isRed_()) {
4555 n = n.moveRedRight_();
4556 }
4557 if (comparator(key, n.key) === 0) {
4558 if (n.right.isEmpty()) {
4559 return SortedMap.EMPTY_NODE;
4560 }
4561 else {
4562 smallest = n.right.min_();
4563 n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin_());
4564 }
4565 }
4566 n = n.copy(null, null, null, null, n.right.remove(key, comparator));
4567 }
4568 return n.fixUp_();
4569 };
4570 /**
4571 * @returns Whether this is a RED node.
4572 */
4573 LLRBNode.prototype.isRed_ = function () {
4574 return this.color;
4575 };
4576 /**
4577 * @returns New tree after performing any needed rotations.
4578 */
4579 LLRBNode.prototype.fixUp_ = function () {
4580 var n = this;
4581 if (n.right.isRed_() && !n.left.isRed_()) {
4582 n = n.rotateLeft_();
4583 }
4584 if (n.left.isRed_() && n.left.left.isRed_()) {
4585 n = n.rotateRight_();
4586 }
4587 if (n.left.isRed_() && n.right.isRed_()) {
4588 n = n.colorFlip_();
4589 }
4590 return n;
4591 };
4592 /**
4593 * @returns New tree, after moveRedLeft.
4594 */
4595 LLRBNode.prototype.moveRedLeft_ = function () {
4596 var n = this.colorFlip_();
4597 if (n.right.left.isRed_()) {
4598 n = n.copy(null, null, null, null, n.right.rotateRight_());
4599 n = n.rotateLeft_();
4600 n = n.colorFlip_();
4601 }
4602 return n;
4603 };
4604 /**
4605 * @returns New tree, after moveRedRight.
4606 */
4607 LLRBNode.prototype.moveRedRight_ = function () {
4608 var n = this.colorFlip_();
4609 if (n.left.left.isRed_()) {
4610 n = n.rotateRight_();
4611 n = n.colorFlip_();
4612 }
4613 return n;
4614 };
4615 /**
4616 * @returns New tree, after rotateLeft.
4617 */
4618 LLRBNode.prototype.rotateLeft_ = function () {
4619 var nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
4620 return this.right.copy(null, null, this.color, nl, null);
4621 };
4622 /**
4623 * @returns New tree, after rotateRight.
4624 */
4625 LLRBNode.prototype.rotateRight_ = function () {
4626 var nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
4627 return this.left.copy(null, null, this.color, null, nr);
4628 };
4629 /**
4630 * @returns Newt ree, after colorFlip.
4631 */
4632 LLRBNode.prototype.colorFlip_ = function () {
4633 var left = this.left.copy(null, null, !this.left.color, null, null);
4634 var right = this.right.copy(null, null, !this.right.color, null, null);
4635 return this.copy(null, null, !this.color, left, right);
4636 };
4637 /**
4638 * For testing.
4639 *
4640 * @returns True if all is well.
4641 */
4642 LLRBNode.prototype.checkMaxDepth_ = function () {
4643 var blackDepth = this.check_();
4644 return Math.pow(2.0, blackDepth) <= this.count() + 1;
4645 };
4646 LLRBNode.prototype.check_ = function () {
4647 if (this.isRed_() && this.left.isRed_()) {
4648 throw new Error('Red node has red child(' + this.key + ',' + this.value + ')');
4649 }
4650 if (this.right.isRed_()) {
4651 throw new Error('Right child of (' + this.key + ',' + this.value + ') is red');
4652 }
4653 var blackDepth = this.left.check_();
4654 if (blackDepth !== this.right.check_()) {
4655 throw new Error('Black depths differ');
4656 }
4657 else {
4658 return blackDepth + (this.isRed_() ? 0 : 1);
4659 }
4660 };
4661 LLRBNode.RED = true;
4662 LLRBNode.BLACK = false;
4663 return LLRBNode;
4664}());
4665/**
4666 * Represents an empty node (a leaf node in the Red-Black Tree).
4667 */
4668var LLRBEmptyNode = /** @class */ (function () {
4669 function LLRBEmptyNode() {
4670 }
4671 /**
4672 * Returns a copy of the current node.
4673 *
4674 * @returns The node copy.
4675 */
4676 LLRBEmptyNode.prototype.copy = function (key, value, color, left, right) {
4677 return this;
4678 };
4679 /**
4680 * Returns a copy of the tree, with the specified key/value added.
4681 *
4682 * @param key - Key to be added.
4683 * @param value - Value to be added.
4684 * @param comparator - Comparator.
4685 * @returns New tree, with item added.
4686 */
4687 LLRBEmptyNode.prototype.insert = function (key, value, comparator) {
4688 return new LLRBNode(key, value, null);
4689 };
4690 /**
4691 * Returns a copy of the tree, with the specified key removed.
4692 *
4693 * @param key - The key to remove.
4694 * @param comparator - Comparator.
4695 * @returns New tree, with item removed.
4696 */
4697 LLRBEmptyNode.prototype.remove = function (key, comparator) {
4698 return this;
4699 };
4700 /**
4701 * @returns The total number of nodes in the tree.
4702 */
4703 LLRBEmptyNode.prototype.count = function () {
4704 return 0;
4705 };
4706 /**
4707 * @returns True if the tree is empty.
4708 */
4709 LLRBEmptyNode.prototype.isEmpty = function () {
4710 return true;
4711 };
4712 /**
4713 * Traverses the tree in key order and calls the specified action function
4714 * for each node.
4715 *
4716 * @param action - Callback function to be called for each
4717 * node. If it returns true, traversal is aborted.
4718 * @returns True if traversal was aborted.
4719 */
4720 LLRBEmptyNode.prototype.inorderTraversal = function (action) {
4721 return false;
4722 };
4723 /**
4724 * Traverses the tree in reverse key order and calls the specified action function
4725 * for each node.
4726 *
4727 * @param action - Callback function to be called for each
4728 * node. If it returns true, traversal is aborted.
4729 * @returns True if traversal was aborted.
4730 */
4731 LLRBEmptyNode.prototype.reverseTraversal = function (action) {
4732 return false;
4733 };
4734 LLRBEmptyNode.prototype.minKey = function () {
4735 return null;
4736 };
4737 LLRBEmptyNode.prototype.maxKey = function () {
4738 return null;
4739 };
4740 LLRBEmptyNode.prototype.check_ = function () {
4741 return 0;
4742 };
4743 /**
4744 * @returns Whether this node is red.
4745 */
4746 LLRBEmptyNode.prototype.isRed_ = function () {
4747 return false;
4748 };
4749 return LLRBEmptyNode;
4750}());
4751/**
4752 * An immutable sorted map implementation, based on a Left-leaning Red-Black
4753 * tree.
4754 */
4755var SortedMap = /** @class */ (function () {
4756 /**
4757 * @param comparator_ - Key comparator.
4758 * @param root_ - Optional root node for the map.
4759 */
4760 function SortedMap(comparator_, root_) {
4761 if (root_ === void 0) { root_ = SortedMap.EMPTY_NODE; }
4762 this.comparator_ = comparator_;
4763 this.root_ = root_;
4764 }
4765 /**
4766 * Returns a copy of the map, with the specified key/value added or replaced.
4767 * (TODO: We should perhaps rename this method to 'put')
4768 *
4769 * @param key - Key to be added.
4770 * @param value - Value to be added.
4771 * @returns New map, with item added.
4772 */
4773 SortedMap.prototype.insert = function (key, value) {
4774 return new SortedMap(this.comparator_, this.root_
4775 .insert(key, value, this.comparator_)
4776 .copy(null, null, LLRBNode.BLACK, null, null));
4777 };
4778 /**
4779 * Returns a copy of the map, with the specified key removed.
4780 *
4781 * @param key - The key to remove.
4782 * @returns New map, with item removed.
4783 */
4784 SortedMap.prototype.remove = function (key) {
4785 return new SortedMap(this.comparator_, this.root_
4786 .remove(key, this.comparator_)
4787 .copy(null, null, LLRBNode.BLACK, null, null));
4788 };
4789 /**
4790 * Returns the value of the node with the given key, or null.
4791 *
4792 * @param key - The key to look up.
4793 * @returns The value of the node with the given key, or null if the
4794 * key doesn't exist.
4795 */
4796 SortedMap.prototype.get = function (key) {
4797 var cmp;
4798 var node = this.root_;
4799 while (!node.isEmpty()) {
4800 cmp = this.comparator_(key, node.key);
4801 if (cmp === 0) {
4802 return node.value;
4803 }
4804 else if (cmp < 0) {
4805 node = node.left;
4806 }
4807 else if (cmp > 0) {
4808 node = node.right;
4809 }
4810 }
4811 return null;
4812 };
4813 /**
4814 * Returns the key of the item *before* the specified key, or null if key is the first item.
4815 * @param key - The key to find the predecessor of
4816 * @returns The predecessor key.
4817 */
4818 SortedMap.prototype.getPredecessorKey = function (key) {
4819 var cmp, node = this.root_, rightParent = null;
4820 while (!node.isEmpty()) {
4821 cmp = this.comparator_(key, node.key);
4822 if (cmp === 0) {
4823 if (!node.left.isEmpty()) {
4824 node = node.left;
4825 while (!node.right.isEmpty()) {
4826 node = node.right;
4827 }
4828 return node.key;
4829 }
4830 else if (rightParent) {
4831 return rightParent.key;
4832 }
4833 else {
4834 return null; // first item.
4835 }
4836 }
4837 else if (cmp < 0) {
4838 node = node.left;
4839 }
4840 else if (cmp > 0) {
4841 rightParent = node;
4842 node = node.right;
4843 }
4844 }
4845 throw new Error('Attempted to find predecessor key for a nonexistent key. What gives?');
4846 };
4847 /**
4848 * @returns True if the map is empty.
4849 */
4850 SortedMap.prototype.isEmpty = function () {
4851 return this.root_.isEmpty();
4852 };
4853 /**
4854 * @returns The total number of nodes in the map.
4855 */
4856 SortedMap.prototype.count = function () {
4857 return this.root_.count();
4858 };
4859 /**
4860 * @returns The minimum key in the map.
4861 */
4862 SortedMap.prototype.minKey = function () {
4863 return this.root_.minKey();
4864 };
4865 /**
4866 * @returns The maximum key in the map.
4867 */
4868 SortedMap.prototype.maxKey = function () {
4869 return this.root_.maxKey();
4870 };
4871 /**
4872 * Traverses the map in key order and calls the specified action function
4873 * for each key/value pair.
4874 *
4875 * @param action - Callback function to be called
4876 * for each key/value pair. If action returns true, traversal is aborted.
4877 * @returns The first truthy value returned by action, or the last falsey
4878 * value returned by action
4879 */
4880 SortedMap.prototype.inorderTraversal = function (action) {
4881 return this.root_.inorderTraversal(action);
4882 };
4883 /**
4884 * Traverses the map in reverse key order and calls the specified action function
4885 * for each key/value pair.
4886 *
4887 * @param action - Callback function to be called
4888 * for each key/value pair. If action returns true, traversal is aborted.
4889 * @returns True if the traversal was aborted.
4890 */
4891 SortedMap.prototype.reverseTraversal = function (action) {
4892 return this.root_.reverseTraversal(action);
4893 };
4894 /**
4895 * Returns an iterator over the SortedMap.
4896 * @returns The iterator.
4897 */
4898 SortedMap.prototype.getIterator = function (resultGenerator) {
4899 return new SortedMapIterator(this.root_, null, this.comparator_, false, resultGenerator);
4900 };
4901 SortedMap.prototype.getIteratorFrom = function (key, resultGenerator) {
4902 return new SortedMapIterator(this.root_, key, this.comparator_, false, resultGenerator);
4903 };
4904 SortedMap.prototype.getReverseIteratorFrom = function (key, resultGenerator) {
4905 return new SortedMapIterator(this.root_, key, this.comparator_, true, resultGenerator);
4906 };
4907 SortedMap.prototype.getReverseIterator = function (resultGenerator) {
4908 return new SortedMapIterator(this.root_, null, this.comparator_, true, resultGenerator);
4909 };
4910 /**
4911 * Always use the same empty node, to reduce memory.
4912 */
4913 SortedMap.EMPTY_NODE = new LLRBEmptyNode();
4914 return SortedMap;
4915}());
4916
4917/**
4918 * @license
4919 * Copyright 2017 Google LLC
4920 *
4921 * Licensed under the Apache License, Version 2.0 (the "License");
4922 * you may not use this file except in compliance with the License.
4923 * You may obtain a copy of the License at
4924 *
4925 * http://www.apache.org/licenses/LICENSE-2.0
4926 *
4927 * Unless required by applicable law or agreed to in writing, software
4928 * distributed under the License is distributed on an "AS IS" BASIS,
4929 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4930 * See the License for the specific language governing permissions and
4931 * limitations under the License.
4932 */
4933function NAME_ONLY_COMPARATOR(left, right) {
4934 return nameCompare(left.name, right.name);
4935}
4936function NAME_COMPARATOR(left, right) {
4937 return nameCompare(left, right);
4938}
4939
4940/**
4941 * @license
4942 * Copyright 2017 Google LLC
4943 *
4944 * Licensed under the Apache License, Version 2.0 (the "License");
4945 * you may not use this file except in compliance with the License.
4946 * You may obtain a copy of the License at
4947 *
4948 * http://www.apache.org/licenses/LICENSE-2.0
4949 *
4950 * Unless required by applicable law or agreed to in writing, software
4951 * distributed under the License is distributed on an "AS IS" BASIS,
4952 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4953 * See the License for the specific language governing permissions and
4954 * limitations under the License.
4955 */
4956var MAX_NODE;
4957function setMaxNode(val) {
4958 MAX_NODE = val;
4959}
4960var priorityHashText = function (priority) {
4961 if (typeof priority === 'number') {
4962 return 'number:' + doubleToIEEE754String(priority);
4963 }
4964 else {
4965 return 'string:' + priority;
4966 }
4967};
4968/**
4969 * Validates that a priority snapshot Node is valid.
4970 */
4971var validatePriorityNode = function (priorityNode) {
4972 if (priorityNode.isLeafNode()) {
4973 var val = priorityNode.val();
4974 util.assert(typeof val === 'string' ||
4975 typeof val === 'number' ||
4976 (typeof val === 'object' && util.contains(val, '.sv')), 'Priority must be a string or number.');
4977 }
4978 else {
4979 util.assert(priorityNode === MAX_NODE || priorityNode.isEmpty(), 'priority of unexpected type.');
4980 }
4981 // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
4982 util.assert(priorityNode === MAX_NODE || priorityNode.getPriority().isEmpty(), "Priority nodes can't have a priority of their own.");
4983};
4984
4985/**
4986 * @license
4987 * Copyright 2017 Google LLC
4988 *
4989 * Licensed under the Apache License, Version 2.0 (the "License");
4990 * you may not use this file except in compliance with the License.
4991 * You may obtain a copy of the License at
4992 *
4993 * http://www.apache.org/licenses/LICENSE-2.0
4994 *
4995 * Unless required by applicable law or agreed to in writing, software
4996 * distributed under the License is distributed on an "AS IS" BASIS,
4997 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4998 * See the License for the specific language governing permissions and
4999 * limitations under the License.
5000 */
5001var __childrenNodeConstructor;
5002/**
5003 * LeafNode is a class for storing leaf nodes in a DataSnapshot. It
5004 * implements Node and stores the value of the node (a string,
5005 * number, or boolean) accessible via getValue().
5006 */
5007var LeafNode = /** @class */ (function () {
5008 /**
5009 * @param value_ - The value to store in this leaf node. The object type is
5010 * possible in the event of a deferred value
5011 * @param priorityNode_ - The priority of this node.
5012 */
5013 function LeafNode(value_, priorityNode_) {
5014 if (priorityNode_ === void 0) { priorityNode_ = LeafNode.__childrenNodeConstructor.EMPTY_NODE; }
5015 this.value_ = value_;
5016 this.priorityNode_ = priorityNode_;
5017 this.lazyHash_ = null;
5018 util.assert(this.value_ !== undefined && this.value_ !== null, "LeafNode shouldn't be created with null/undefined value.");
5019 validatePriorityNode(this.priorityNode_);
5020 }
5021 Object.defineProperty(LeafNode, "__childrenNodeConstructor", {
5022 get: function () {
5023 return __childrenNodeConstructor;
5024 },
5025 set: function (val) {
5026 __childrenNodeConstructor = val;
5027 },
5028 enumerable: false,
5029 configurable: true
5030 });
5031 /** @inheritDoc */
5032 LeafNode.prototype.isLeafNode = function () {
5033 return true;
5034 };
5035 /** @inheritDoc */
5036 LeafNode.prototype.getPriority = function () {
5037 return this.priorityNode_;
5038 };
5039 /** @inheritDoc */
5040 LeafNode.prototype.updatePriority = function (newPriorityNode) {
5041 return new LeafNode(this.value_, newPriorityNode);
5042 };
5043 /** @inheritDoc */
5044 LeafNode.prototype.getImmediateChild = function (childName) {
5045 // Hack to treat priority as a regular child
5046 if (childName === '.priority') {
5047 return this.priorityNode_;
5048 }
5049 else {
5050 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5051 }
5052 };
5053 /** @inheritDoc */
5054 LeafNode.prototype.getChild = function (path) {
5055 if (pathIsEmpty(path)) {
5056 return this;
5057 }
5058 else if (pathGetFront(path) === '.priority') {
5059 return this.priorityNode_;
5060 }
5061 else {
5062 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5063 }
5064 };
5065 LeafNode.prototype.hasChild = function () {
5066 return false;
5067 };
5068 /** @inheritDoc */
5069 LeafNode.prototype.getPredecessorChildName = function (childName, childNode) {
5070 return null;
5071 };
5072 /** @inheritDoc */
5073 LeafNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5074 if (childName === '.priority') {
5075 return this.updatePriority(newChildNode);
5076 }
5077 else if (newChildNode.isEmpty() && childName !== '.priority') {
5078 return this;
5079 }
5080 else {
5081 return LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateImmediateChild(childName, newChildNode).updatePriority(this.priorityNode_);
5082 }
5083 };
5084 /** @inheritDoc */
5085 LeafNode.prototype.updateChild = function (path, newChildNode) {
5086 var front = pathGetFront(path);
5087 if (front === null) {
5088 return newChildNode;
5089 }
5090 else if (newChildNode.isEmpty() && front !== '.priority') {
5091 return this;
5092 }
5093 else {
5094 util.assert(front !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5095 return this.updateImmediateChild(front, LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateChild(pathPopFront(path), newChildNode));
5096 }
5097 };
5098 /** @inheritDoc */
5099 LeafNode.prototype.isEmpty = function () {
5100 return false;
5101 };
5102 /** @inheritDoc */
5103 LeafNode.prototype.numChildren = function () {
5104 return 0;
5105 };
5106 /** @inheritDoc */
5107 LeafNode.prototype.forEachChild = function (index, action) {
5108 return false;
5109 };
5110 LeafNode.prototype.val = function (exportFormat) {
5111 if (exportFormat && !this.getPriority().isEmpty()) {
5112 return {
5113 '.value': this.getValue(),
5114 '.priority': this.getPriority().val()
5115 };
5116 }
5117 else {
5118 return this.getValue();
5119 }
5120 };
5121 /** @inheritDoc */
5122 LeafNode.prototype.hash = function () {
5123 if (this.lazyHash_ === null) {
5124 var toHash = '';
5125 if (!this.priorityNode_.isEmpty()) {
5126 toHash +=
5127 'priority:' +
5128 priorityHashText(this.priorityNode_.val()) +
5129 ':';
5130 }
5131 var type = typeof this.value_;
5132 toHash += type + ':';
5133 if (type === 'number') {
5134 toHash += doubleToIEEE754String(this.value_);
5135 }
5136 else {
5137 toHash += this.value_;
5138 }
5139 this.lazyHash_ = sha1(toHash);
5140 }
5141 return this.lazyHash_;
5142 };
5143 /**
5144 * Returns the value of the leaf node.
5145 * @returns The value of the node.
5146 */
5147 LeafNode.prototype.getValue = function () {
5148 return this.value_;
5149 };
5150 LeafNode.prototype.compareTo = function (other) {
5151 if (other === LeafNode.__childrenNodeConstructor.EMPTY_NODE) {
5152 return 1;
5153 }
5154 else if (other instanceof LeafNode.__childrenNodeConstructor) {
5155 return -1;
5156 }
5157 else {
5158 util.assert(other.isLeafNode(), 'Unknown node type');
5159 return this.compareToLeafNode_(other);
5160 }
5161 };
5162 /**
5163 * Comparison specifically for two leaf nodes
5164 */
5165 LeafNode.prototype.compareToLeafNode_ = function (otherLeaf) {
5166 var otherLeafType = typeof otherLeaf.value_;
5167 var thisLeafType = typeof this.value_;
5168 var otherIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(otherLeafType);
5169 var thisIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(thisLeafType);
5170 util.assert(otherIndex >= 0, 'Unknown leaf type: ' + otherLeafType);
5171 util.assert(thisIndex >= 0, 'Unknown leaf type: ' + thisLeafType);
5172 if (otherIndex === thisIndex) {
5173 // Same type, compare values
5174 if (thisLeafType === 'object') {
5175 // Deferred value nodes are all equal, but we should also never get to this point...
5176 return 0;
5177 }
5178 else {
5179 // Note that this works because true > false, all others are number or string comparisons
5180 if (this.value_ < otherLeaf.value_) {
5181 return -1;
5182 }
5183 else if (this.value_ === otherLeaf.value_) {
5184 return 0;
5185 }
5186 else {
5187 return 1;
5188 }
5189 }
5190 }
5191 else {
5192 return thisIndex - otherIndex;
5193 }
5194 };
5195 LeafNode.prototype.withIndex = function () {
5196 return this;
5197 };
5198 LeafNode.prototype.isIndexed = function () {
5199 return true;
5200 };
5201 LeafNode.prototype.equals = function (other) {
5202 if (other === this) {
5203 return true;
5204 }
5205 else if (other.isLeafNode()) {
5206 var otherLeaf = other;
5207 return (this.value_ === otherLeaf.value_ &&
5208 this.priorityNode_.equals(otherLeaf.priorityNode_));
5209 }
5210 else {
5211 return false;
5212 }
5213 };
5214 /**
5215 * The sort order for comparing leaf nodes of different types. If two leaf nodes have
5216 * the same type, the comparison falls back to their value
5217 */
5218 LeafNode.VALUE_TYPE_ORDER = ['object', 'boolean', 'number', 'string'];
5219 return LeafNode;
5220}());
5221
5222/**
5223 * @license
5224 * Copyright 2017 Google LLC
5225 *
5226 * Licensed under the Apache License, Version 2.0 (the "License");
5227 * you may not use this file except in compliance with the License.
5228 * You may obtain a copy of the License at
5229 *
5230 * http://www.apache.org/licenses/LICENSE-2.0
5231 *
5232 * Unless required by applicable law or agreed to in writing, software
5233 * distributed under the License is distributed on an "AS IS" BASIS,
5234 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5235 * See the License for the specific language governing permissions and
5236 * limitations under the License.
5237 */
5238var nodeFromJSON;
5239var MAX_NODE$1;
5240function setNodeFromJSON(val) {
5241 nodeFromJSON = val;
5242}
5243function setMaxNode$1(val) {
5244 MAX_NODE$1 = val;
5245}
5246var PriorityIndex = /** @class */ (function (_super) {
5247 tslib.__extends(PriorityIndex, _super);
5248 function PriorityIndex() {
5249 return _super !== null && _super.apply(this, arguments) || this;
5250 }
5251 PriorityIndex.prototype.compare = function (a, b) {
5252 var aPriority = a.node.getPriority();
5253 var bPriority = b.node.getPriority();
5254 var indexCmp = aPriority.compareTo(bPriority);
5255 if (indexCmp === 0) {
5256 return nameCompare(a.name, b.name);
5257 }
5258 else {
5259 return indexCmp;
5260 }
5261 };
5262 PriorityIndex.prototype.isDefinedOn = function (node) {
5263 return !node.getPriority().isEmpty();
5264 };
5265 PriorityIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
5266 return !oldNode.getPriority().equals(newNode.getPriority());
5267 };
5268 PriorityIndex.prototype.minPost = function () {
5269 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5270 return NamedNode.MIN;
5271 };
5272 PriorityIndex.prototype.maxPost = function () {
5273 return new NamedNode(MAX_NAME, new LeafNode('[PRIORITY-POST]', MAX_NODE$1));
5274 };
5275 PriorityIndex.prototype.makePost = function (indexValue, name) {
5276 var priorityNode = nodeFromJSON(indexValue);
5277 return new NamedNode(name, new LeafNode('[PRIORITY-POST]', priorityNode));
5278 };
5279 /**
5280 * @returns String representation for inclusion in a query spec
5281 */
5282 PriorityIndex.prototype.toString = function () {
5283 return '.priority';
5284 };
5285 return PriorityIndex;
5286}(Index));
5287var PRIORITY_INDEX = new PriorityIndex();
5288
5289/**
5290 * @license
5291 * Copyright 2017 Google LLC
5292 *
5293 * Licensed under the Apache License, Version 2.0 (the "License");
5294 * you may not use this file except in compliance with the License.
5295 * You may obtain a copy of the License at
5296 *
5297 * http://www.apache.org/licenses/LICENSE-2.0
5298 *
5299 * Unless required by applicable law or agreed to in writing, software
5300 * distributed under the License is distributed on an "AS IS" BASIS,
5301 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5302 * See the License for the specific language governing permissions and
5303 * limitations under the License.
5304 */
5305var LOG_2 = Math.log(2);
5306var Base12Num = /** @class */ (function () {
5307 function Base12Num(length) {
5308 var logBase2 = function (num) {
5309 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5310 return parseInt((Math.log(num) / LOG_2), 10);
5311 };
5312 var bitMask = function (bits) { return parseInt(Array(bits + 1).join('1'), 2); };
5313 this.count = logBase2(length + 1);
5314 this.current_ = this.count - 1;
5315 var mask = bitMask(this.count);
5316 this.bits_ = (length + 1) & mask;
5317 }
5318 Base12Num.prototype.nextBitIsOne = function () {
5319 //noinspection JSBitwiseOperatorUsage
5320 var result = !(this.bits_ & (0x1 << this.current_));
5321 this.current_--;
5322 return result;
5323 };
5324 return Base12Num;
5325}());
5326/**
5327 * Takes a list of child nodes and constructs a SortedSet using the given comparison
5328 * function
5329 *
5330 * Uses the algorithm described in the paper linked here:
5331 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458
5332 *
5333 * @param childList - Unsorted list of children
5334 * @param cmp - The comparison method to be used
5335 * @param keyFn - An optional function to extract K from a node wrapper, if K's
5336 * type is not NamedNode
5337 * @param mapSortFn - An optional override for comparator used by the generated sorted map
5338 */
5339var buildChildSet = function (childList, cmp, keyFn, mapSortFn) {
5340 childList.sort(cmp);
5341 var buildBalancedTree = function (low, high) {
5342 var length = high - low;
5343 var namedNode;
5344 var key;
5345 if (length === 0) {
5346 return null;
5347 }
5348 else if (length === 1) {
5349 namedNode = childList[low];
5350 key = keyFn ? keyFn(namedNode) : namedNode;
5351 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, null, null);
5352 }
5353 else {
5354 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5355 var middle = parseInt((length / 2), 10) + low;
5356 var left = buildBalancedTree(low, middle);
5357 var right = buildBalancedTree(middle + 1, high);
5358 namedNode = childList[middle];
5359 key = keyFn ? keyFn(namedNode) : namedNode;
5360 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, left, right);
5361 }
5362 };
5363 var buildFrom12Array = function (base12) {
5364 var node = null;
5365 var root = null;
5366 var index = childList.length;
5367 var buildPennant = function (chunkSize, color) {
5368 var low = index - chunkSize;
5369 var high = index;
5370 index -= chunkSize;
5371 var childTree = buildBalancedTree(low + 1, high);
5372 var namedNode = childList[low];
5373 var key = keyFn ? keyFn(namedNode) : namedNode;
5374 attachPennant(new LLRBNode(key, namedNode.node, color, null, childTree));
5375 };
5376 var attachPennant = function (pennant) {
5377 if (node) {
5378 node.left = pennant;
5379 node = pennant;
5380 }
5381 else {
5382 root = pennant;
5383 node = pennant;
5384 }
5385 };
5386 for (var i = 0; i < base12.count; ++i) {
5387 var isOne = base12.nextBitIsOne();
5388 // The number of nodes taken in each slice is 2^(arr.length - (i + 1))
5389 var chunkSize = Math.pow(2, base12.count - (i + 1));
5390 if (isOne) {
5391 buildPennant(chunkSize, LLRBNode.BLACK);
5392 }
5393 else {
5394 // current == 2
5395 buildPennant(chunkSize, LLRBNode.BLACK);
5396 buildPennant(chunkSize, LLRBNode.RED);
5397 }
5398 }
5399 return root;
5400 };
5401 var base12 = new Base12Num(childList.length);
5402 var root = buildFrom12Array(base12);
5403 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5404 return new SortedMap(mapSortFn || cmp, root);
5405};
5406
5407/**
5408 * @license
5409 * Copyright 2017 Google LLC
5410 *
5411 * Licensed under the Apache License, Version 2.0 (the "License");
5412 * you may not use this file except in compliance with the License.
5413 * You may obtain a copy of the License at
5414 *
5415 * http://www.apache.org/licenses/LICENSE-2.0
5416 *
5417 * Unless required by applicable law or agreed to in writing, software
5418 * distributed under the License is distributed on an "AS IS" BASIS,
5419 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5420 * See the License for the specific language governing permissions and
5421 * limitations under the License.
5422 */
5423var _defaultIndexMap;
5424var fallbackObject = {};
5425var IndexMap = /** @class */ (function () {
5426 function IndexMap(indexes_, indexSet_) {
5427 this.indexes_ = indexes_;
5428 this.indexSet_ = indexSet_;
5429 }
5430 Object.defineProperty(IndexMap, "Default", {
5431 /**
5432 * The default IndexMap for nodes without a priority
5433 */
5434 get: function () {
5435 util.assert(fallbackObject && PRIORITY_INDEX, 'ChildrenNode.ts has not been loaded');
5436 _defaultIndexMap =
5437 _defaultIndexMap ||
5438 new IndexMap({ '.priority': fallbackObject }, { '.priority': PRIORITY_INDEX });
5439 return _defaultIndexMap;
5440 },
5441 enumerable: false,
5442 configurable: true
5443 });
5444 IndexMap.prototype.get = function (indexKey) {
5445 var sortedMap = util.safeGet(this.indexes_, indexKey);
5446 if (!sortedMap) {
5447 throw new Error('No index defined for ' + indexKey);
5448 }
5449 if (sortedMap instanceof SortedMap) {
5450 return sortedMap;
5451 }
5452 else {
5453 // The index exists, but it falls back to just name comparison. Return null so that the calling code uses the
5454 // regular child map
5455 return null;
5456 }
5457 };
5458 IndexMap.prototype.hasIndex = function (indexDefinition) {
5459 return util.contains(this.indexSet_, indexDefinition.toString());
5460 };
5461 IndexMap.prototype.addIndex = function (indexDefinition, existingChildren) {
5462 util.assert(indexDefinition !== KEY_INDEX, "KeyIndex always exists and isn't meant to be added to the IndexMap.");
5463 var childList = [];
5464 var sawIndexedValue = false;
5465 var iter = existingChildren.getIterator(NamedNode.Wrap);
5466 var next = iter.getNext();
5467 while (next) {
5468 sawIndexedValue =
5469 sawIndexedValue || indexDefinition.isDefinedOn(next.node);
5470 childList.push(next);
5471 next = iter.getNext();
5472 }
5473 var newIndex;
5474 if (sawIndexedValue) {
5475 newIndex = buildChildSet(childList, indexDefinition.getCompare());
5476 }
5477 else {
5478 newIndex = fallbackObject;
5479 }
5480 var indexName = indexDefinition.toString();
5481 var newIndexSet = tslib.__assign({}, this.indexSet_);
5482 newIndexSet[indexName] = indexDefinition;
5483 var newIndexes = tslib.__assign({}, this.indexes_);
5484 newIndexes[indexName] = newIndex;
5485 return new IndexMap(newIndexes, newIndexSet);
5486 };
5487 /**
5488 * Ensure that this node is properly tracked in any indexes that we're maintaining
5489 */
5490 IndexMap.prototype.addToIndexes = function (namedNode, existingChildren) {
5491 var _this = this;
5492 var newIndexes = util.map(this.indexes_, function (indexedChildren, indexName) {
5493 var index = util.safeGet(_this.indexSet_, indexName);
5494 util.assert(index, 'Missing index implementation for ' + indexName);
5495 if (indexedChildren === fallbackObject) {
5496 // Check to see if we need to index everything
5497 if (index.isDefinedOn(namedNode.node)) {
5498 // We need to build this index
5499 var childList = [];
5500 var iter = existingChildren.getIterator(NamedNode.Wrap);
5501 var next = iter.getNext();
5502 while (next) {
5503 if (next.name !== namedNode.name) {
5504 childList.push(next);
5505 }
5506 next = iter.getNext();
5507 }
5508 childList.push(namedNode);
5509 return buildChildSet(childList, index.getCompare());
5510 }
5511 else {
5512 // No change, this remains a fallback
5513 return fallbackObject;
5514 }
5515 }
5516 else {
5517 var existingSnap = existingChildren.get(namedNode.name);
5518 var newChildren = indexedChildren;
5519 if (existingSnap) {
5520 newChildren = newChildren.remove(new NamedNode(namedNode.name, existingSnap));
5521 }
5522 return newChildren.insert(namedNode, namedNode.node);
5523 }
5524 });
5525 return new IndexMap(newIndexes, this.indexSet_);
5526 };
5527 /**
5528 * Create a new IndexMap instance with the given value removed
5529 */
5530 IndexMap.prototype.removeFromIndexes = function (namedNode, existingChildren) {
5531 var newIndexes = util.map(this.indexes_, function (indexedChildren) {
5532 if (indexedChildren === fallbackObject) {
5533 // This is the fallback. Just return it, nothing to do in this case
5534 return indexedChildren;
5535 }
5536 else {
5537 var existingSnap = existingChildren.get(namedNode.name);
5538 if (existingSnap) {
5539 return indexedChildren.remove(new NamedNode(namedNode.name, existingSnap));
5540 }
5541 else {
5542 // No record of this child
5543 return indexedChildren;
5544 }
5545 }
5546 });
5547 return new IndexMap(newIndexes, this.indexSet_);
5548 };
5549 return IndexMap;
5550}());
5551
5552/**
5553 * @license
5554 * Copyright 2017 Google LLC
5555 *
5556 * Licensed under the Apache License, Version 2.0 (the "License");
5557 * you may not use this file except in compliance with the License.
5558 * You may obtain a copy of the License at
5559 *
5560 * http://www.apache.org/licenses/LICENSE-2.0
5561 *
5562 * Unless required by applicable law or agreed to in writing, software
5563 * distributed under the License is distributed on an "AS IS" BASIS,
5564 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5565 * See the License for the specific language governing permissions and
5566 * limitations under the License.
5567 */
5568// TODO: For memory savings, don't store priorityNode_ if it's empty.
5569var EMPTY_NODE;
5570/**
5571 * ChildrenNode is a class for storing internal nodes in a DataSnapshot
5572 * (i.e. nodes with children). It implements Node and stores the
5573 * list of children in the children property, sorted by child name.
5574 */
5575var ChildrenNode = /** @class */ (function () {
5576 /**
5577 * @param children_ - List of children of this node..
5578 * @param priorityNode_ - The priority of this node (as a snapshot node).
5579 */
5580 function ChildrenNode(children_, priorityNode_, indexMap_) {
5581 this.children_ = children_;
5582 this.priorityNode_ = priorityNode_;
5583 this.indexMap_ = indexMap_;
5584 this.lazyHash_ = null;
5585 /**
5586 * Note: The only reason we allow null priority is for EMPTY_NODE, since we can't use
5587 * EMPTY_NODE as the priority of EMPTY_NODE. We might want to consider making EMPTY_NODE its own
5588 * class instead of an empty ChildrenNode.
5589 */
5590 if (this.priorityNode_) {
5591 validatePriorityNode(this.priorityNode_);
5592 }
5593 if (this.children_.isEmpty()) {
5594 util.assert(!this.priorityNode_ || this.priorityNode_.isEmpty(), 'An empty node cannot have a priority');
5595 }
5596 }
5597 Object.defineProperty(ChildrenNode, "EMPTY_NODE", {
5598 get: function () {
5599 return (EMPTY_NODE ||
5600 (EMPTY_NODE = new ChildrenNode(new SortedMap(NAME_COMPARATOR), null, IndexMap.Default)));
5601 },
5602 enumerable: false,
5603 configurable: true
5604 });
5605 /** @inheritDoc */
5606 ChildrenNode.prototype.isLeafNode = function () {
5607 return false;
5608 };
5609 /** @inheritDoc */
5610 ChildrenNode.prototype.getPriority = function () {
5611 return this.priorityNode_ || EMPTY_NODE;
5612 };
5613 /** @inheritDoc */
5614 ChildrenNode.prototype.updatePriority = function (newPriorityNode) {
5615 if (this.children_.isEmpty()) {
5616 // Don't allow priorities on empty nodes
5617 return this;
5618 }
5619 else {
5620 return new ChildrenNode(this.children_, newPriorityNode, this.indexMap_);
5621 }
5622 };
5623 /** @inheritDoc */
5624 ChildrenNode.prototype.getImmediateChild = function (childName) {
5625 // Hack to treat priority as a regular child
5626 if (childName === '.priority') {
5627 return this.getPriority();
5628 }
5629 else {
5630 var child = this.children_.get(childName);
5631 return child === null ? EMPTY_NODE : child;
5632 }
5633 };
5634 /** @inheritDoc */
5635 ChildrenNode.prototype.getChild = function (path) {
5636 var front = pathGetFront(path);
5637 if (front === null) {
5638 return this;
5639 }
5640 return this.getImmediateChild(front).getChild(pathPopFront(path));
5641 };
5642 /** @inheritDoc */
5643 ChildrenNode.prototype.hasChild = function (childName) {
5644 return this.children_.get(childName) !== null;
5645 };
5646 /** @inheritDoc */
5647 ChildrenNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5648 util.assert(newChildNode, 'We should always be passing snapshot nodes');
5649 if (childName === '.priority') {
5650 return this.updatePriority(newChildNode);
5651 }
5652 else {
5653 var namedNode = new NamedNode(childName, newChildNode);
5654 var newChildren = void 0, newIndexMap = void 0;
5655 if (newChildNode.isEmpty()) {
5656 newChildren = this.children_.remove(childName);
5657 newIndexMap = this.indexMap_.removeFromIndexes(namedNode, this.children_);
5658 }
5659 else {
5660 newChildren = this.children_.insert(childName, newChildNode);
5661 newIndexMap = this.indexMap_.addToIndexes(namedNode, this.children_);
5662 }
5663 var newPriority = newChildren.isEmpty()
5664 ? EMPTY_NODE
5665 : this.priorityNode_;
5666 return new ChildrenNode(newChildren, newPriority, newIndexMap);
5667 }
5668 };
5669 /** @inheritDoc */
5670 ChildrenNode.prototype.updateChild = function (path, newChildNode) {
5671 var front = pathGetFront(path);
5672 if (front === null) {
5673 return newChildNode;
5674 }
5675 else {
5676 util.assert(pathGetFront(path) !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5677 var newImmediateChild = this.getImmediateChild(front).updateChild(pathPopFront(path), newChildNode);
5678 return this.updateImmediateChild(front, newImmediateChild);
5679 }
5680 };
5681 /** @inheritDoc */
5682 ChildrenNode.prototype.isEmpty = function () {
5683 return this.children_.isEmpty();
5684 };
5685 /** @inheritDoc */
5686 ChildrenNode.prototype.numChildren = function () {
5687 return this.children_.count();
5688 };
5689 /** @inheritDoc */
5690 ChildrenNode.prototype.val = function (exportFormat) {
5691 if (this.isEmpty()) {
5692 return null;
5693 }
5694 var obj = {};
5695 var numKeys = 0, maxKey = 0, allIntegerKeys = true;
5696 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5697 obj[key] = childNode.val(exportFormat);
5698 numKeys++;
5699 if (allIntegerKeys && ChildrenNode.INTEGER_REGEXP_.test(key)) {
5700 maxKey = Math.max(maxKey, Number(key));
5701 }
5702 else {
5703 allIntegerKeys = false;
5704 }
5705 });
5706 if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
5707 // convert to array.
5708 var array = [];
5709 // eslint-disable-next-line guard-for-in
5710 for (var key in obj) {
5711 array[key] = obj[key];
5712 }
5713 return array;
5714 }
5715 else {
5716 if (exportFormat && !this.getPriority().isEmpty()) {
5717 obj['.priority'] = this.getPriority().val();
5718 }
5719 return obj;
5720 }
5721 };
5722 /** @inheritDoc */
5723 ChildrenNode.prototype.hash = function () {
5724 if (this.lazyHash_ === null) {
5725 var toHash_1 = '';
5726 if (!this.getPriority().isEmpty()) {
5727 toHash_1 +=
5728 'priority:' +
5729 priorityHashText(this.getPriority().val()) +
5730 ':';
5731 }
5732 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5733 var childHash = childNode.hash();
5734 if (childHash !== '') {
5735 toHash_1 += ':' + key + ':' + childHash;
5736 }
5737 });
5738 this.lazyHash_ = toHash_1 === '' ? '' : sha1(toHash_1);
5739 }
5740 return this.lazyHash_;
5741 };
5742 /** @inheritDoc */
5743 ChildrenNode.prototype.getPredecessorChildName = function (childName, childNode, index) {
5744 var idx = this.resolveIndex_(index);
5745 if (idx) {
5746 var predecessor = idx.getPredecessorKey(new NamedNode(childName, childNode));
5747 return predecessor ? predecessor.name : null;
5748 }
5749 else {
5750 return this.children_.getPredecessorKey(childName);
5751 }
5752 };
5753 ChildrenNode.prototype.getFirstChildName = function (indexDefinition) {
5754 var idx = this.resolveIndex_(indexDefinition);
5755 if (idx) {
5756 var minKey = idx.minKey();
5757 return minKey && minKey.name;
5758 }
5759 else {
5760 return this.children_.minKey();
5761 }
5762 };
5763 ChildrenNode.prototype.getFirstChild = function (indexDefinition) {
5764 var minKey = this.getFirstChildName(indexDefinition);
5765 if (minKey) {
5766 return new NamedNode(minKey, this.children_.get(minKey));
5767 }
5768 else {
5769 return null;
5770 }
5771 };
5772 /**
5773 * Given an index, return the key name of the largest value we have, according to that index
5774 */
5775 ChildrenNode.prototype.getLastChildName = function (indexDefinition) {
5776 var idx = this.resolveIndex_(indexDefinition);
5777 if (idx) {
5778 var maxKey = idx.maxKey();
5779 return maxKey && maxKey.name;
5780 }
5781 else {
5782 return this.children_.maxKey();
5783 }
5784 };
5785 ChildrenNode.prototype.getLastChild = function (indexDefinition) {
5786 var maxKey = this.getLastChildName(indexDefinition);
5787 if (maxKey) {
5788 return new NamedNode(maxKey, this.children_.get(maxKey));
5789 }
5790 else {
5791 return null;
5792 }
5793 };
5794 ChildrenNode.prototype.forEachChild = function (index, action) {
5795 var idx = this.resolveIndex_(index);
5796 if (idx) {
5797 return idx.inorderTraversal(function (wrappedNode) {
5798 return action(wrappedNode.name, wrappedNode.node);
5799 });
5800 }
5801 else {
5802 return this.children_.inorderTraversal(action);
5803 }
5804 };
5805 ChildrenNode.prototype.getIterator = function (indexDefinition) {
5806 return this.getIteratorFrom(indexDefinition.minPost(), indexDefinition);
5807 };
5808 ChildrenNode.prototype.getIteratorFrom = function (startPost, indexDefinition) {
5809 var idx = this.resolveIndex_(indexDefinition);
5810 if (idx) {
5811 return idx.getIteratorFrom(startPost, function (key) { return key; });
5812 }
5813 else {
5814 var iterator = this.children_.getIteratorFrom(startPost.name, NamedNode.Wrap);
5815 var next = iterator.peek();
5816 while (next != null && indexDefinition.compare(next, startPost) < 0) {
5817 iterator.getNext();
5818 next = iterator.peek();
5819 }
5820 return iterator;
5821 }
5822 };
5823 ChildrenNode.prototype.getReverseIterator = function (indexDefinition) {
5824 return this.getReverseIteratorFrom(indexDefinition.maxPost(), indexDefinition);
5825 };
5826 ChildrenNode.prototype.getReverseIteratorFrom = function (endPost, indexDefinition) {
5827 var idx = this.resolveIndex_(indexDefinition);
5828 if (idx) {
5829 return idx.getReverseIteratorFrom(endPost, function (key) {
5830 return key;
5831 });
5832 }
5833 else {
5834 var iterator = this.children_.getReverseIteratorFrom(endPost.name, NamedNode.Wrap);
5835 var next = iterator.peek();
5836 while (next != null && indexDefinition.compare(next, endPost) > 0) {
5837 iterator.getNext();
5838 next = iterator.peek();
5839 }
5840 return iterator;
5841 }
5842 };
5843 ChildrenNode.prototype.compareTo = function (other) {
5844 if (this.isEmpty()) {
5845 if (other.isEmpty()) {
5846 return 0;
5847 }
5848 else {
5849 return -1;
5850 }
5851 }
5852 else if (other.isLeafNode() || other.isEmpty()) {
5853 return 1;
5854 }
5855 else if (other === MAX_NODE$2) {
5856 return -1;
5857 }
5858 else {
5859 // Must be another node with children.
5860 return 0;
5861 }
5862 };
5863 ChildrenNode.prototype.withIndex = function (indexDefinition) {
5864 if (indexDefinition === KEY_INDEX ||
5865 this.indexMap_.hasIndex(indexDefinition)) {
5866 return this;
5867 }
5868 else {
5869 var newIndexMap = this.indexMap_.addIndex(indexDefinition, this.children_);
5870 return new ChildrenNode(this.children_, this.priorityNode_, newIndexMap);
5871 }
5872 };
5873 ChildrenNode.prototype.isIndexed = function (index) {
5874 return index === KEY_INDEX || this.indexMap_.hasIndex(index);
5875 };
5876 ChildrenNode.prototype.equals = function (other) {
5877 if (other === this) {
5878 return true;
5879 }
5880 else if (other.isLeafNode()) {
5881 return false;
5882 }
5883 else {
5884 var otherChildrenNode = other;
5885 if (!this.getPriority().equals(otherChildrenNode.getPriority())) {
5886 return false;
5887 }
5888 else if (this.children_.count() === otherChildrenNode.children_.count()) {
5889 var thisIter = this.getIterator(PRIORITY_INDEX);
5890 var otherIter = otherChildrenNode.getIterator(PRIORITY_INDEX);
5891 var thisCurrent = thisIter.getNext();
5892 var otherCurrent = otherIter.getNext();
5893 while (thisCurrent && otherCurrent) {
5894 if (thisCurrent.name !== otherCurrent.name ||
5895 !thisCurrent.node.equals(otherCurrent.node)) {
5896 return false;
5897 }
5898 thisCurrent = thisIter.getNext();
5899 otherCurrent = otherIter.getNext();
5900 }
5901 return thisCurrent === null && otherCurrent === null;
5902 }
5903 else {
5904 return false;
5905 }
5906 }
5907 };
5908 /**
5909 * Returns a SortedMap ordered by index, or null if the default (by-key) ordering can be used
5910 * instead.
5911 *
5912 */
5913 ChildrenNode.prototype.resolveIndex_ = function (indexDefinition) {
5914 if (indexDefinition === KEY_INDEX) {
5915 return null;
5916 }
5917 else {
5918 return this.indexMap_.get(indexDefinition.toString());
5919 }
5920 };
5921 ChildrenNode.INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;
5922 return ChildrenNode;
5923}());
5924var MaxNode = /** @class */ (function (_super) {
5925 tslib.__extends(MaxNode, _super);
5926 function MaxNode() {
5927 return _super.call(this, new SortedMap(NAME_COMPARATOR), ChildrenNode.EMPTY_NODE, IndexMap.Default) || this;
5928 }
5929 MaxNode.prototype.compareTo = function (other) {
5930 if (other === this) {
5931 return 0;
5932 }
5933 else {
5934 return 1;
5935 }
5936 };
5937 MaxNode.prototype.equals = function (other) {
5938 // Not that we every compare it, but MAX_NODE is only ever equal to itself
5939 return other === this;
5940 };
5941 MaxNode.prototype.getPriority = function () {
5942 return this;
5943 };
5944 MaxNode.prototype.getImmediateChild = function (childName) {
5945 return ChildrenNode.EMPTY_NODE;
5946 };
5947 MaxNode.prototype.isEmpty = function () {
5948 return false;
5949 };
5950 return MaxNode;
5951}(ChildrenNode));
5952/**
5953 * Marker that will sort higher than any other snapshot.
5954 */
5955var MAX_NODE$2 = new MaxNode();
5956Object.defineProperties(NamedNode, {
5957 MIN: {
5958 value: new NamedNode(MIN_NAME, ChildrenNode.EMPTY_NODE)
5959 },
5960 MAX: {
5961 value: new NamedNode(MAX_NAME, MAX_NODE$2)
5962 }
5963});
5964/**
5965 * Reference Extensions
5966 */
5967KeyIndex.__EMPTY_NODE = ChildrenNode.EMPTY_NODE;
5968LeafNode.__childrenNodeConstructor = ChildrenNode;
5969setMaxNode(MAX_NODE$2);
5970setMaxNode$1(MAX_NODE$2);
5971
5972/**
5973 * @license
5974 * Copyright 2017 Google LLC
5975 *
5976 * Licensed under the Apache License, Version 2.0 (the "License");
5977 * you may not use this file except in compliance with the License.
5978 * You may obtain a copy of the License at
5979 *
5980 * http://www.apache.org/licenses/LICENSE-2.0
5981 *
5982 * Unless required by applicable law or agreed to in writing, software
5983 * distributed under the License is distributed on an "AS IS" BASIS,
5984 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5985 * See the License for the specific language governing permissions and
5986 * limitations under the License.
5987 */
5988var USE_HINZE = true;
5989/**
5990 * Constructs a snapshot node representing the passed JSON and returns it.
5991 * @param json - JSON to create a node for.
5992 * @param priority - Optional priority to use. This will be ignored if the
5993 * passed JSON contains a .priority property.
5994 */
5995function nodeFromJSON$1(json, priority) {
5996 if (priority === void 0) { priority = null; }
5997 if (json === null) {
5998 return ChildrenNode.EMPTY_NODE;
5999 }
6000 if (typeof json === 'object' && '.priority' in json) {
6001 priority = json['.priority'];
6002 }
6003 util.assert(priority === null ||
6004 typeof priority === 'string' ||
6005 typeof priority === 'number' ||
6006 (typeof priority === 'object' && '.sv' in priority), 'Invalid priority type found: ' + typeof priority);
6007 if (typeof json === 'object' && '.value' in json && json['.value'] !== null) {
6008 json = json['.value'];
6009 }
6010 // Valid leaf nodes include non-objects or server-value wrapper objects
6011 if (typeof json !== 'object' || '.sv' in json) {
6012 var jsonLeaf = json;
6013 return new LeafNode(jsonLeaf, nodeFromJSON$1(priority));
6014 }
6015 if (!(json instanceof Array) && USE_HINZE) {
6016 var children_1 = [];
6017 var childrenHavePriority_1 = false;
6018 var hinzeJsonObj = json;
6019 each(hinzeJsonObj, function (key, child) {
6020 if (key.substring(0, 1) !== '.') {
6021 // Ignore metadata nodes
6022 var childNode = nodeFromJSON$1(child);
6023 if (!childNode.isEmpty()) {
6024 childrenHavePriority_1 =
6025 childrenHavePriority_1 || !childNode.getPriority().isEmpty();
6026 children_1.push(new NamedNode(key, childNode));
6027 }
6028 }
6029 });
6030 if (children_1.length === 0) {
6031 return ChildrenNode.EMPTY_NODE;
6032 }
6033 var childSet = buildChildSet(children_1, NAME_ONLY_COMPARATOR, function (namedNode) { return namedNode.name; }, NAME_COMPARATOR);
6034 if (childrenHavePriority_1) {
6035 var sortedChildSet = buildChildSet(children_1, PRIORITY_INDEX.getCompare());
6036 return new ChildrenNode(childSet, nodeFromJSON$1(priority), new IndexMap({ '.priority': sortedChildSet }, { '.priority': PRIORITY_INDEX }));
6037 }
6038 else {
6039 return new ChildrenNode(childSet, nodeFromJSON$1(priority), IndexMap.Default);
6040 }
6041 }
6042 else {
6043 var node_1 = ChildrenNode.EMPTY_NODE;
6044 each(json, function (key, childData) {
6045 if (util.contains(json, key)) {
6046 if (key.substring(0, 1) !== '.') {
6047 // ignore metadata nodes.
6048 var childNode = nodeFromJSON$1(childData);
6049 if (childNode.isLeafNode() || !childNode.isEmpty()) {
6050 node_1 = node_1.updateImmediateChild(key, childNode);
6051 }
6052 }
6053 }
6054 });
6055 return node_1.updatePriority(nodeFromJSON$1(priority));
6056 }
6057}
6058setNodeFromJSON(nodeFromJSON$1);
6059
6060/**
6061 * @license
6062 * Copyright 2017 Google LLC
6063 *
6064 * Licensed under the Apache License, Version 2.0 (the "License");
6065 * you may not use this file except in compliance with the License.
6066 * You may obtain a copy of the License at
6067 *
6068 * http://www.apache.org/licenses/LICENSE-2.0
6069 *
6070 * Unless required by applicable law or agreed to in writing, software
6071 * distributed under the License is distributed on an "AS IS" BASIS,
6072 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6073 * See the License for the specific language governing permissions and
6074 * limitations under the License.
6075 */
6076var PathIndex = /** @class */ (function (_super) {
6077 tslib.__extends(PathIndex, _super);
6078 function PathIndex(indexPath_) {
6079 var _this = _super.call(this) || this;
6080 _this.indexPath_ = indexPath_;
6081 util.assert(!pathIsEmpty(indexPath_) && pathGetFront(indexPath_) !== '.priority', "Can't create PathIndex with empty path or .priority key");
6082 return _this;
6083 }
6084 PathIndex.prototype.extractChild = function (snap) {
6085 return snap.getChild(this.indexPath_);
6086 };
6087 PathIndex.prototype.isDefinedOn = function (node) {
6088 return !node.getChild(this.indexPath_).isEmpty();
6089 };
6090 PathIndex.prototype.compare = function (a, b) {
6091 var aChild = this.extractChild(a.node);
6092 var bChild = this.extractChild(b.node);
6093 var indexCmp = aChild.compareTo(bChild);
6094 if (indexCmp === 0) {
6095 return nameCompare(a.name, b.name);
6096 }
6097 else {
6098 return indexCmp;
6099 }
6100 };
6101 PathIndex.prototype.makePost = function (indexValue, name) {
6102 var valueNode = nodeFromJSON$1(indexValue);
6103 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, valueNode);
6104 return new NamedNode(name, node);
6105 };
6106 PathIndex.prototype.maxPost = function () {
6107 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, MAX_NODE$2);
6108 return new NamedNode(MAX_NAME, node);
6109 };
6110 PathIndex.prototype.toString = function () {
6111 return pathSlice(this.indexPath_, 0).join('/');
6112 };
6113 return PathIndex;
6114}(Index));
6115
6116/**
6117 * @license
6118 * Copyright 2017 Google LLC
6119 *
6120 * Licensed under the Apache License, Version 2.0 (the "License");
6121 * you may not use this file except in compliance with the License.
6122 * You may obtain a copy of the License at
6123 *
6124 * http://www.apache.org/licenses/LICENSE-2.0
6125 *
6126 * Unless required by applicable law or agreed to in writing, software
6127 * distributed under the License is distributed on an "AS IS" BASIS,
6128 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6129 * See the License for the specific language governing permissions and
6130 * limitations under the License.
6131 */
6132var ValueIndex = /** @class */ (function (_super) {
6133 tslib.__extends(ValueIndex, _super);
6134 function ValueIndex() {
6135 return _super !== null && _super.apply(this, arguments) || this;
6136 }
6137 ValueIndex.prototype.compare = function (a, b) {
6138 var indexCmp = a.node.compareTo(b.node);
6139 if (indexCmp === 0) {
6140 return nameCompare(a.name, b.name);
6141 }
6142 else {
6143 return indexCmp;
6144 }
6145 };
6146 ValueIndex.prototype.isDefinedOn = function (node) {
6147 return true;
6148 };
6149 ValueIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
6150 return !oldNode.equals(newNode);
6151 };
6152 ValueIndex.prototype.minPost = function () {
6153 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6154 return NamedNode.MIN;
6155 };
6156 ValueIndex.prototype.maxPost = function () {
6157 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6158 return NamedNode.MAX;
6159 };
6160 ValueIndex.prototype.makePost = function (indexValue, name) {
6161 var valueNode = nodeFromJSON$1(indexValue);
6162 return new NamedNode(name, valueNode);
6163 };
6164 /**
6165 * @returns String representation for inclusion in a query spec
6166 */
6167 ValueIndex.prototype.toString = function () {
6168 return '.value';
6169 };
6170 return ValueIndex;
6171}(Index));
6172var VALUE_INDEX = new ValueIndex();
6173
6174/**
6175 * @license
6176 * Copyright 2017 Google LLC
6177 *
6178 * Licensed under the Apache License, Version 2.0 (the "License");
6179 * you may not use this file except in compliance with the License.
6180 * You may obtain a copy of the License at
6181 *
6182 * http://www.apache.org/licenses/LICENSE-2.0
6183 *
6184 * Unless required by applicable law or agreed to in writing, software
6185 * distributed under the License is distributed on an "AS IS" BASIS,
6186 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6187 * See the License for the specific language governing permissions and
6188 * limitations under the License.
6189 */
6190// Modeled after base64 web-safe chars, but ordered by ASCII.
6191var PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
6192var MIN_PUSH_CHAR = '-';
6193var MAX_PUSH_CHAR = 'z';
6194var MAX_KEY_LEN = 786;
6195/**
6196 * Fancy ID generator that creates 20-character string identifiers with the
6197 * following properties:
6198 *
6199 * 1. They're based on timestamp so that they sort *after* any existing ids.
6200 * 2. They contain 72-bits of random data after the timestamp so that IDs won't
6201 * collide with other clients' IDs.
6202 * 3. They sort *lexicographically* (so the timestamp is converted to characters
6203 * that will sort properly).
6204 * 4. They're monotonically increasing. Even if you generate more than one in
6205 * the same timestamp, the latter ones will sort after the former ones. We do
6206 * this by using the previous random bits but "incrementing" them by 1 (only
6207 * in the case of a timestamp collision).
6208 */
6209var nextPushId = (function () {
6210 // Timestamp of last push, used to prevent local collisions if you push twice
6211 // in one ms.
6212 var lastPushTime = 0;
6213 // We generate 72-bits of randomness which get turned into 12 characters and
6214 // appended to the timestamp to prevent collisions with other clients. We
6215 // store the last characters we generated because in the event of a collision,
6216 // we'll use those same characters except "incremented" by one.
6217 var lastRandChars = [];
6218 return function (now) {
6219 var duplicateTime = now === lastPushTime;
6220 lastPushTime = now;
6221 var i;
6222 var timeStampChars = new Array(8);
6223 for (i = 7; i >= 0; i--) {
6224 timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
6225 // NOTE: Can't use << here because javascript will convert to int and lose
6226 // the upper bits.
6227 now = Math.floor(now / 64);
6228 }
6229 util.assert(now === 0, 'Cannot push at time == 0');
6230 var id = timeStampChars.join('');
6231 if (!duplicateTime) {
6232 for (i = 0; i < 12; i++) {
6233 lastRandChars[i] = Math.floor(Math.random() * 64);
6234 }
6235 }
6236 else {
6237 // If the timestamp hasn't changed since last push, use the same random
6238 // number, except incremented by 1.
6239 for (i = 11; i >= 0 && lastRandChars[i] === 63; i--) {
6240 lastRandChars[i] = 0;
6241 }
6242 lastRandChars[i]++;
6243 }
6244 for (i = 0; i < 12; i++) {
6245 id += PUSH_CHARS.charAt(lastRandChars[i]);
6246 }
6247 util.assert(id.length === 20, 'nextPushId: Length should be 20.');
6248 return id;
6249 };
6250})();
6251var successor = function (key) {
6252 if (key === '' + INTEGER_32_MAX) {
6253 // See https://firebase.google.com/docs/database/web/lists-of-data#data-order
6254 return MIN_PUSH_CHAR;
6255 }
6256 var keyAsInt = tryParseInt(key);
6257 if (keyAsInt != null) {
6258 return '' + (keyAsInt + 1);
6259 }
6260 var next = new Array(key.length);
6261 for (var i_1 = 0; i_1 < next.length; i_1++) {
6262 next[i_1] = key.charAt(i_1);
6263 }
6264 if (next.length < MAX_KEY_LEN) {
6265 next.push(MIN_PUSH_CHAR);
6266 return next.join('');
6267 }
6268 var i = next.length - 1;
6269 while (i >= 0 && next[i] === MAX_PUSH_CHAR) {
6270 i--;
6271 }
6272 // `successor` was called on the largest possible key, so return the
6273 // MAX_NAME, which sorts larger than all keys.
6274 if (i === -1) {
6275 return MAX_NAME;
6276 }
6277 var source = next[i];
6278 var sourcePlusOne = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(source) + 1);
6279 next[i] = sourcePlusOne;
6280 return next.slice(0, i + 1).join('');
6281};
6282// `key` is assumed to be non-empty.
6283var predecessor = function (key) {
6284 if (key === '' + INTEGER_32_MIN) {
6285 return MIN_NAME;
6286 }
6287 var keyAsInt = tryParseInt(key);
6288 if (keyAsInt != null) {
6289 return '' + (keyAsInt - 1);
6290 }
6291 var next = new Array(key.length);
6292 for (var i = 0; i < next.length; i++) {
6293 next[i] = key.charAt(i);
6294 }
6295 // If `key` ends in `MIN_PUSH_CHAR`, the largest key lexicographically
6296 // smaller than `key`, is `key[0:key.length - 1]`. The next key smaller
6297 // than that, `predecessor(predecessor(key))`, is
6298 //
6299 // `key[0:key.length - 2] + (key[key.length - 1] - 1) + \
6300 // { MAX_PUSH_CHAR repeated MAX_KEY_LEN - (key.length - 1) times }
6301 //
6302 // analogous to increment/decrement for base-10 integers.
6303 //
6304 // This works because lexigographic comparison works character-by-character,
6305 // using length as a tie-breaker if one key is a prefix of the other.
6306 if (next[next.length - 1] === MIN_PUSH_CHAR) {
6307 if (next.length === 1) {
6308 // See https://firebase.google.com/docs/database/web/lists-of-data#orderbykey
6309 return '' + INTEGER_32_MAX;
6310 }
6311 delete next[next.length - 1];
6312 return next.join('');
6313 }
6314 // Replace the last character with it's immediate predecessor, and
6315 // fill the suffix of the key with MAX_PUSH_CHAR. This is the
6316 // lexicographically largest possible key smaller than `key`.
6317 next[next.length - 1] = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(next[next.length - 1]) - 1);
6318 return next.join('') + MAX_PUSH_CHAR.repeat(MAX_KEY_LEN - next.length);
6319};
6320
6321/**
6322 * @license
6323 * Copyright 2017 Google LLC
6324 *
6325 * Licensed under the Apache License, Version 2.0 (the "License");
6326 * you may not use this file except in compliance with the License.
6327 * You may obtain a copy of the License at
6328 *
6329 * http://www.apache.org/licenses/LICENSE-2.0
6330 *
6331 * Unless required by applicable law or agreed to in writing, software
6332 * distributed under the License is distributed on an "AS IS" BASIS,
6333 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6334 * See the License for the specific language governing permissions and
6335 * limitations under the License.
6336 */
6337function changeValue(snapshotNode) {
6338 return { type: "value" /* VALUE */, snapshotNode: snapshotNode };
6339}
6340function changeChildAdded(childName, snapshotNode) {
6341 return { type: "child_added" /* CHILD_ADDED */, snapshotNode: snapshotNode, childName: childName };
6342}
6343function changeChildRemoved(childName, snapshotNode) {
6344 return { type: "child_removed" /* CHILD_REMOVED */, snapshotNode: snapshotNode, childName: childName };
6345}
6346function changeChildChanged(childName, snapshotNode, oldSnap) {
6347 return {
6348 type: "child_changed" /* CHILD_CHANGED */,
6349 snapshotNode: snapshotNode,
6350 childName: childName,
6351 oldSnap: oldSnap
6352 };
6353}
6354function changeChildMoved(childName, snapshotNode) {
6355 return { type: "child_moved" /* CHILD_MOVED */, snapshotNode: snapshotNode, childName: childName };
6356}
6357
6358/**
6359 * @license
6360 * Copyright 2017 Google LLC
6361 *
6362 * Licensed under the Apache License, Version 2.0 (the "License");
6363 * you may not use this file except in compliance with the License.
6364 * You may obtain a copy of the License at
6365 *
6366 * http://www.apache.org/licenses/LICENSE-2.0
6367 *
6368 * Unless required by applicable law or agreed to in writing, software
6369 * distributed under the License is distributed on an "AS IS" BASIS,
6370 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6371 * See the License for the specific language governing permissions and
6372 * limitations under the License.
6373 */
6374/**
6375 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
6376 */
6377var IndexedFilter = /** @class */ (function () {
6378 function IndexedFilter(index_) {
6379 this.index_ = index_;
6380 }
6381 IndexedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6382 util.assert(snap.isIndexed(this.index_), 'A node must be indexed if only a child is updated');
6383 var oldChild = snap.getImmediateChild(key);
6384 // Check if anything actually changed.
6385 if (oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))) {
6386 // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
6387 // In this case, affectedPath will appear null in both the old and new snapshots. So we need
6388 // to avoid treating these cases as "nothing changed."
6389 if (oldChild.isEmpty() === newChild.isEmpty()) {
6390 // Nothing changed.
6391 // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
6392 //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
6393 return snap;
6394 }
6395 }
6396 if (optChangeAccumulator != null) {
6397 if (newChild.isEmpty()) {
6398 if (snap.hasChild(key)) {
6399 optChangeAccumulator.trackChildChange(changeChildRemoved(key, oldChild));
6400 }
6401 else {
6402 util.assert(snap.isLeafNode(), 'A child remove without an old child only makes sense on a leaf node');
6403 }
6404 }
6405 else if (oldChild.isEmpty()) {
6406 optChangeAccumulator.trackChildChange(changeChildAdded(key, newChild));
6407 }
6408 else {
6409 optChangeAccumulator.trackChildChange(changeChildChanged(key, newChild, oldChild));
6410 }
6411 }
6412 if (snap.isLeafNode() && newChild.isEmpty()) {
6413 return snap;
6414 }
6415 else {
6416 // Make sure the node is indexed
6417 return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
6418 }
6419 };
6420 IndexedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6421 if (optChangeAccumulator != null) {
6422 if (!oldSnap.isLeafNode()) {
6423 oldSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6424 if (!newSnap.hasChild(key)) {
6425 optChangeAccumulator.trackChildChange(changeChildRemoved(key, childNode));
6426 }
6427 });
6428 }
6429 if (!newSnap.isLeafNode()) {
6430 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6431 if (oldSnap.hasChild(key)) {
6432 var oldChild = oldSnap.getImmediateChild(key);
6433 if (!oldChild.equals(childNode)) {
6434 optChangeAccumulator.trackChildChange(changeChildChanged(key, childNode, oldChild));
6435 }
6436 }
6437 else {
6438 optChangeAccumulator.trackChildChange(changeChildAdded(key, childNode));
6439 }
6440 });
6441 }
6442 }
6443 return newSnap.withIndex(this.index_);
6444 };
6445 IndexedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6446 if (oldSnap.isEmpty()) {
6447 return ChildrenNode.EMPTY_NODE;
6448 }
6449 else {
6450 return oldSnap.updatePriority(newPriority);
6451 }
6452 };
6453 IndexedFilter.prototype.filtersNodes = function () {
6454 return false;
6455 };
6456 IndexedFilter.prototype.getIndexedFilter = function () {
6457 return this;
6458 };
6459 IndexedFilter.prototype.getIndex = function () {
6460 return this.index_;
6461 };
6462 return IndexedFilter;
6463}());
6464
6465/**
6466 * @license
6467 * Copyright 2017 Google LLC
6468 *
6469 * Licensed under the Apache License, Version 2.0 (the "License");
6470 * you may not use this file except in compliance with the License.
6471 * You may obtain a copy of the License at
6472 *
6473 * http://www.apache.org/licenses/LICENSE-2.0
6474 *
6475 * Unless required by applicable law or agreed to in writing, software
6476 * distributed under the License is distributed on an "AS IS" BASIS,
6477 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6478 * See the License for the specific language governing permissions and
6479 * limitations under the License.
6480 */
6481/**
6482 * Filters nodes by range and uses an IndexFilter to track any changes after filtering the node
6483 */
6484var RangedFilter = /** @class */ (function () {
6485 function RangedFilter(params) {
6486 this.indexedFilter_ = new IndexedFilter(params.getIndex());
6487 this.index_ = params.getIndex();
6488 this.startPost_ = RangedFilter.getStartPost_(params);
6489 this.endPost_ = RangedFilter.getEndPost_(params);
6490 }
6491 RangedFilter.prototype.getStartPost = function () {
6492 return this.startPost_;
6493 };
6494 RangedFilter.prototype.getEndPost = function () {
6495 return this.endPost_;
6496 };
6497 RangedFilter.prototype.matches = function (node) {
6498 return (this.index_.compare(this.getStartPost(), node) <= 0 &&
6499 this.index_.compare(node, this.getEndPost()) <= 0);
6500 };
6501 RangedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6502 if (!this.matches(new NamedNode(key, newChild))) {
6503 newChild = ChildrenNode.EMPTY_NODE;
6504 }
6505 return this.indexedFilter_.updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6506 };
6507 RangedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6508 if (newSnap.isLeafNode()) {
6509 // Make sure we have a children node with the correct index, not a leaf node;
6510 newSnap = ChildrenNode.EMPTY_NODE;
6511 }
6512 var filtered = newSnap.withIndex(this.index_);
6513 // Don't support priorities on queries
6514 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6515 var self = this;
6516 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6517 if (!self.matches(new NamedNode(key, childNode))) {
6518 filtered = filtered.updateImmediateChild(key, ChildrenNode.EMPTY_NODE);
6519 }
6520 });
6521 return this.indexedFilter_.updateFullNode(oldSnap, filtered, optChangeAccumulator);
6522 };
6523 RangedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6524 // Don't support priorities on queries
6525 return oldSnap;
6526 };
6527 RangedFilter.prototype.filtersNodes = function () {
6528 return true;
6529 };
6530 RangedFilter.prototype.getIndexedFilter = function () {
6531 return this.indexedFilter_;
6532 };
6533 RangedFilter.prototype.getIndex = function () {
6534 return this.index_;
6535 };
6536 RangedFilter.getStartPost_ = function (params) {
6537 if (params.hasStart()) {
6538 var startName = params.getIndexStartName();
6539 return params.getIndex().makePost(params.getIndexStartValue(), startName);
6540 }
6541 else {
6542 return params.getIndex().minPost();
6543 }
6544 };
6545 RangedFilter.getEndPost_ = function (params) {
6546 if (params.hasEnd()) {
6547 var endName = params.getIndexEndName();
6548 return params.getIndex().makePost(params.getIndexEndValue(), endName);
6549 }
6550 else {
6551 return params.getIndex().maxPost();
6552 }
6553 };
6554 return RangedFilter;
6555}());
6556
6557/**
6558 * @license
6559 * Copyright 2017 Google LLC
6560 *
6561 * Licensed under the Apache License, Version 2.0 (the "License");
6562 * you may not use this file except in compliance with the License.
6563 * You may obtain a copy of the License at
6564 *
6565 * http://www.apache.org/licenses/LICENSE-2.0
6566 *
6567 * Unless required by applicable law or agreed to in writing, software
6568 * distributed under the License is distributed on an "AS IS" BASIS,
6569 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6570 * See the License for the specific language governing permissions and
6571 * limitations under the License.
6572 */
6573/**
6574 * Applies a limit and a range to a node and uses RangedFilter to do the heavy lifting where possible
6575 */
6576var LimitedFilter = /** @class */ (function () {
6577 function LimitedFilter(params) {
6578 this.rangedFilter_ = new RangedFilter(params);
6579 this.index_ = params.getIndex();
6580 this.limit_ = params.getLimit();
6581 this.reverse_ = !params.isViewFromLeft();
6582 }
6583 LimitedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6584 if (!this.rangedFilter_.matches(new NamedNode(key, newChild))) {
6585 newChild = ChildrenNode.EMPTY_NODE;
6586 }
6587 if (snap.getImmediateChild(key).equals(newChild)) {
6588 // No change
6589 return snap;
6590 }
6591 else if (snap.numChildren() < this.limit_) {
6592 return this.rangedFilter_
6593 .getIndexedFilter()
6594 .updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6595 }
6596 else {
6597 return this.fullLimitUpdateChild_(snap, key, newChild, source, optChangeAccumulator);
6598 }
6599 };
6600 LimitedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6601 var filtered;
6602 if (newSnap.isLeafNode() || newSnap.isEmpty()) {
6603 // Make sure we have a children node with the correct index, not a leaf node;
6604 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6605 }
6606 else {
6607 if (this.limit_ * 2 < newSnap.numChildren() &&
6608 newSnap.isIndexed(this.index_)) {
6609 // Easier to build up a snapshot, since what we're given has more than twice the elements we want
6610 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6611 // anchor to the startPost, endPost, or last element as appropriate
6612 var iterator = void 0;
6613 if (this.reverse_) {
6614 iterator = newSnap.getReverseIteratorFrom(this.rangedFilter_.getEndPost(), this.index_);
6615 }
6616 else {
6617 iterator = newSnap.getIteratorFrom(this.rangedFilter_.getStartPost(), this.index_);
6618 }
6619 var count = 0;
6620 while (iterator.hasNext() && count < this.limit_) {
6621 var next = iterator.getNext();
6622 var inRange = void 0;
6623 if (this.reverse_) {
6624 inRange =
6625 this.index_.compare(this.rangedFilter_.getStartPost(), next) <= 0;
6626 }
6627 else {
6628 inRange =
6629 this.index_.compare(next, this.rangedFilter_.getEndPost()) <= 0;
6630 }
6631 if (inRange) {
6632 filtered = filtered.updateImmediateChild(next.name, next.node);
6633 count++;
6634 }
6635 else {
6636 // if we have reached the end post, we cannot keep adding elemments
6637 break;
6638 }
6639 }
6640 }
6641 else {
6642 // The snap contains less than twice the limit. Faster to delete from the snap than build up a new one
6643 filtered = newSnap.withIndex(this.index_);
6644 // Don't support priorities on queries
6645 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6646 var startPost = void 0;
6647 var endPost = void 0;
6648 var cmp = void 0;
6649 var iterator = void 0;
6650 if (this.reverse_) {
6651 iterator = filtered.getReverseIterator(this.index_);
6652 startPost = this.rangedFilter_.getEndPost();
6653 endPost = this.rangedFilter_.getStartPost();
6654 var indexCompare_1 = this.index_.getCompare();
6655 cmp = function (a, b) { return indexCompare_1(b, a); };
6656 }
6657 else {
6658 iterator = filtered.getIterator(this.index_);
6659 startPost = this.rangedFilter_.getStartPost();
6660 endPost = this.rangedFilter_.getEndPost();
6661 cmp = this.index_.getCompare();
6662 }
6663 var count = 0;
6664 var foundStartPost = false;
6665 while (iterator.hasNext()) {
6666 var next = iterator.getNext();
6667 if (!foundStartPost && cmp(startPost, next) <= 0) {
6668 // start adding
6669 foundStartPost = true;
6670 }
6671 var inRange = foundStartPost && count < this.limit_ && cmp(next, endPost) <= 0;
6672 if (inRange) {
6673 count++;
6674 }
6675 else {
6676 filtered = filtered.updateImmediateChild(next.name, ChildrenNode.EMPTY_NODE);
6677 }
6678 }
6679 }
6680 }
6681 return this.rangedFilter_
6682 .getIndexedFilter()
6683 .updateFullNode(oldSnap, filtered, optChangeAccumulator);
6684 };
6685 LimitedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6686 // Don't support priorities on queries
6687 return oldSnap;
6688 };
6689 LimitedFilter.prototype.filtersNodes = function () {
6690 return true;
6691 };
6692 LimitedFilter.prototype.getIndexedFilter = function () {
6693 return this.rangedFilter_.getIndexedFilter();
6694 };
6695 LimitedFilter.prototype.getIndex = function () {
6696 return this.index_;
6697 };
6698 LimitedFilter.prototype.fullLimitUpdateChild_ = function (snap, childKey, childSnap, source, changeAccumulator) {
6699 // TODO: rename all cache stuff etc to general snap terminology
6700 var cmp;
6701 if (this.reverse_) {
6702 var indexCmp_1 = this.index_.getCompare();
6703 cmp = function (a, b) { return indexCmp_1(b, a); };
6704 }
6705 else {
6706 cmp = this.index_.getCompare();
6707 }
6708 var oldEventCache = snap;
6709 util.assert(oldEventCache.numChildren() === this.limit_, '');
6710 var newChildNamedNode = new NamedNode(childKey, childSnap);
6711 var windowBoundary = this.reverse_
6712 ? oldEventCache.getFirstChild(this.index_)
6713 : oldEventCache.getLastChild(this.index_);
6714 var inRange = this.rangedFilter_.matches(newChildNamedNode);
6715 if (oldEventCache.hasChild(childKey)) {
6716 var oldChildSnap = oldEventCache.getImmediateChild(childKey);
6717 var nextChild = source.getChildAfterChild(this.index_, windowBoundary, this.reverse_);
6718 while (nextChild != null &&
6719 (nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))) {
6720 // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
6721 // been applied to the limited filter yet. Ignore this next child which will be updated later in
6722 // the limited filter...
6723 nextChild = source.getChildAfterChild(this.index_, nextChild, this.reverse_);
6724 }
6725 var compareNext = nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
6726 var remainsInWindow = inRange && !childSnap.isEmpty() && compareNext >= 0;
6727 if (remainsInWindow) {
6728 if (changeAccumulator != null) {
6729 changeAccumulator.trackChildChange(changeChildChanged(childKey, childSnap, oldChildSnap));
6730 }
6731 return oldEventCache.updateImmediateChild(childKey, childSnap);
6732 }
6733 else {
6734 if (changeAccumulator != null) {
6735 changeAccumulator.trackChildChange(changeChildRemoved(childKey, oldChildSnap));
6736 }
6737 var newEventCache = oldEventCache.updateImmediateChild(childKey, ChildrenNode.EMPTY_NODE);
6738 var nextChildInRange = nextChild != null && this.rangedFilter_.matches(nextChild);
6739 if (nextChildInRange) {
6740 if (changeAccumulator != null) {
6741 changeAccumulator.trackChildChange(changeChildAdded(nextChild.name, nextChild.node));
6742 }
6743 return newEventCache.updateImmediateChild(nextChild.name, nextChild.node);
6744 }
6745 else {
6746 return newEventCache;
6747 }
6748 }
6749 }
6750 else if (childSnap.isEmpty()) {
6751 // we're deleting a node, but it was not in the window, so ignore it
6752 return snap;
6753 }
6754 else if (inRange) {
6755 if (cmp(windowBoundary, newChildNamedNode) >= 0) {
6756 if (changeAccumulator != null) {
6757 changeAccumulator.trackChildChange(changeChildRemoved(windowBoundary.name, windowBoundary.node));
6758 changeAccumulator.trackChildChange(changeChildAdded(childKey, childSnap));
6759 }
6760 return oldEventCache
6761 .updateImmediateChild(childKey, childSnap)
6762 .updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
6763 }
6764 else {
6765 return snap;
6766 }
6767 }
6768 else {
6769 return snap;
6770 }
6771 };
6772 return LimitedFilter;
6773}());
6774
6775/**
6776 * @license
6777 * Copyright 2017 Google LLC
6778 *
6779 * Licensed under the Apache License, Version 2.0 (the "License");
6780 * you may not use this file except in compliance with the License.
6781 * You may obtain a copy of the License at
6782 *
6783 * http://www.apache.org/licenses/LICENSE-2.0
6784 *
6785 * Unless required by applicable law or agreed to in writing, software
6786 * distributed under the License is distributed on an "AS IS" BASIS,
6787 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6788 * See the License for the specific language governing permissions and
6789 * limitations under the License.
6790 */
6791/**
6792 * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a
6793 * range to be returned for a particular location. It is assumed that validation of parameters is done at the
6794 * user-facing API level, so it is not done here.
6795 */
6796var QueryParams = /** @class */ (function () {
6797 function QueryParams() {
6798 this.limitSet_ = false;
6799 this.startSet_ = false;
6800 this.startNameSet_ = false;
6801 this.startAfterSet_ = false;
6802 this.endSet_ = false;
6803 this.endNameSet_ = false;
6804 this.endBeforeSet_ = false;
6805 this.limit_ = 0;
6806 this.viewFrom_ = '';
6807 this.indexStartValue_ = null;
6808 this.indexStartName_ = '';
6809 this.indexEndValue_ = null;
6810 this.indexEndName_ = '';
6811 this.index_ = PRIORITY_INDEX;
6812 }
6813 QueryParams.prototype.hasStart = function () {
6814 return this.startSet_;
6815 };
6816 QueryParams.prototype.hasStartAfter = function () {
6817 return this.startAfterSet_;
6818 };
6819 QueryParams.prototype.hasEndBefore = function () {
6820 return this.endBeforeSet_;
6821 };
6822 /**
6823 * @returns True if it would return from left.
6824 */
6825 QueryParams.prototype.isViewFromLeft = function () {
6826 if (this.viewFrom_ === '') {
6827 // limit(), rather than limitToFirst or limitToLast was called.
6828 // This means that only one of startSet_ and endSet_ is true. Use them
6829 // to calculate which side of the view to anchor to. If neither is set,
6830 // anchor to the end.
6831 return this.startSet_;
6832 }
6833 else {
6834 return this.viewFrom_ === "l" /* VIEW_FROM_LEFT */;
6835 }
6836 };
6837 /**
6838 * Only valid to call if hasStart() returns true
6839 */
6840 QueryParams.prototype.getIndexStartValue = function () {
6841 util.assert(this.startSet_, 'Only valid if start has been set');
6842 return this.indexStartValue_;
6843 };
6844 /**
6845 * Only valid to call if hasStart() returns true.
6846 * Returns the starting key name for the range defined by these query parameters
6847 */
6848 QueryParams.prototype.getIndexStartName = function () {
6849 util.assert(this.startSet_, 'Only valid if start has been set');
6850 if (this.startNameSet_) {
6851 return this.indexStartName_;
6852 }
6853 else {
6854 return MIN_NAME;
6855 }
6856 };
6857 QueryParams.prototype.hasEnd = function () {
6858 return this.endSet_;
6859 };
6860 /**
6861 * Only valid to call if hasEnd() returns true.
6862 */
6863 QueryParams.prototype.getIndexEndValue = function () {
6864 util.assert(this.endSet_, 'Only valid if end has been set');
6865 return this.indexEndValue_;
6866 };
6867 /**
6868 * Only valid to call if hasEnd() returns true.
6869 * Returns the end key name for the range defined by these query parameters
6870 */
6871 QueryParams.prototype.getIndexEndName = function () {
6872 util.assert(this.endSet_, 'Only valid if end has been set');
6873 if (this.endNameSet_) {
6874 return this.indexEndName_;
6875 }
6876 else {
6877 return MAX_NAME;
6878 }
6879 };
6880 QueryParams.prototype.hasLimit = function () {
6881 return this.limitSet_;
6882 };
6883 /**
6884 * @returns True if a limit has been set and it has been explicitly anchored
6885 */
6886 QueryParams.prototype.hasAnchoredLimit = function () {
6887 return this.limitSet_ && this.viewFrom_ !== '';
6888 };
6889 /**
6890 * Only valid to call if hasLimit() returns true
6891 */
6892 QueryParams.prototype.getLimit = function () {
6893 util.assert(this.limitSet_, 'Only valid if limit has been set');
6894 return this.limit_;
6895 };
6896 QueryParams.prototype.getIndex = function () {
6897 return this.index_;
6898 };
6899 QueryParams.prototype.loadsAllData = function () {
6900 return !(this.startSet_ || this.endSet_ || this.limitSet_);
6901 };
6902 QueryParams.prototype.isDefault = function () {
6903 return this.loadsAllData() && this.index_ === PRIORITY_INDEX;
6904 };
6905 QueryParams.prototype.copy = function () {
6906 var copy = new QueryParams();
6907 copy.limitSet_ = this.limitSet_;
6908 copy.limit_ = this.limit_;
6909 copy.startSet_ = this.startSet_;
6910 copy.indexStartValue_ = this.indexStartValue_;
6911 copy.startNameSet_ = this.startNameSet_;
6912 copy.indexStartName_ = this.indexStartName_;
6913 copy.endSet_ = this.endSet_;
6914 copy.indexEndValue_ = this.indexEndValue_;
6915 copy.endNameSet_ = this.endNameSet_;
6916 copy.indexEndName_ = this.indexEndName_;
6917 copy.index_ = this.index_;
6918 copy.viewFrom_ = this.viewFrom_;
6919 return copy;
6920 };
6921 return QueryParams;
6922}());
6923function queryParamsGetNodeFilter(queryParams) {
6924 if (queryParams.loadsAllData()) {
6925 return new IndexedFilter(queryParams.getIndex());
6926 }
6927 else if (queryParams.hasLimit()) {
6928 return new LimitedFilter(queryParams);
6929 }
6930 else {
6931 return new RangedFilter(queryParams);
6932 }
6933}
6934function queryParamsLimitToFirst(queryParams, newLimit) {
6935 var newParams = queryParams.copy();
6936 newParams.limitSet_ = true;
6937 newParams.limit_ = newLimit;
6938 newParams.viewFrom_ = "l" /* VIEW_FROM_LEFT */;
6939 return newParams;
6940}
6941function queryParamsLimitToLast(queryParams, newLimit) {
6942 var newParams = queryParams.copy();
6943 newParams.limitSet_ = true;
6944 newParams.limit_ = newLimit;
6945 newParams.viewFrom_ = "r" /* VIEW_FROM_RIGHT */;
6946 return newParams;
6947}
6948function queryParamsStartAt(queryParams, indexValue, key) {
6949 var newParams = queryParams.copy();
6950 newParams.startSet_ = true;
6951 if (indexValue === undefined) {
6952 indexValue = null;
6953 }
6954 newParams.indexStartValue_ = indexValue;
6955 if (key != null) {
6956 newParams.startNameSet_ = true;
6957 newParams.indexStartName_ = key;
6958 }
6959 else {
6960 newParams.startNameSet_ = false;
6961 newParams.indexStartName_ = '';
6962 }
6963 return newParams;
6964}
6965function queryParamsStartAfter(queryParams, indexValue, key) {
6966 var params;
6967 if (queryParams.index_ === KEY_INDEX) {
6968 if (typeof indexValue === 'string') {
6969 indexValue = successor(indexValue);
6970 }
6971 params = queryParamsStartAt(queryParams, indexValue, key);
6972 }
6973 else {
6974 var childKey = void 0;
6975 if (key == null) {
6976 childKey = MAX_NAME;
6977 }
6978 else {
6979 childKey = successor(key);
6980 }
6981 params = queryParamsStartAt(queryParams, indexValue, childKey);
6982 }
6983 params.startAfterSet_ = true;
6984 return params;
6985}
6986function queryParamsEndAt(queryParams, indexValue, key) {
6987 var newParams = queryParams.copy();
6988 newParams.endSet_ = true;
6989 if (indexValue === undefined) {
6990 indexValue = null;
6991 }
6992 newParams.indexEndValue_ = indexValue;
6993 if (key !== undefined) {
6994 newParams.endNameSet_ = true;
6995 newParams.indexEndName_ = key;
6996 }
6997 else {
6998 newParams.endNameSet_ = false;
6999 newParams.indexEndName_ = '';
7000 }
7001 return newParams;
7002}
7003function queryParamsEndBefore(queryParams, indexValue, key) {
7004 var childKey;
7005 var params;
7006 if (queryParams.index_ === KEY_INDEX) {
7007 if (typeof indexValue === 'string') {
7008 indexValue = predecessor(indexValue);
7009 }
7010 params = queryParamsEndAt(queryParams, indexValue, key);
7011 }
7012 else {
7013 if (key == null) {
7014 childKey = MIN_NAME;
7015 }
7016 else {
7017 childKey = predecessor(key);
7018 }
7019 params = queryParamsEndAt(queryParams, indexValue, childKey);
7020 }
7021 params.endBeforeSet_ = true;
7022 return params;
7023}
7024function queryParamsOrderBy(queryParams, index) {
7025 var newParams = queryParams.copy();
7026 newParams.index_ = index;
7027 return newParams;
7028}
7029/**
7030 * Returns a set of REST query string parameters representing this query.
7031 *
7032 * @returns query string parameters
7033 */
7034function queryParamsToRestQueryStringParameters(queryParams) {
7035 var qs = {};
7036 if (queryParams.isDefault()) {
7037 return qs;
7038 }
7039 var orderBy;
7040 if (queryParams.index_ === PRIORITY_INDEX) {
7041 orderBy = "$priority" /* PRIORITY_INDEX */;
7042 }
7043 else if (queryParams.index_ === VALUE_INDEX) {
7044 orderBy = "$value" /* VALUE_INDEX */;
7045 }
7046 else if (queryParams.index_ === KEY_INDEX) {
7047 orderBy = "$key" /* KEY_INDEX */;
7048 }
7049 else {
7050 util.assert(queryParams.index_ instanceof PathIndex, 'Unrecognized index type!');
7051 orderBy = queryParams.index_.toString();
7052 }
7053 qs["orderBy" /* ORDER_BY */] = util.stringify(orderBy);
7054 if (queryParams.startSet_) {
7055 qs["startAt" /* START_AT */] = util.stringify(queryParams.indexStartValue_);
7056 if (queryParams.startNameSet_) {
7057 qs["startAt" /* START_AT */] +=
7058 ',' + util.stringify(queryParams.indexStartName_);
7059 }
7060 }
7061 if (queryParams.endSet_) {
7062 qs["endAt" /* END_AT */] = util.stringify(queryParams.indexEndValue_);
7063 if (queryParams.endNameSet_) {
7064 qs["endAt" /* END_AT */] +=
7065 ',' + util.stringify(queryParams.indexEndName_);
7066 }
7067 }
7068 if (queryParams.limitSet_) {
7069 if (queryParams.isViewFromLeft()) {
7070 qs["limitToFirst" /* LIMIT_TO_FIRST */] = queryParams.limit_;
7071 }
7072 else {
7073 qs["limitToLast" /* LIMIT_TO_LAST */] = queryParams.limit_;
7074 }
7075 }
7076 return qs;
7077}
7078function queryParamsGetQueryObject(queryParams) {
7079 var obj = {};
7080 if (queryParams.startSet_) {
7081 obj["sp" /* INDEX_START_VALUE */] =
7082 queryParams.indexStartValue_;
7083 if (queryParams.startNameSet_) {
7084 obj["sn" /* INDEX_START_NAME */] =
7085 queryParams.indexStartName_;
7086 }
7087 }
7088 if (queryParams.endSet_) {
7089 obj["ep" /* INDEX_END_VALUE */] = queryParams.indexEndValue_;
7090 if (queryParams.endNameSet_) {
7091 obj["en" /* INDEX_END_NAME */] = queryParams.indexEndName_;
7092 }
7093 }
7094 if (queryParams.limitSet_) {
7095 obj["l" /* LIMIT */] = queryParams.limit_;
7096 var viewFrom = queryParams.viewFrom_;
7097 if (viewFrom === '') {
7098 if (queryParams.isViewFromLeft()) {
7099 viewFrom = "l" /* VIEW_FROM_LEFT */;
7100 }
7101 else {
7102 viewFrom = "r" /* VIEW_FROM_RIGHT */;
7103 }
7104 }
7105 obj["vf" /* VIEW_FROM */] = viewFrom;
7106 }
7107 // For now, priority index is the default, so we only specify if it's some other index
7108 if (queryParams.index_ !== PRIORITY_INDEX) {
7109 obj["i" /* INDEX */] = queryParams.index_.toString();
7110 }
7111 return obj;
7112}
7113
7114/**
7115 * @license
7116 * Copyright 2017 Google LLC
7117 *
7118 * Licensed under the Apache License, Version 2.0 (the "License");
7119 * you may not use this file except in compliance with the License.
7120 * You may obtain a copy of the License at
7121 *
7122 * http://www.apache.org/licenses/LICENSE-2.0
7123 *
7124 * Unless required by applicable law or agreed to in writing, software
7125 * distributed under the License is distributed on an "AS IS" BASIS,
7126 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7127 * See the License for the specific language governing permissions and
7128 * limitations under the License.
7129 */
7130/**
7131 * An implementation of ServerActions that communicates with the server via REST requests.
7132 * This is mostly useful for compatibility with crawlers, where we don't want to spin up a full
7133 * persistent connection (using WebSockets or long-polling)
7134 */
7135var ReadonlyRestClient = /** @class */ (function (_super) {
7136 tslib.__extends(ReadonlyRestClient, _super);
7137 /**
7138 * @param repoInfo_ - Data about the namespace we are connecting to
7139 * @param onDataUpdate_ - A callback for new data from the server
7140 */
7141 function ReadonlyRestClient(repoInfo_, onDataUpdate_, authTokenProvider_, appCheckTokenProvider_) {
7142 var _this = _super.call(this) || this;
7143 _this.repoInfo_ = repoInfo_;
7144 _this.onDataUpdate_ = onDataUpdate_;
7145 _this.authTokenProvider_ = authTokenProvider_;
7146 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
7147 /** @private {function(...[*])} */
7148 _this.log_ = logWrapper('p:rest:');
7149 /**
7150 * We don't actually need to track listens, except to prevent us calling an onComplete for a listen
7151 * that's been removed. :-/
7152 */
7153 _this.listens_ = {};
7154 return _this;
7155 }
7156 ReadonlyRestClient.prototype.reportStats = function (stats) {
7157 throw new Error('Method not implemented.');
7158 };
7159 ReadonlyRestClient.getListenId_ = function (query, tag) {
7160 if (tag !== undefined) {
7161 return 'tag$' + tag;
7162 }
7163 else {
7164 util.assert(query._queryParams.isDefault(), "should have a tag if it's not a default query.");
7165 return query._path.toString();
7166 }
7167 };
7168 /** @inheritDoc */
7169 ReadonlyRestClient.prototype.listen = function (query, currentHashFn, tag, onComplete) {
7170 var _this = this;
7171 var pathString = query._path.toString();
7172 this.log_('Listen called for ' + pathString + ' ' + query._queryIdentifier);
7173 // Mark this listener so we can tell if it's removed.
7174 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7175 var thisListen = {};
7176 this.listens_[listenId] = thisListen;
7177 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7178 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7179 var data = result;
7180 if (error === 404) {
7181 data = null;
7182 error = null;
7183 }
7184 if (error === null) {
7185 _this.onDataUpdate_(pathString, data, /*isMerge=*/ false, tag);
7186 }
7187 if (util.safeGet(_this.listens_, listenId) === thisListen) {
7188 var status_1;
7189 if (!error) {
7190 status_1 = 'ok';
7191 }
7192 else if (error === 401) {
7193 status_1 = 'permission_denied';
7194 }
7195 else {
7196 status_1 = 'rest_error:' + error;
7197 }
7198 onComplete(status_1, null);
7199 }
7200 });
7201 };
7202 /** @inheritDoc */
7203 ReadonlyRestClient.prototype.unlisten = function (query, tag) {
7204 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7205 delete this.listens_[listenId];
7206 };
7207 ReadonlyRestClient.prototype.get = function (query) {
7208 var _this = this;
7209 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7210 var pathString = query._path.toString();
7211 var deferred = new util.Deferred();
7212 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7213 var data = result;
7214 if (error === 404) {
7215 data = null;
7216 error = null;
7217 }
7218 if (error === null) {
7219 _this.onDataUpdate_(pathString, data,
7220 /*isMerge=*/ false,
7221 /*tag=*/ null);
7222 deferred.resolve(data);
7223 }
7224 else {
7225 deferred.reject(new Error(data));
7226 }
7227 });
7228 return deferred.promise;
7229 };
7230 /** @inheritDoc */
7231 ReadonlyRestClient.prototype.refreshAuthToken = function (token) {
7232 // no-op since we just always call getToken.
7233 };
7234 /**
7235 * Performs a REST request to the given path, with the provided query string parameters,
7236 * and any auth credentials we have.
7237 */
7238 ReadonlyRestClient.prototype.restRequest_ = function (pathString, queryStringParameters, callback) {
7239 var _this = this;
7240 if (queryStringParameters === void 0) { queryStringParameters = {}; }
7241 queryStringParameters['format'] = 'export';
7242 return Promise.all([
7243 this.authTokenProvider_.getToken(/*forceRefresh=*/ false),
7244 this.appCheckTokenProvider_.getToken(/*forceRefresh=*/ false)
7245 ]).then(function (_a) {
7246 var _b = tslib.__read(_a, 2), authToken = _b[0], appCheckToken = _b[1];
7247 if (authToken && authToken.accessToken) {
7248 queryStringParameters['auth'] = authToken.accessToken;
7249 }
7250 if (appCheckToken && appCheckToken.token) {
7251 queryStringParameters['ac'] = appCheckToken.token;
7252 }
7253 var url = (_this.repoInfo_.secure ? 'https://' : 'http://') +
7254 _this.repoInfo_.host +
7255 pathString +
7256 '?' +
7257 'ns=' +
7258 _this.repoInfo_.namespace +
7259 util.querystring(queryStringParameters);
7260 _this.log_('Sending REST request for ' + url);
7261 var xhr = new XMLHttpRequest();
7262 xhr.onreadystatechange = function () {
7263 if (callback && xhr.readyState === 4) {
7264 _this.log_('REST Response for ' + url + ' received. status:', xhr.status, 'response:', xhr.responseText);
7265 var res = null;
7266 if (xhr.status >= 200 && xhr.status < 300) {
7267 try {
7268 res = util.jsonEval(xhr.responseText);
7269 }
7270 catch (e) {
7271 warn('Failed to parse JSON response for ' +
7272 url +
7273 ': ' +
7274 xhr.responseText);
7275 }
7276 callback(null, res);
7277 }
7278 else {
7279 // 401 and 404 are expected.
7280 if (xhr.status !== 401 && xhr.status !== 404) {
7281 warn('Got unsuccessful REST response for ' +
7282 url +
7283 ' Status: ' +
7284 xhr.status);
7285 }
7286 callback(xhr.status);
7287 }
7288 callback = null;
7289 }
7290 };
7291 xhr.open('GET', url, /*asynchronous=*/ true);
7292 xhr.send();
7293 });
7294 };
7295 return ReadonlyRestClient;
7296}(ServerActions));
7297
7298/**
7299 * @license
7300 * Copyright 2017 Google LLC
7301 *
7302 * Licensed under the Apache License, Version 2.0 (the "License");
7303 * you may not use this file except in compliance with the License.
7304 * You may obtain a copy of the License at
7305 *
7306 * http://www.apache.org/licenses/LICENSE-2.0
7307 *
7308 * Unless required by applicable law or agreed to in writing, software
7309 * distributed under the License is distributed on an "AS IS" BASIS,
7310 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7311 * See the License for the specific language governing permissions and
7312 * limitations under the License.
7313 */
7314/**
7315 * Mutable object which basically just stores a reference to the "latest" immutable snapshot.
7316 */
7317var SnapshotHolder = /** @class */ (function () {
7318 function SnapshotHolder() {
7319 this.rootNode_ = ChildrenNode.EMPTY_NODE;
7320 }
7321 SnapshotHolder.prototype.getNode = function (path) {
7322 return this.rootNode_.getChild(path);
7323 };
7324 SnapshotHolder.prototype.updateSnapshot = function (path, newSnapshotNode) {
7325 this.rootNode_ = this.rootNode_.updateChild(path, newSnapshotNode);
7326 };
7327 return SnapshotHolder;
7328}());
7329
7330/**
7331 * @license
7332 * Copyright 2017 Google LLC
7333 *
7334 * Licensed under the Apache License, Version 2.0 (the "License");
7335 * you may not use this file except in compliance with the License.
7336 * You may obtain a copy of the License at
7337 *
7338 * http://www.apache.org/licenses/LICENSE-2.0
7339 *
7340 * Unless required by applicable law or agreed to in writing, software
7341 * distributed under the License is distributed on an "AS IS" BASIS,
7342 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7343 * See the License for the specific language governing permissions and
7344 * limitations under the License.
7345 */
7346function newSparseSnapshotTree() {
7347 return {
7348 value: null,
7349 children: new Map()
7350 };
7351}
7352/**
7353 * Stores the given node at the specified path. If there is already a node
7354 * at a shallower path, it merges the new data into that snapshot node.
7355 *
7356 * @param path - Path to look up snapshot for.
7357 * @param data - The new data, or null.
7358 */
7359function sparseSnapshotTreeRemember(sparseSnapshotTree, path, data) {
7360 if (pathIsEmpty(path)) {
7361 sparseSnapshotTree.value = data;
7362 sparseSnapshotTree.children.clear();
7363 }
7364 else if (sparseSnapshotTree.value !== null) {
7365 sparseSnapshotTree.value = sparseSnapshotTree.value.updateChild(path, data);
7366 }
7367 else {
7368 var childKey = pathGetFront(path);
7369 if (!sparseSnapshotTree.children.has(childKey)) {
7370 sparseSnapshotTree.children.set(childKey, newSparseSnapshotTree());
7371 }
7372 var child = sparseSnapshotTree.children.get(childKey);
7373 path = pathPopFront(path);
7374 sparseSnapshotTreeRemember(child, path, data);
7375 }
7376}
7377/**
7378 * Purge the data at path from the cache.
7379 *
7380 * @param path - Path to look up snapshot for.
7381 * @returns True if this node should now be removed.
7382 */
7383function sparseSnapshotTreeForget(sparseSnapshotTree, path) {
7384 if (pathIsEmpty(path)) {
7385 sparseSnapshotTree.value = null;
7386 sparseSnapshotTree.children.clear();
7387 return true;
7388 }
7389 else {
7390 if (sparseSnapshotTree.value !== null) {
7391 if (sparseSnapshotTree.value.isLeafNode()) {
7392 // We're trying to forget a node that doesn't exist
7393 return false;
7394 }
7395 else {
7396 var value = sparseSnapshotTree.value;
7397 sparseSnapshotTree.value = null;
7398 value.forEachChild(PRIORITY_INDEX, function (key, tree) {
7399 sparseSnapshotTreeRemember(sparseSnapshotTree, new Path(key), tree);
7400 });
7401 return sparseSnapshotTreeForget(sparseSnapshotTree, path);
7402 }
7403 }
7404 else if (sparseSnapshotTree.children.size > 0) {
7405 var childKey = pathGetFront(path);
7406 path = pathPopFront(path);
7407 if (sparseSnapshotTree.children.has(childKey)) {
7408 var safeToRemove = sparseSnapshotTreeForget(sparseSnapshotTree.children.get(childKey), path);
7409 if (safeToRemove) {
7410 sparseSnapshotTree.children.delete(childKey);
7411 }
7412 }
7413 return sparseSnapshotTree.children.size === 0;
7414 }
7415 else {
7416 return true;
7417 }
7418 }
7419}
7420/**
7421 * Recursively iterates through all of the stored tree and calls the
7422 * callback on each one.
7423 *
7424 * @param prefixPath - Path to look up node for.
7425 * @param func - The function to invoke for each tree.
7426 */
7427function sparseSnapshotTreeForEachTree(sparseSnapshotTree, prefixPath, func) {
7428 if (sparseSnapshotTree.value !== null) {
7429 func(prefixPath, sparseSnapshotTree.value);
7430 }
7431 else {
7432 sparseSnapshotTreeForEachChild(sparseSnapshotTree, function (key, tree) {
7433 var path = new Path(prefixPath.toString() + '/' + key);
7434 sparseSnapshotTreeForEachTree(tree, path, func);
7435 });
7436 }
7437}
7438/**
7439 * Iterates through each immediate child and triggers the callback.
7440 * Only seems to be used in tests.
7441 *
7442 * @param func - The function to invoke for each child.
7443 */
7444function sparseSnapshotTreeForEachChild(sparseSnapshotTree, func) {
7445 sparseSnapshotTree.children.forEach(function (tree, key) {
7446 func(key, tree);
7447 });
7448}
7449
7450/**
7451 * @license
7452 * Copyright 2017 Google LLC
7453 *
7454 * Licensed under the Apache License, Version 2.0 (the "License");
7455 * you may not use this file except in compliance with the License.
7456 * You may obtain a copy of the License at
7457 *
7458 * http://www.apache.org/licenses/LICENSE-2.0
7459 *
7460 * Unless required by applicable law or agreed to in writing, software
7461 * distributed under the License is distributed on an "AS IS" BASIS,
7462 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7463 * See the License for the specific language governing permissions and
7464 * limitations under the License.
7465 */
7466/**
7467 * Returns the delta from the previous call to get stats.
7468 *
7469 * @param collection_ - The collection to "listen" to.
7470 */
7471var StatsListener = /** @class */ (function () {
7472 function StatsListener(collection_) {
7473 this.collection_ = collection_;
7474 this.last_ = null;
7475 }
7476 StatsListener.prototype.get = function () {
7477 var newStats = this.collection_.get();
7478 var delta = tslib.__assign({}, newStats);
7479 if (this.last_) {
7480 each(this.last_, function (stat, value) {
7481 delta[stat] = delta[stat] - value;
7482 });
7483 }
7484 this.last_ = newStats;
7485 return delta;
7486 };
7487 return StatsListener;
7488}());
7489
7490/**
7491 * @license
7492 * Copyright 2017 Google LLC
7493 *
7494 * Licensed under the Apache License, Version 2.0 (the "License");
7495 * you may not use this file except in compliance with the License.
7496 * You may obtain a copy of the License at
7497 *
7498 * http://www.apache.org/licenses/LICENSE-2.0
7499 *
7500 * Unless required by applicable law or agreed to in writing, software
7501 * distributed under the License is distributed on an "AS IS" BASIS,
7502 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7503 * See the License for the specific language governing permissions and
7504 * limitations under the License.
7505 */
7506// Assuming some apps may have a short amount of time on page, and a bulk of firebase operations probably
7507// happen on page load, we try to report our first set of stats pretty quickly, but we wait at least 10
7508// seconds to try to ensure the Firebase connection is established / settled.
7509var FIRST_STATS_MIN_TIME = 10 * 1000;
7510var FIRST_STATS_MAX_TIME = 30 * 1000;
7511// We'll continue to report stats on average every 5 minutes.
7512var REPORT_STATS_INTERVAL = 5 * 60 * 1000;
7513var StatsReporter = /** @class */ (function () {
7514 function StatsReporter(collection, server_) {
7515 this.server_ = server_;
7516 this.statsToReport_ = {};
7517 this.statsListener_ = new StatsListener(collection);
7518 var timeout = FIRST_STATS_MIN_TIME +
7519 (FIRST_STATS_MAX_TIME - FIRST_STATS_MIN_TIME) * Math.random();
7520 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(timeout));
7521 }
7522 StatsReporter.prototype.reportStats_ = function () {
7523 var _this = this;
7524 var stats = this.statsListener_.get();
7525 var reportedStats = {};
7526 var haveStatsToReport = false;
7527 each(stats, function (stat, value) {
7528 if (value > 0 && util.contains(_this.statsToReport_, stat)) {
7529 reportedStats[stat] = value;
7530 haveStatsToReport = true;
7531 }
7532 });
7533 if (haveStatsToReport) {
7534 this.server_.reportStats(reportedStats);
7535 }
7536 // queue our next run.
7537 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(Math.random() * 2 * REPORT_STATS_INTERVAL));
7538 };
7539 return StatsReporter;
7540}());
7541function statsReporterIncludeStat(reporter, stat) {
7542 reporter.statsToReport_[stat] = true;
7543}
7544
7545/**
7546 * @license
7547 * Copyright 2017 Google LLC
7548 *
7549 * Licensed under the Apache License, Version 2.0 (the "License");
7550 * you may not use this file except in compliance with the License.
7551 * You may obtain a copy of the License at
7552 *
7553 * http://www.apache.org/licenses/LICENSE-2.0
7554 *
7555 * Unless required by applicable law or agreed to in writing, software
7556 * distributed under the License is distributed on an "AS IS" BASIS,
7557 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7558 * See the License for the specific language governing permissions and
7559 * limitations under the License.
7560 */
7561/**
7562 *
7563 * @enum
7564 */
7565var OperationType;
7566(function (OperationType) {
7567 OperationType[OperationType["OVERWRITE"] = 0] = "OVERWRITE";
7568 OperationType[OperationType["MERGE"] = 1] = "MERGE";
7569 OperationType[OperationType["ACK_USER_WRITE"] = 2] = "ACK_USER_WRITE";
7570 OperationType[OperationType["LISTEN_COMPLETE"] = 3] = "LISTEN_COMPLETE";
7571})(OperationType || (OperationType = {}));
7572function newOperationSourceUser() {
7573 return {
7574 fromUser: true,
7575 fromServer: false,
7576 queryId: null,
7577 tagged: false
7578 };
7579}
7580function newOperationSourceServer() {
7581 return {
7582 fromUser: false,
7583 fromServer: true,
7584 queryId: null,
7585 tagged: false
7586 };
7587}
7588function newOperationSourceServerTaggedQuery(queryId) {
7589 return {
7590 fromUser: false,
7591 fromServer: true,
7592 queryId: queryId,
7593 tagged: true
7594 };
7595}
7596
7597/**
7598 * @license
7599 * Copyright 2017 Google LLC
7600 *
7601 * Licensed under the Apache License, Version 2.0 (the "License");
7602 * you may not use this file except in compliance with the License.
7603 * You may obtain a copy of the License at
7604 *
7605 * http://www.apache.org/licenses/LICENSE-2.0
7606 *
7607 * Unless required by applicable law or agreed to in writing, software
7608 * distributed under the License is distributed on an "AS IS" BASIS,
7609 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7610 * See the License for the specific language governing permissions and
7611 * limitations under the License.
7612 */
7613var AckUserWrite = /** @class */ (function () {
7614 /**
7615 * @param affectedTree - A tree containing true for each affected path. Affected paths can't overlap.
7616 */
7617 function AckUserWrite(
7618 /** @inheritDoc */ path,
7619 /** @inheritDoc */ affectedTree,
7620 /** @inheritDoc */ revert) {
7621 this.path = path;
7622 this.affectedTree = affectedTree;
7623 this.revert = revert;
7624 /** @inheritDoc */
7625 this.type = OperationType.ACK_USER_WRITE;
7626 /** @inheritDoc */
7627 this.source = newOperationSourceUser();
7628 }
7629 AckUserWrite.prototype.operationForChild = function (childName) {
7630 if (!pathIsEmpty(this.path)) {
7631 util.assert(pathGetFront(this.path) === childName, 'operationForChild called for unrelated child.');
7632 return new AckUserWrite(pathPopFront(this.path), this.affectedTree, this.revert);
7633 }
7634 else if (this.affectedTree.value != null) {
7635 util.assert(this.affectedTree.children.isEmpty(), 'affectedTree should not have overlapping affected paths.');
7636 // All child locations are affected as well; just return same operation.
7637 return this;
7638 }
7639 else {
7640 var childTree = this.affectedTree.subtree(new Path(childName));
7641 return new AckUserWrite(newEmptyPath(), childTree, this.revert);
7642 }
7643 };
7644 return AckUserWrite;
7645}());
7646
7647/**
7648 * @license
7649 * Copyright 2017 Google LLC
7650 *
7651 * Licensed under the Apache License, Version 2.0 (the "License");
7652 * you may not use this file except in compliance with the License.
7653 * You may obtain a copy of the License at
7654 *
7655 * http://www.apache.org/licenses/LICENSE-2.0
7656 *
7657 * Unless required by applicable law or agreed to in writing, software
7658 * distributed under the License is distributed on an "AS IS" BASIS,
7659 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7660 * See the License for the specific language governing permissions and
7661 * limitations under the License.
7662 */
7663var ListenComplete = /** @class */ (function () {
7664 function ListenComplete(source, path) {
7665 this.source = source;
7666 this.path = path;
7667 /** @inheritDoc */
7668 this.type = OperationType.LISTEN_COMPLETE;
7669 }
7670 ListenComplete.prototype.operationForChild = function (childName) {
7671 if (pathIsEmpty(this.path)) {
7672 return new ListenComplete(this.source, newEmptyPath());
7673 }
7674 else {
7675 return new ListenComplete(this.source, pathPopFront(this.path));
7676 }
7677 };
7678 return ListenComplete;
7679}());
7680
7681/**
7682 * @license
7683 * Copyright 2017 Google LLC
7684 *
7685 * Licensed under the Apache License, Version 2.0 (the "License");
7686 * you may not use this file except in compliance with the License.
7687 * You may obtain a copy of the License at
7688 *
7689 * http://www.apache.org/licenses/LICENSE-2.0
7690 *
7691 * Unless required by applicable law or agreed to in writing, software
7692 * distributed under the License is distributed on an "AS IS" BASIS,
7693 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7694 * See the License for the specific language governing permissions and
7695 * limitations under the License.
7696 */
7697var Overwrite = /** @class */ (function () {
7698 function Overwrite(source, path, snap) {
7699 this.source = source;
7700 this.path = path;
7701 this.snap = snap;
7702 /** @inheritDoc */
7703 this.type = OperationType.OVERWRITE;
7704 }
7705 Overwrite.prototype.operationForChild = function (childName) {
7706 if (pathIsEmpty(this.path)) {
7707 return new Overwrite(this.source, newEmptyPath(), this.snap.getImmediateChild(childName));
7708 }
7709 else {
7710 return new Overwrite(this.source, pathPopFront(this.path), this.snap);
7711 }
7712 };
7713 return Overwrite;
7714}());
7715
7716/**
7717 * @license
7718 * Copyright 2017 Google LLC
7719 *
7720 * Licensed under the Apache License, Version 2.0 (the "License");
7721 * you may not use this file except in compliance with the License.
7722 * You may obtain a copy of the License at
7723 *
7724 * http://www.apache.org/licenses/LICENSE-2.0
7725 *
7726 * Unless required by applicable law or agreed to in writing, software
7727 * distributed under the License is distributed on an "AS IS" BASIS,
7728 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7729 * See the License for the specific language governing permissions and
7730 * limitations under the License.
7731 */
7732var Merge = /** @class */ (function () {
7733 function Merge(
7734 /** @inheritDoc */ source,
7735 /** @inheritDoc */ path,
7736 /** @inheritDoc */ children) {
7737 this.source = source;
7738 this.path = path;
7739 this.children = children;
7740 /** @inheritDoc */
7741 this.type = OperationType.MERGE;
7742 }
7743 Merge.prototype.operationForChild = function (childName) {
7744 if (pathIsEmpty(this.path)) {
7745 var childTree = this.children.subtree(new Path(childName));
7746 if (childTree.isEmpty()) {
7747 // This child is unaffected
7748 return null;
7749 }
7750 else if (childTree.value) {
7751 // We have a snapshot for the child in question. This becomes an overwrite of the child.
7752 return new Overwrite(this.source, newEmptyPath(), childTree.value);
7753 }
7754 else {
7755 // This is a merge at a deeper level
7756 return new Merge(this.source, newEmptyPath(), childTree);
7757 }
7758 }
7759 else {
7760 util.assert(pathGetFront(this.path) === childName, "Can't get a merge for a child not on the path of the operation");
7761 return new Merge(this.source, pathPopFront(this.path), this.children);
7762 }
7763 };
7764 Merge.prototype.toString = function () {
7765 return ('Operation(' +
7766 this.path +
7767 ': ' +
7768 this.source.toString() +
7769 ' merge: ' +
7770 this.children.toString() +
7771 ')');
7772 };
7773 return Merge;
7774}());
7775
7776/**
7777 * @license
7778 * Copyright 2017 Google LLC
7779 *
7780 * Licensed under the Apache License, Version 2.0 (the "License");
7781 * you may not use this file except in compliance with the License.
7782 * You may obtain a copy of the License at
7783 *
7784 * http://www.apache.org/licenses/LICENSE-2.0
7785 *
7786 * Unless required by applicable law or agreed to in writing, software
7787 * distributed under the License is distributed on an "AS IS" BASIS,
7788 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7789 * See the License for the specific language governing permissions and
7790 * limitations under the License.
7791 */
7792/**
7793 * A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
7794 * initialized in the sense that we know at one point in time this represented a valid state of the world, e.g.
7795 * initialized with data from the server, or a complete overwrite by the client. The filtered flag also tracks
7796 * whether a node potentially had children removed due to a filter.
7797 */
7798var CacheNode = /** @class */ (function () {
7799 function CacheNode(node_, fullyInitialized_, filtered_) {
7800 this.node_ = node_;
7801 this.fullyInitialized_ = fullyInitialized_;
7802 this.filtered_ = filtered_;
7803 }
7804 /**
7805 * Returns whether this node was fully initialized with either server data or a complete overwrite by the client
7806 */
7807 CacheNode.prototype.isFullyInitialized = function () {
7808 return this.fullyInitialized_;
7809 };
7810 /**
7811 * Returns whether this node is potentially missing children due to a filter applied to the node
7812 */
7813 CacheNode.prototype.isFiltered = function () {
7814 return this.filtered_;
7815 };
7816 CacheNode.prototype.isCompleteForPath = function (path) {
7817 if (pathIsEmpty(path)) {
7818 return this.isFullyInitialized() && !this.filtered_;
7819 }
7820 var childKey = pathGetFront(path);
7821 return this.isCompleteForChild(childKey);
7822 };
7823 CacheNode.prototype.isCompleteForChild = function (key) {
7824 return ((this.isFullyInitialized() && !this.filtered_) || this.node_.hasChild(key));
7825 };
7826 CacheNode.prototype.getNode = function () {
7827 return this.node_;
7828 };
7829 return CacheNode;
7830}());
7831
7832/**
7833 * @license
7834 * Copyright 2017 Google LLC
7835 *
7836 * Licensed under the Apache License, Version 2.0 (the "License");
7837 * you may not use this file except in compliance with the License.
7838 * You may obtain a copy of the License at
7839 *
7840 * http://www.apache.org/licenses/LICENSE-2.0
7841 *
7842 * Unless required by applicable law or agreed to in writing, software
7843 * distributed under the License is distributed on an "AS IS" BASIS,
7844 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7845 * See the License for the specific language governing permissions and
7846 * limitations under the License.
7847 */
7848/**
7849 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
7850 * CacheDiffer into actual events (Event) that can be raised. See generateEventsForChanges()
7851 * for details.
7852 *
7853 */
7854var EventGenerator = /** @class */ (function () {
7855 function EventGenerator(query_) {
7856 this.query_ = query_;
7857 this.index_ = this.query_._queryParams.getIndex();
7858 }
7859 return EventGenerator;
7860}());
7861/**
7862 * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
7863 * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
7864 *
7865 * Notes:
7866 * - child_moved events will be synthesized at this time for any child_changed events that affect
7867 * our index.
7868 * - prevName will be calculated based on the index ordering.
7869 */
7870function eventGeneratorGenerateEventsForChanges(eventGenerator, changes, eventCache, eventRegistrations) {
7871 var events = [];
7872 var moves = [];
7873 changes.forEach(function (change) {
7874 if (change.type === "child_changed" /* CHILD_CHANGED */ &&
7875 eventGenerator.index_.indexedValueChanged(change.oldSnap, change.snapshotNode)) {
7876 moves.push(changeChildMoved(change.childName, change.snapshotNode));
7877 }
7878 });
7879 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_removed" /* CHILD_REMOVED */, changes, eventRegistrations, eventCache);
7880 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_added" /* CHILD_ADDED */, changes, eventRegistrations, eventCache);
7881 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_moved" /* CHILD_MOVED */, moves, eventRegistrations, eventCache);
7882 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_changed" /* CHILD_CHANGED */, changes, eventRegistrations, eventCache);
7883 eventGeneratorGenerateEventsForType(eventGenerator, events, "value" /* VALUE */, changes, eventRegistrations, eventCache);
7884 return events;
7885}
7886/**
7887 * Given changes of a single change type, generate the corresponding events.
7888 */
7889function eventGeneratorGenerateEventsForType(eventGenerator, events, eventType, changes, registrations, eventCache) {
7890 var filteredChanges = changes.filter(function (change) { return change.type === eventType; });
7891 filteredChanges.sort(function (a, b) {
7892 return eventGeneratorCompareChanges(eventGenerator, a, b);
7893 });
7894 filteredChanges.forEach(function (change) {
7895 var materializedChange = eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache);
7896 registrations.forEach(function (registration) {
7897 if (registration.respondsTo(change.type)) {
7898 events.push(registration.createEvent(materializedChange, eventGenerator.query_));
7899 }
7900 });
7901 });
7902}
7903function eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache) {
7904 if (change.type === 'value' || change.type === 'child_removed') {
7905 return change;
7906 }
7907 else {
7908 change.prevName = eventCache.getPredecessorChildName(change.childName, change.snapshotNode, eventGenerator.index_);
7909 return change;
7910 }
7911}
7912function eventGeneratorCompareChanges(eventGenerator, a, b) {
7913 if (a.childName == null || b.childName == null) {
7914 throw util.assertionError('Should only compare child_ events.');
7915 }
7916 var aWrapped = new NamedNode(a.childName, a.snapshotNode);
7917 var bWrapped = new NamedNode(b.childName, b.snapshotNode);
7918 return eventGenerator.index_.compare(aWrapped, bWrapped);
7919}
7920
7921/**
7922 * @license
7923 * Copyright 2017 Google LLC
7924 *
7925 * Licensed under the Apache License, Version 2.0 (the "License");
7926 * you may not use this file except in compliance with the License.
7927 * You may obtain a copy of the License at
7928 *
7929 * http://www.apache.org/licenses/LICENSE-2.0
7930 *
7931 * Unless required by applicable law or agreed to in writing, software
7932 * distributed under the License is distributed on an "AS IS" BASIS,
7933 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7934 * See the License for the specific language governing permissions and
7935 * limitations under the License.
7936 */
7937function newViewCache(eventCache, serverCache) {
7938 return { eventCache: eventCache, serverCache: serverCache };
7939}
7940function viewCacheUpdateEventSnap(viewCache, eventSnap, complete, filtered) {
7941 return newViewCache(new CacheNode(eventSnap, complete, filtered), viewCache.serverCache);
7942}
7943function viewCacheUpdateServerSnap(viewCache, serverSnap, complete, filtered) {
7944 return newViewCache(viewCache.eventCache, new CacheNode(serverSnap, complete, filtered));
7945}
7946function viewCacheGetCompleteEventSnap(viewCache) {
7947 return viewCache.eventCache.isFullyInitialized()
7948 ? viewCache.eventCache.getNode()
7949 : null;
7950}
7951function viewCacheGetCompleteServerSnap(viewCache) {
7952 return viewCache.serverCache.isFullyInitialized()
7953 ? viewCache.serverCache.getNode()
7954 : null;
7955}
7956
7957/**
7958 * @license
7959 * Copyright 2017 Google LLC
7960 *
7961 * Licensed under the Apache License, Version 2.0 (the "License");
7962 * you may not use this file except in compliance with the License.
7963 * You may obtain a copy of the License at
7964 *
7965 * http://www.apache.org/licenses/LICENSE-2.0
7966 *
7967 * Unless required by applicable law or agreed to in writing, software
7968 * distributed under the License is distributed on an "AS IS" BASIS,
7969 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7970 * See the License for the specific language governing permissions and
7971 * limitations under the License.
7972 */
7973var emptyChildrenSingleton;
7974/**
7975 * Singleton empty children collection.
7976 *
7977 */
7978var EmptyChildren = function () {
7979 if (!emptyChildrenSingleton) {
7980 emptyChildrenSingleton = new SortedMap(stringCompare);
7981 }
7982 return emptyChildrenSingleton;
7983};
7984/**
7985 * A tree with immutable elements.
7986 */
7987var ImmutableTree = /** @class */ (function () {
7988 function ImmutableTree(value, children) {
7989 if (children === void 0) { children = EmptyChildren(); }
7990 this.value = value;
7991 this.children = children;
7992 }
7993 ImmutableTree.fromObject = function (obj) {
7994 var tree = new ImmutableTree(null);
7995 each(obj, function (childPath, childSnap) {
7996 tree = tree.set(new Path(childPath), childSnap);
7997 });
7998 return tree;
7999 };
8000 /**
8001 * True if the value is empty and there are no children
8002 */
8003 ImmutableTree.prototype.isEmpty = function () {
8004 return this.value === null && this.children.isEmpty();
8005 };
8006 /**
8007 * Given a path and predicate, return the first node and the path to that node
8008 * where the predicate returns true.
8009 *
8010 * TODO Do a perf test -- If we're creating a bunch of `{path: value:}`
8011 * objects on the way back out, it may be better to pass down a pathSoFar obj.
8012 *
8013 * @param relativePath - The remainder of the path
8014 * @param predicate - The predicate to satisfy to return a node
8015 */
8016 ImmutableTree.prototype.findRootMostMatchingPathAndValue = function (relativePath, predicate) {
8017 if (this.value != null && predicate(this.value)) {
8018 return { path: newEmptyPath(), value: this.value };
8019 }
8020 else {
8021 if (pathIsEmpty(relativePath)) {
8022 return null;
8023 }
8024 else {
8025 var front = pathGetFront(relativePath);
8026 var child = this.children.get(front);
8027 if (child !== null) {
8028 var childExistingPathAndValue = child.findRootMostMatchingPathAndValue(pathPopFront(relativePath), predicate);
8029 if (childExistingPathAndValue != null) {
8030 var fullPath = pathChild(new Path(front), childExistingPathAndValue.path);
8031 return { path: fullPath, value: childExistingPathAndValue.value };
8032 }
8033 else {
8034 return null;
8035 }
8036 }
8037 else {
8038 return null;
8039 }
8040 }
8041 }
8042 };
8043 /**
8044 * Find, if it exists, the shortest subpath of the given path that points a defined
8045 * value in the tree
8046 */
8047 ImmutableTree.prototype.findRootMostValueAndPath = function (relativePath) {
8048 return this.findRootMostMatchingPathAndValue(relativePath, function () { return true; });
8049 };
8050 /**
8051 * @returns The subtree at the given path
8052 */
8053 ImmutableTree.prototype.subtree = function (relativePath) {
8054 if (pathIsEmpty(relativePath)) {
8055 return this;
8056 }
8057 else {
8058 var front = pathGetFront(relativePath);
8059 var childTree = this.children.get(front);
8060 if (childTree !== null) {
8061 return childTree.subtree(pathPopFront(relativePath));
8062 }
8063 else {
8064 return new ImmutableTree(null);
8065 }
8066 }
8067 };
8068 /**
8069 * Sets a value at the specified path.
8070 *
8071 * @param relativePath - Path to set value at.
8072 * @param toSet - Value to set.
8073 * @returns Resulting tree.
8074 */
8075 ImmutableTree.prototype.set = function (relativePath, toSet) {
8076 if (pathIsEmpty(relativePath)) {
8077 return new ImmutableTree(toSet, this.children);
8078 }
8079 else {
8080 var front = pathGetFront(relativePath);
8081 var child = this.children.get(front) || new ImmutableTree(null);
8082 var newChild = child.set(pathPopFront(relativePath), toSet);
8083 var newChildren = this.children.insert(front, newChild);
8084 return new ImmutableTree(this.value, newChildren);
8085 }
8086 };
8087 /**
8088 * Removes the value at the specified path.
8089 *
8090 * @param relativePath - Path to value to remove.
8091 * @returns Resulting tree.
8092 */
8093 ImmutableTree.prototype.remove = function (relativePath) {
8094 if (pathIsEmpty(relativePath)) {
8095 if (this.children.isEmpty()) {
8096 return new ImmutableTree(null);
8097 }
8098 else {
8099 return new ImmutableTree(null, this.children);
8100 }
8101 }
8102 else {
8103 var front = pathGetFront(relativePath);
8104 var child = this.children.get(front);
8105 if (child) {
8106 var newChild = child.remove(pathPopFront(relativePath));
8107 var newChildren = void 0;
8108 if (newChild.isEmpty()) {
8109 newChildren = this.children.remove(front);
8110 }
8111 else {
8112 newChildren = this.children.insert(front, newChild);
8113 }
8114 if (this.value === null && newChildren.isEmpty()) {
8115 return new ImmutableTree(null);
8116 }
8117 else {
8118 return new ImmutableTree(this.value, newChildren);
8119 }
8120 }
8121 else {
8122 return this;
8123 }
8124 }
8125 };
8126 /**
8127 * Gets a value from the tree.
8128 *
8129 * @param relativePath - Path to get value for.
8130 * @returns Value at path, or null.
8131 */
8132 ImmutableTree.prototype.get = function (relativePath) {
8133 if (pathIsEmpty(relativePath)) {
8134 return this.value;
8135 }
8136 else {
8137 var front = pathGetFront(relativePath);
8138 var child = this.children.get(front);
8139 if (child) {
8140 return child.get(pathPopFront(relativePath));
8141 }
8142 else {
8143 return null;
8144 }
8145 }
8146 };
8147 /**
8148 * Replace the subtree at the specified path with the given new tree.
8149 *
8150 * @param relativePath - Path to replace subtree for.
8151 * @param newTree - New tree.
8152 * @returns Resulting tree.
8153 */
8154 ImmutableTree.prototype.setTree = function (relativePath, newTree) {
8155 if (pathIsEmpty(relativePath)) {
8156 return newTree;
8157 }
8158 else {
8159 var front = pathGetFront(relativePath);
8160 var child = this.children.get(front) || new ImmutableTree(null);
8161 var newChild = child.setTree(pathPopFront(relativePath), newTree);
8162 var newChildren = void 0;
8163 if (newChild.isEmpty()) {
8164 newChildren = this.children.remove(front);
8165 }
8166 else {
8167 newChildren = this.children.insert(front, newChild);
8168 }
8169 return new ImmutableTree(this.value, newChildren);
8170 }
8171 };
8172 /**
8173 * Performs a depth first fold on this tree. Transforms a tree into a single
8174 * value, given a function that operates on the path to a node, an optional
8175 * current value, and a map of child names to folded subtrees
8176 */
8177 ImmutableTree.prototype.fold = function (fn) {
8178 return this.fold_(newEmptyPath(), fn);
8179 };
8180 /**
8181 * Recursive helper for public-facing fold() method
8182 */
8183 ImmutableTree.prototype.fold_ = function (pathSoFar, fn) {
8184 var accum = {};
8185 this.children.inorderTraversal(function (childKey, childTree) {
8186 accum[childKey] = childTree.fold_(pathChild(pathSoFar, childKey), fn);
8187 });
8188 return fn(pathSoFar, this.value, accum);
8189 };
8190 /**
8191 * Find the first matching value on the given path. Return the result of applying f to it.
8192 */
8193 ImmutableTree.prototype.findOnPath = function (path, f) {
8194 return this.findOnPath_(path, newEmptyPath(), f);
8195 };
8196 ImmutableTree.prototype.findOnPath_ = function (pathToFollow, pathSoFar, f) {
8197 var result = this.value ? f(pathSoFar, this.value) : false;
8198 if (result) {
8199 return result;
8200 }
8201 else {
8202 if (pathIsEmpty(pathToFollow)) {
8203 return null;
8204 }
8205 else {
8206 var front = pathGetFront(pathToFollow);
8207 var nextChild = this.children.get(front);
8208 if (nextChild) {
8209 return nextChild.findOnPath_(pathPopFront(pathToFollow), pathChild(pathSoFar, front), f);
8210 }
8211 else {
8212 return null;
8213 }
8214 }
8215 }
8216 };
8217 ImmutableTree.prototype.foreachOnPath = function (path, f) {
8218 return this.foreachOnPath_(path, newEmptyPath(), f);
8219 };
8220 ImmutableTree.prototype.foreachOnPath_ = function (pathToFollow, currentRelativePath, f) {
8221 if (pathIsEmpty(pathToFollow)) {
8222 return this;
8223 }
8224 else {
8225 if (this.value) {
8226 f(currentRelativePath, this.value);
8227 }
8228 var front = pathGetFront(pathToFollow);
8229 var nextChild = this.children.get(front);
8230 if (nextChild) {
8231 return nextChild.foreachOnPath_(pathPopFront(pathToFollow), pathChild(currentRelativePath, front), f);
8232 }
8233 else {
8234 return new ImmutableTree(null);
8235 }
8236 }
8237 };
8238 /**
8239 * Calls the given function for each node in the tree that has a value.
8240 *
8241 * @param f - A function to be called with the path from the root of the tree to
8242 * a node, and the value at that node. Called in depth-first order.
8243 */
8244 ImmutableTree.prototype.foreach = function (f) {
8245 this.foreach_(newEmptyPath(), f);
8246 };
8247 ImmutableTree.prototype.foreach_ = function (currentRelativePath, f) {
8248 this.children.inorderTraversal(function (childName, childTree) {
8249 childTree.foreach_(pathChild(currentRelativePath, childName), f);
8250 });
8251 if (this.value) {
8252 f(currentRelativePath, this.value);
8253 }
8254 };
8255 ImmutableTree.prototype.foreachChild = function (f) {
8256 this.children.inorderTraversal(function (childName, childTree) {
8257 if (childTree.value) {
8258 f(childName, childTree.value);
8259 }
8260 });
8261 };
8262 return ImmutableTree;
8263}());
8264
8265/**
8266 * @license
8267 * Copyright 2017 Google LLC
8268 *
8269 * Licensed under the Apache License, Version 2.0 (the "License");
8270 * you may not use this file except in compliance with the License.
8271 * You may obtain a copy of the License at
8272 *
8273 * http://www.apache.org/licenses/LICENSE-2.0
8274 *
8275 * Unless required by applicable law or agreed to in writing, software
8276 * distributed under the License is distributed on an "AS IS" BASIS,
8277 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8278 * See the License for the specific language governing permissions and
8279 * limitations under the License.
8280 */
8281/**
8282 * This class holds a collection of writes that can be applied to nodes in unison. It abstracts away the logic with
8283 * dealing with priority writes and multiple nested writes. At any given path there is only allowed to be one write
8284 * modifying that path. Any write to an existing path or shadowing an existing path will modify that existing write
8285 * to reflect the write added.
8286 */
8287var CompoundWrite = /** @class */ (function () {
8288 function CompoundWrite(writeTree_) {
8289 this.writeTree_ = writeTree_;
8290 }
8291 CompoundWrite.empty = function () {
8292 return new CompoundWrite(new ImmutableTree(null));
8293 };
8294 return CompoundWrite;
8295}());
8296function compoundWriteAddWrite(compoundWrite, path, node) {
8297 if (pathIsEmpty(path)) {
8298 return new CompoundWrite(new ImmutableTree(node));
8299 }
8300 else {
8301 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8302 if (rootmost != null) {
8303 var rootMostPath = rootmost.path;
8304 var value = rootmost.value;
8305 var relativePath = newRelativePath(rootMostPath, path);
8306 value = value.updateChild(relativePath, node);
8307 return new CompoundWrite(compoundWrite.writeTree_.set(rootMostPath, value));
8308 }
8309 else {
8310 var subtree = new ImmutableTree(node);
8311 var newWriteTree = compoundWrite.writeTree_.setTree(path, subtree);
8312 return new CompoundWrite(newWriteTree);
8313 }
8314 }
8315}
8316function compoundWriteAddWrites(compoundWrite, path, updates) {
8317 var newWrite = compoundWrite;
8318 each(updates, function (childKey, node) {
8319 newWrite = compoundWriteAddWrite(newWrite, pathChild(path, childKey), node);
8320 });
8321 return newWrite;
8322}
8323/**
8324 * Will remove a write at the given path and deeper paths. This will <em>not</em> modify a write at a higher
8325 * location, which must be removed by calling this method with that path.
8326 *
8327 * @param compoundWrite - The CompoundWrite to remove.
8328 * @param path - The path at which a write and all deeper writes should be removed
8329 * @returns The new CompoundWrite with the removed path
8330 */
8331function compoundWriteRemoveWrite(compoundWrite, path) {
8332 if (pathIsEmpty(path)) {
8333 return CompoundWrite.empty();
8334 }
8335 else {
8336 var newWriteTree = compoundWrite.writeTree_.setTree(path, new ImmutableTree(null));
8337 return new CompoundWrite(newWriteTree);
8338 }
8339}
8340/**
8341 * Returns whether this CompoundWrite will fully overwrite a node at a given location and can therefore be
8342 * considered "complete".
8343 *
8344 * @param compoundWrite - The CompoundWrite to check.
8345 * @param path - The path to check for
8346 * @returns Whether there is a complete write at that path
8347 */
8348function compoundWriteHasCompleteWrite(compoundWrite, path) {
8349 return compoundWriteGetCompleteNode(compoundWrite, path) != null;
8350}
8351/**
8352 * Returns a node for a path if and only if the node is a "complete" overwrite at that path. This will not aggregate
8353 * writes from deeper paths, but will return child nodes from a more shallow path.
8354 *
8355 * @param compoundWrite - The CompoundWrite to get the node from.
8356 * @param path - The path to get a complete write
8357 * @returns The node if complete at that path, or null otherwise.
8358 */
8359function compoundWriteGetCompleteNode(compoundWrite, path) {
8360 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8361 if (rootmost != null) {
8362 return compoundWrite.writeTree_
8363 .get(rootmost.path)
8364 .getChild(newRelativePath(rootmost.path, path));
8365 }
8366 else {
8367 return null;
8368 }
8369}
8370/**
8371 * Returns all children that are guaranteed to be a complete overwrite.
8372 *
8373 * @param compoundWrite - The CompoundWrite to get children from.
8374 * @returns A list of all complete children.
8375 */
8376function compoundWriteGetCompleteChildren(compoundWrite) {
8377 var children = [];
8378 var node = compoundWrite.writeTree_.value;
8379 if (node != null) {
8380 // If it's a leaf node, it has no children; so nothing to do.
8381 if (!node.isLeafNode()) {
8382 node.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8383 children.push(new NamedNode(childName, childNode));
8384 });
8385 }
8386 }
8387 else {
8388 compoundWrite.writeTree_.children.inorderTraversal(function (childName, childTree) {
8389 if (childTree.value != null) {
8390 children.push(new NamedNode(childName, childTree.value));
8391 }
8392 });
8393 }
8394 return children;
8395}
8396function compoundWriteChildCompoundWrite(compoundWrite, path) {
8397 if (pathIsEmpty(path)) {
8398 return compoundWrite;
8399 }
8400 else {
8401 var shadowingNode = compoundWriteGetCompleteNode(compoundWrite, path);
8402 if (shadowingNode != null) {
8403 return new CompoundWrite(new ImmutableTree(shadowingNode));
8404 }
8405 else {
8406 return new CompoundWrite(compoundWrite.writeTree_.subtree(path));
8407 }
8408 }
8409}
8410/**
8411 * Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
8412 * @returns Whether this CompoundWrite is empty
8413 */
8414function compoundWriteIsEmpty(compoundWrite) {
8415 return compoundWrite.writeTree_.isEmpty();
8416}
8417/**
8418 * Applies this CompoundWrite to a node. The node is returned with all writes from this CompoundWrite applied to the
8419 * node
8420 * @param node - The node to apply this CompoundWrite to
8421 * @returns The node with all writes applied
8422 */
8423function compoundWriteApply(compoundWrite, node) {
8424 return applySubtreeWrite(newEmptyPath(), compoundWrite.writeTree_, node);
8425}
8426function applySubtreeWrite(relativePath, writeTree, node) {
8427 if (writeTree.value != null) {
8428 // Since there a write is always a leaf, we're done here
8429 return node.updateChild(relativePath, writeTree.value);
8430 }
8431 else {
8432 var priorityWrite_1 = null;
8433 writeTree.children.inorderTraversal(function (childKey, childTree) {
8434 if (childKey === '.priority') {
8435 // Apply priorities at the end so we don't update priorities for either empty nodes or forget
8436 // to apply priorities to empty nodes that are later filled
8437 util.assert(childTree.value !== null, 'Priority writes must always be leaf nodes');
8438 priorityWrite_1 = childTree.value;
8439 }
8440 else {
8441 node = applySubtreeWrite(pathChild(relativePath, childKey), childTree, node);
8442 }
8443 });
8444 // If there was a priority write, we only apply it if the node is not empty
8445 if (!node.getChild(relativePath).isEmpty() && priorityWrite_1 !== null) {
8446 node = node.updateChild(pathChild(relativePath, '.priority'), priorityWrite_1);
8447 }
8448 return node;
8449 }
8450}
8451
8452/**
8453 * @license
8454 * Copyright 2017 Google LLC
8455 *
8456 * Licensed under the Apache License, Version 2.0 (the "License");
8457 * you may not use this file except in compliance with the License.
8458 * You may obtain a copy of the License at
8459 *
8460 * http://www.apache.org/licenses/LICENSE-2.0
8461 *
8462 * Unless required by applicable law or agreed to in writing, software
8463 * distributed under the License is distributed on an "AS IS" BASIS,
8464 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8465 * See the License for the specific language governing permissions and
8466 * limitations under the License.
8467 */
8468/**
8469 * Create a new WriteTreeRef for the given path. For use with a new sync point at the given path.
8470 *
8471 */
8472function writeTreeChildWrites(writeTree, path) {
8473 return newWriteTreeRef(path, writeTree);
8474}
8475/**
8476 * Record a new overwrite from user code.
8477 *
8478 * @param visible - This is set to false by some transactions. It should be excluded from event caches
8479 */
8480function writeTreeAddOverwrite(writeTree, path, snap, writeId, visible) {
8481 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older write on top of newer ones');
8482 if (visible === undefined) {
8483 visible = true;
8484 }
8485 writeTree.allWrites.push({
8486 path: path,
8487 snap: snap,
8488 writeId: writeId,
8489 visible: visible
8490 });
8491 if (visible) {
8492 writeTree.visibleWrites = compoundWriteAddWrite(writeTree.visibleWrites, path, snap);
8493 }
8494 writeTree.lastWriteId = writeId;
8495}
8496/**
8497 * Record a new merge from user code.
8498 */
8499function writeTreeAddMerge(writeTree, path, changedChildren, writeId) {
8500 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older merge on top of newer ones');
8501 writeTree.allWrites.push({
8502 path: path,
8503 children: changedChildren,
8504 writeId: writeId,
8505 visible: true
8506 });
8507 writeTree.visibleWrites = compoundWriteAddWrites(writeTree.visibleWrites, path, changedChildren);
8508 writeTree.lastWriteId = writeId;
8509}
8510function writeTreeGetWrite(writeTree, writeId) {
8511 for (var i = 0; i < writeTree.allWrites.length; i++) {
8512 var record = writeTree.allWrites[i];
8513 if (record.writeId === writeId) {
8514 return record;
8515 }
8516 }
8517 return null;
8518}
8519/**
8520 * Remove a write (either an overwrite or merge) that has been successfully acknowledge by the server. Recalculates
8521 * the tree if necessary. We return true if it may have been visible, meaning views need to reevaluate.
8522 *
8523 * @returns true if the write may have been visible (meaning we'll need to reevaluate / raise
8524 * events as a result).
8525 */
8526function writeTreeRemoveWrite(writeTree, writeId) {
8527 // Note: disabling this check. It could be a transaction that preempted another transaction, and thus was applied
8528 // out of order.
8529 //const validClear = revert || this.allWrites_.length === 0 || writeId <= this.allWrites_[0].writeId;
8530 //assert(validClear, "Either we don't have this write, or it's the first one in the queue");
8531 var idx = writeTree.allWrites.findIndex(function (s) {
8532 return s.writeId === writeId;
8533 });
8534 util.assert(idx >= 0, 'removeWrite called with nonexistent writeId.');
8535 var writeToRemove = writeTree.allWrites[idx];
8536 writeTree.allWrites.splice(idx, 1);
8537 var removedWriteWasVisible = writeToRemove.visible;
8538 var removedWriteOverlapsWithOtherWrites = false;
8539 var i = writeTree.allWrites.length - 1;
8540 while (removedWriteWasVisible && i >= 0) {
8541 var currentWrite = writeTree.allWrites[i];
8542 if (currentWrite.visible) {
8543 if (i >= idx &&
8544 writeTreeRecordContainsPath_(currentWrite, writeToRemove.path)) {
8545 // The removed write was completely shadowed by a subsequent write.
8546 removedWriteWasVisible = false;
8547 }
8548 else if (pathContains(writeToRemove.path, currentWrite.path)) {
8549 // Either we're covering some writes or they're covering part of us (depending on which came first).
8550 removedWriteOverlapsWithOtherWrites = true;
8551 }
8552 }
8553 i--;
8554 }
8555 if (!removedWriteWasVisible) {
8556 return false;
8557 }
8558 else if (removedWriteOverlapsWithOtherWrites) {
8559 // There's some shadowing going on. Just rebuild the visible writes from scratch.
8560 writeTreeResetTree_(writeTree);
8561 return true;
8562 }
8563 else {
8564 // There's no shadowing. We can safely just remove the write(s) from visibleWrites.
8565 if (writeToRemove.snap) {
8566 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, writeToRemove.path);
8567 }
8568 else {
8569 var children = writeToRemove.children;
8570 each(children, function (childName) {
8571 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, pathChild(writeToRemove.path, childName));
8572 });
8573 }
8574 return true;
8575 }
8576}
8577function writeTreeRecordContainsPath_(writeRecord, path) {
8578 if (writeRecord.snap) {
8579 return pathContains(writeRecord.path, path);
8580 }
8581 else {
8582 for (var childName in writeRecord.children) {
8583 if (writeRecord.children.hasOwnProperty(childName) &&
8584 pathContains(pathChild(writeRecord.path, childName), path)) {
8585 return true;
8586 }
8587 }
8588 return false;
8589 }
8590}
8591/**
8592 * Re-layer the writes and merges into a tree so we can efficiently calculate event snapshots
8593 */
8594function writeTreeResetTree_(writeTree) {
8595 writeTree.visibleWrites = writeTreeLayerTree_(writeTree.allWrites, writeTreeDefaultFilter_, newEmptyPath());
8596 if (writeTree.allWrites.length > 0) {
8597 writeTree.lastWriteId =
8598 writeTree.allWrites[writeTree.allWrites.length - 1].writeId;
8599 }
8600 else {
8601 writeTree.lastWriteId = -1;
8602 }
8603}
8604/**
8605 * The default filter used when constructing the tree. Keep everything that's visible.
8606 */
8607function writeTreeDefaultFilter_(write) {
8608 return write.visible;
8609}
8610/**
8611 * Static method. Given an array of WriteRecords, a filter for which ones to include, and a path, construct the tree of
8612 * event data at that path.
8613 */
8614function writeTreeLayerTree_(writes, filter, treeRoot) {
8615 var compoundWrite = CompoundWrite.empty();
8616 for (var i = 0; i < writes.length; ++i) {
8617 var write = writes[i];
8618 // Theory, a later set will either:
8619 // a) abort a relevant transaction, so no need to worry about excluding it from calculating that transaction
8620 // b) not be relevant to a transaction (separate branch), so again will not affect the data for that transaction
8621 if (filter(write)) {
8622 var writePath = write.path;
8623 var relativePath = void 0;
8624 if (write.snap) {
8625 if (pathContains(treeRoot, writePath)) {
8626 relativePath = newRelativePath(treeRoot, writePath);
8627 compoundWrite = compoundWriteAddWrite(compoundWrite, relativePath, write.snap);
8628 }
8629 else if (pathContains(writePath, treeRoot)) {
8630 relativePath = newRelativePath(writePath, treeRoot);
8631 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), write.snap.getChild(relativePath));
8632 }
8633 else ;
8634 }
8635 else if (write.children) {
8636 if (pathContains(treeRoot, writePath)) {
8637 relativePath = newRelativePath(treeRoot, writePath);
8638 compoundWrite = compoundWriteAddWrites(compoundWrite, relativePath, write.children);
8639 }
8640 else if (pathContains(writePath, treeRoot)) {
8641 relativePath = newRelativePath(writePath, treeRoot);
8642 if (pathIsEmpty(relativePath)) {
8643 compoundWrite = compoundWriteAddWrites(compoundWrite, newEmptyPath(), write.children);
8644 }
8645 else {
8646 var child = util.safeGet(write.children, pathGetFront(relativePath));
8647 if (child) {
8648 // There exists a child in this node that matches the root path
8649 var deepNode = child.getChild(pathPopFront(relativePath));
8650 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), deepNode);
8651 }
8652 }
8653 }
8654 else ;
8655 }
8656 else {
8657 throw util.assertionError('WriteRecord should have .snap or .children');
8658 }
8659 }
8660 }
8661 return compoundWrite;
8662}
8663/**
8664 * Given optional, underlying server data, and an optional set of constraints (exclude some sets, include hidden
8665 * writes), attempt to calculate a complete snapshot for the given path
8666 *
8667 * @param writeIdsToExclude - An optional set to be excluded
8668 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8669 */
8670function writeTreeCalcCompleteEventCache(writeTree, treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8671 if (!writeIdsToExclude && !includeHiddenWrites) {
8672 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8673 if (shadowingNode != null) {
8674 return shadowingNode;
8675 }
8676 else {
8677 var subMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8678 if (compoundWriteIsEmpty(subMerge)) {
8679 return completeServerCache;
8680 }
8681 else if (completeServerCache == null &&
8682 !compoundWriteHasCompleteWrite(subMerge, newEmptyPath())) {
8683 // We wouldn't have a complete snapshot, since there's no underlying data and no complete shadow
8684 return null;
8685 }
8686 else {
8687 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8688 return compoundWriteApply(subMerge, layeredCache);
8689 }
8690 }
8691 }
8692 else {
8693 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8694 if (!includeHiddenWrites && compoundWriteIsEmpty(merge)) {
8695 return completeServerCache;
8696 }
8697 else {
8698 // If the server cache is null, and we don't have a complete cache, we need to return null
8699 if (!includeHiddenWrites &&
8700 completeServerCache == null &&
8701 !compoundWriteHasCompleteWrite(merge, newEmptyPath())) {
8702 return null;
8703 }
8704 else {
8705 var filter = function (write) {
8706 return ((write.visible || includeHiddenWrites) &&
8707 (!writeIdsToExclude ||
8708 !~writeIdsToExclude.indexOf(write.writeId)) &&
8709 (pathContains(write.path, treePath) ||
8710 pathContains(treePath, write.path)));
8711 };
8712 var mergeAtPath = writeTreeLayerTree_(writeTree.allWrites, filter, treePath);
8713 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8714 return compoundWriteApply(mergeAtPath, layeredCache);
8715 }
8716 }
8717 }
8718}
8719/**
8720 * With optional, underlying server data, attempt to return a children node of children that we have complete data for.
8721 * Used when creating new views, to pre-fill their complete event children snapshot.
8722 */
8723function writeTreeCalcCompleteEventChildren(writeTree, treePath, completeServerChildren) {
8724 var completeChildren = ChildrenNode.EMPTY_NODE;
8725 var topLevelSet = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8726 if (topLevelSet) {
8727 if (!topLevelSet.isLeafNode()) {
8728 // we're shadowing everything. Return the children.
8729 topLevelSet.forEachChild(PRIORITY_INDEX, function (childName, childSnap) {
8730 completeChildren = completeChildren.updateImmediateChild(childName, childSnap);
8731 });
8732 }
8733 return completeChildren;
8734 }
8735 else if (completeServerChildren) {
8736 // Layer any children we have on top of this
8737 // We know we don't have a top-level set, so just enumerate existing children
8738 var merge_1 = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8739 completeServerChildren.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8740 var node = compoundWriteApply(compoundWriteChildCompoundWrite(merge_1, new Path(childName)), childNode);
8741 completeChildren = completeChildren.updateImmediateChild(childName, node);
8742 });
8743 // Add any complete children we have from the set
8744 compoundWriteGetCompleteChildren(merge_1).forEach(function (namedNode) {
8745 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8746 });
8747 return completeChildren;
8748 }
8749 else {
8750 // We don't have anything to layer on top of. Layer on any children we have
8751 // Note that we can return an empty snap if we have a defined delete
8752 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8753 compoundWriteGetCompleteChildren(merge).forEach(function (namedNode) {
8754 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8755 });
8756 return completeChildren;
8757 }
8758}
8759/**
8760 * Given that the underlying server data has updated, determine what, if anything, needs to be
8761 * applied to the event cache.
8762 *
8763 * Possibilities:
8764 *
8765 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8766 *
8767 * 2. Some write is completely shadowing. No events to be raised
8768 *
8769 * 3. Is partially shadowed. Events
8770 *
8771 * Either existingEventSnap or existingServerSnap must exist
8772 */
8773function writeTreeCalcEventCacheAfterServerOverwrite(writeTree, treePath, childPath, existingEventSnap, existingServerSnap) {
8774 util.assert(existingEventSnap || existingServerSnap, 'Either existingEventSnap or existingServerSnap must exist');
8775 var path = pathChild(treePath, childPath);
8776 if (compoundWriteHasCompleteWrite(writeTree.visibleWrites, path)) {
8777 // At this point we can probably guarantee that we're in case 2, meaning no events
8778 // May need to check visibility while doing the findRootMostValueAndPath call
8779 return null;
8780 }
8781 else {
8782 // No complete shadowing. We're either partially shadowing or not shadowing at all.
8783 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8784 if (compoundWriteIsEmpty(childMerge)) {
8785 // We're not shadowing at all. Case 1
8786 return existingServerSnap.getChild(childPath);
8787 }
8788 else {
8789 // This could be more efficient if the serverNode + updates doesn't change the eventSnap
8790 // However this is tricky to find out, since user updates don't necessary change the server
8791 // snap, e.g. priority updates on empty nodes, or deep deletes. Another special case is if the server
8792 // adds nodes, but doesn't change any existing writes. It is therefore not enough to
8793 // only check if the updates change the serverNode.
8794 // Maybe check if the merge tree contains these special cases and only do a full overwrite in that case?
8795 return compoundWriteApply(childMerge, existingServerSnap.getChild(childPath));
8796 }
8797 }
8798}
8799/**
8800 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8801 * complete child for this ChildKey.
8802 */
8803function writeTreeCalcCompleteChild(writeTree, treePath, childKey, existingServerSnap) {
8804 var path = pathChild(treePath, childKey);
8805 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8806 if (shadowingNode != null) {
8807 return shadowingNode;
8808 }
8809 else {
8810 if (existingServerSnap.isCompleteForChild(childKey)) {
8811 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8812 return compoundWriteApply(childMerge, existingServerSnap.getNode().getImmediateChild(childKey));
8813 }
8814 else {
8815 return null;
8816 }
8817 }
8818}
8819/**
8820 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8821 * a higher path, this will return the child of that write relative to the write and this path.
8822 * Returns null if there is no write at this path.
8823 */
8824function writeTreeShadowingWrite(writeTree, path) {
8825 return compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8826}
8827/**
8828 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8829 * the window, but may now be in the window.
8830 */
8831function writeTreeCalcIndexedSlice(writeTree, treePath, completeServerData, startPost, count, reverse, index) {
8832 var toIterate;
8833 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8834 var shadowingNode = compoundWriteGetCompleteNode(merge, newEmptyPath());
8835 if (shadowingNode != null) {
8836 toIterate = shadowingNode;
8837 }
8838 else if (completeServerData != null) {
8839 toIterate = compoundWriteApply(merge, completeServerData);
8840 }
8841 else {
8842 // no children to iterate on
8843 return [];
8844 }
8845 toIterate = toIterate.withIndex(index);
8846 if (!toIterate.isEmpty() && !toIterate.isLeafNode()) {
8847 var nodes = [];
8848 var cmp = index.getCompare();
8849 var iter = reverse
8850 ? toIterate.getReverseIteratorFrom(startPost, index)
8851 : toIterate.getIteratorFrom(startPost, index);
8852 var next = iter.getNext();
8853 while (next && nodes.length < count) {
8854 if (cmp(next, startPost) !== 0) {
8855 nodes.push(next);
8856 }
8857 next = iter.getNext();
8858 }
8859 return nodes;
8860 }
8861 else {
8862 return [];
8863 }
8864}
8865function newWriteTree() {
8866 return {
8867 visibleWrites: CompoundWrite.empty(),
8868 allWrites: [],
8869 lastWriteId: -1
8870 };
8871}
8872/**
8873 * If possible, returns a complete event cache, using the underlying server data if possible. In addition, can be used
8874 * to get a cache that includes hidden writes, and excludes arbitrary writes. Note that customizing the returned node
8875 * can lead to a more expensive calculation.
8876 *
8877 * @param writeIdsToExclude - Optional writes to exclude.
8878 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8879 */
8880function writeTreeRefCalcCompleteEventCache(writeTreeRef, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8881 return writeTreeCalcCompleteEventCache(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites);
8882}
8883/**
8884 * If possible, returns a children node containing all of the complete children we have data for. The returned data is a
8885 * mix of the given server data and write data.
8886 *
8887 */
8888function writeTreeRefCalcCompleteEventChildren(writeTreeRef, completeServerChildren) {
8889 return writeTreeCalcCompleteEventChildren(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerChildren);
8890}
8891/**
8892 * Given that either the underlying server data has updated or the outstanding writes have updated, determine what,
8893 * if anything, needs to be applied to the event cache.
8894 *
8895 * Possibilities:
8896 *
8897 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8898 *
8899 * 2. Some write is completely shadowing. No events to be raised
8900 *
8901 * 3. Is partially shadowed. Events should be raised
8902 *
8903 * Either existingEventSnap or existingServerSnap must exist, this is validated via an assert
8904 *
8905 *
8906 */
8907function writeTreeRefCalcEventCacheAfterServerOverwrite(writeTreeRef, path, existingEventSnap, existingServerSnap) {
8908 return writeTreeCalcEventCacheAfterServerOverwrite(writeTreeRef.writeTree, writeTreeRef.treePath, path, existingEventSnap, existingServerSnap);
8909}
8910/**
8911 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8912 * a higher path, this will return the child of that write relative to the write and this path.
8913 * Returns null if there is no write at this path.
8914 *
8915 */
8916function writeTreeRefShadowingWrite(writeTreeRef, path) {
8917 return writeTreeShadowingWrite(writeTreeRef.writeTree, pathChild(writeTreeRef.treePath, path));
8918}
8919/**
8920 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8921 * the window, but may now be in the window
8922 */
8923function writeTreeRefCalcIndexedSlice(writeTreeRef, completeServerData, startPost, count, reverse, index) {
8924 return writeTreeCalcIndexedSlice(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerData, startPost, count, reverse, index);
8925}
8926/**
8927 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8928 * complete child for this ChildKey.
8929 */
8930function writeTreeRefCalcCompleteChild(writeTreeRef, childKey, existingServerCache) {
8931 return writeTreeCalcCompleteChild(writeTreeRef.writeTree, writeTreeRef.treePath, childKey, existingServerCache);
8932}
8933/**
8934 * Return a WriteTreeRef for a child.
8935 */
8936function writeTreeRefChild(writeTreeRef, childName) {
8937 return newWriteTreeRef(pathChild(writeTreeRef.treePath, childName), writeTreeRef.writeTree);
8938}
8939function newWriteTreeRef(path, writeTree) {
8940 return {
8941 treePath: path,
8942 writeTree: writeTree
8943 };
8944}
8945
8946/**
8947 * @license
8948 * Copyright 2017 Google LLC
8949 *
8950 * Licensed under the Apache License, Version 2.0 (the "License");
8951 * you may not use this file except in compliance with the License.
8952 * You may obtain a copy of the License at
8953 *
8954 * http://www.apache.org/licenses/LICENSE-2.0
8955 *
8956 * Unless required by applicable law or agreed to in writing, software
8957 * distributed under the License is distributed on an "AS IS" BASIS,
8958 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8959 * See the License for the specific language governing permissions and
8960 * limitations under the License.
8961 */
8962var ChildChangeAccumulator = /** @class */ (function () {
8963 function ChildChangeAccumulator() {
8964 this.changeMap = new Map();
8965 }
8966 ChildChangeAccumulator.prototype.trackChildChange = function (change) {
8967 var type = change.type;
8968 var childKey = change.childName;
8969 util.assert(type === "child_added" /* CHILD_ADDED */ ||
8970 type === "child_changed" /* CHILD_CHANGED */ ||
8971 type === "child_removed" /* CHILD_REMOVED */, 'Only child changes supported for tracking');
8972 util.assert(childKey !== '.priority', 'Only non-priority child changes can be tracked.');
8973 var oldChange = this.changeMap.get(childKey);
8974 if (oldChange) {
8975 var oldType = oldChange.type;
8976 if (type === "child_added" /* CHILD_ADDED */ &&
8977 oldType === "child_removed" /* CHILD_REMOVED */) {
8978 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.snapshotNode));
8979 }
8980 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8981 oldType === "child_added" /* CHILD_ADDED */) {
8982 this.changeMap.delete(childKey);
8983 }
8984 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8985 oldType === "child_changed" /* CHILD_CHANGED */) {
8986 this.changeMap.set(childKey, changeChildRemoved(childKey, oldChange.oldSnap));
8987 }
8988 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8989 oldType === "child_added" /* CHILD_ADDED */) {
8990 this.changeMap.set(childKey, changeChildAdded(childKey, change.snapshotNode));
8991 }
8992 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8993 oldType === "child_changed" /* CHILD_CHANGED */) {
8994 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.oldSnap));
8995 }
8996 else {
8997 throw util.assertionError('Illegal combination of changes: ' +
8998 change +
8999 ' occurred after ' +
9000 oldChange);
9001 }
9002 }
9003 else {
9004 this.changeMap.set(childKey, change);
9005 }
9006 };
9007 ChildChangeAccumulator.prototype.getChanges = function () {
9008 return Array.from(this.changeMap.values());
9009 };
9010 return ChildChangeAccumulator;
9011}());
9012
9013/**
9014 * @license
9015 * Copyright 2017 Google LLC
9016 *
9017 * Licensed under the Apache License, Version 2.0 (the "License");
9018 * you may not use this file except in compliance with the License.
9019 * You may obtain a copy of the License at
9020 *
9021 * http://www.apache.org/licenses/LICENSE-2.0
9022 *
9023 * Unless required by applicable law or agreed to in writing, software
9024 * distributed under the License is distributed on an "AS IS" BASIS,
9025 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9026 * See the License for the specific language governing permissions and
9027 * limitations under the License.
9028 */
9029/**
9030 * An implementation of CompleteChildSource that never returns any additional children
9031 */
9032// eslint-disable-next-line @typescript-eslint/naming-convention
9033var NoCompleteChildSource_ = /** @class */ (function () {
9034 function NoCompleteChildSource_() {
9035 }
9036 NoCompleteChildSource_.prototype.getCompleteChild = function (childKey) {
9037 return null;
9038 };
9039 NoCompleteChildSource_.prototype.getChildAfterChild = function (index, child, reverse) {
9040 return null;
9041 };
9042 return NoCompleteChildSource_;
9043}());
9044/**
9045 * Singleton instance.
9046 */
9047var NO_COMPLETE_CHILD_SOURCE = new NoCompleteChildSource_();
9048/**
9049 * An implementation of CompleteChildSource that uses a WriteTree in addition to any other server data or
9050 * old event caches available to calculate complete children.
9051 */
9052var WriteTreeCompleteChildSource = /** @class */ (function () {
9053 function WriteTreeCompleteChildSource(writes_, viewCache_, optCompleteServerCache_) {
9054 if (optCompleteServerCache_ === void 0) { optCompleteServerCache_ = null; }
9055 this.writes_ = writes_;
9056 this.viewCache_ = viewCache_;
9057 this.optCompleteServerCache_ = optCompleteServerCache_;
9058 }
9059 WriteTreeCompleteChildSource.prototype.getCompleteChild = function (childKey) {
9060 var node = this.viewCache_.eventCache;
9061 if (node.isCompleteForChild(childKey)) {
9062 return node.getNode().getImmediateChild(childKey);
9063 }
9064 else {
9065 var serverNode = this.optCompleteServerCache_ != null
9066 ? new CacheNode(this.optCompleteServerCache_, true, false)
9067 : this.viewCache_.serverCache;
9068 return writeTreeRefCalcCompleteChild(this.writes_, childKey, serverNode);
9069 }
9070 };
9071 WriteTreeCompleteChildSource.prototype.getChildAfterChild = function (index, child, reverse) {
9072 var completeServerData = this.optCompleteServerCache_ != null
9073 ? this.optCompleteServerCache_
9074 : viewCacheGetCompleteServerSnap(this.viewCache_);
9075 var nodes = writeTreeRefCalcIndexedSlice(this.writes_, completeServerData, child, 1, reverse, index);
9076 if (nodes.length === 0) {
9077 return null;
9078 }
9079 else {
9080 return nodes[0];
9081 }
9082 };
9083 return WriteTreeCompleteChildSource;
9084}());
9085
9086/**
9087 * @license
9088 * Copyright 2017 Google LLC
9089 *
9090 * Licensed under the Apache License, Version 2.0 (the "License");
9091 * you may not use this file except in compliance with the License.
9092 * You may obtain a copy of the License at
9093 *
9094 * http://www.apache.org/licenses/LICENSE-2.0
9095 *
9096 * Unless required by applicable law or agreed to in writing, software
9097 * distributed under the License is distributed on an "AS IS" BASIS,
9098 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9099 * See the License for the specific language governing permissions and
9100 * limitations under the License.
9101 */
9102function newViewProcessor(filter) {
9103 return { filter: filter };
9104}
9105function viewProcessorAssertIndexed(viewProcessor, viewCache) {
9106 util.assert(viewCache.eventCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Event snap not indexed');
9107 util.assert(viewCache.serverCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Server snap not indexed');
9108}
9109function viewProcessorApplyOperation(viewProcessor, oldViewCache, operation, writesCache, completeCache) {
9110 var accumulator = new ChildChangeAccumulator();
9111 var newViewCache, filterServerNode;
9112 if (operation.type === OperationType.OVERWRITE) {
9113 var overwrite = operation;
9114 if (overwrite.source.fromUser) {
9115 newViewCache = viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, accumulator);
9116 }
9117 else {
9118 util.assert(overwrite.source.fromServer, 'Unknown source.');
9119 // We filter the node if it's a tagged update or the node has been previously filtered and the
9120 // update is not at the root in which case it is ok (and necessary) to mark the node unfiltered
9121 // again
9122 filterServerNode =
9123 overwrite.source.tagged ||
9124 (oldViewCache.serverCache.isFiltered() && !pathIsEmpty(overwrite.path));
9125 newViewCache = viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, filterServerNode, accumulator);
9126 }
9127 }
9128 else if (operation.type === OperationType.MERGE) {
9129 var merge = operation;
9130 if (merge.source.fromUser) {
9131 newViewCache = viewProcessorApplyUserMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, accumulator);
9132 }
9133 else {
9134 util.assert(merge.source.fromServer, 'Unknown source.');
9135 // We filter the node if it's a tagged update or the node has been previously filtered
9136 filterServerNode =
9137 merge.source.tagged || oldViewCache.serverCache.isFiltered();
9138 newViewCache = viewProcessorApplyServerMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, filterServerNode, accumulator);
9139 }
9140 }
9141 else if (operation.type === OperationType.ACK_USER_WRITE) {
9142 var ackUserWrite = operation;
9143 if (!ackUserWrite.revert) {
9144 newViewCache = viewProcessorAckUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, ackUserWrite.affectedTree, writesCache, completeCache, accumulator);
9145 }
9146 else {
9147 newViewCache = viewProcessorRevertUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, writesCache, completeCache, accumulator);
9148 }
9149 }
9150 else if (operation.type === OperationType.LISTEN_COMPLETE) {
9151 newViewCache = viewProcessorListenComplete(viewProcessor, oldViewCache, operation.path, writesCache, accumulator);
9152 }
9153 else {
9154 throw util.assertionError('Unknown operation type: ' + operation.type);
9155 }
9156 var changes = accumulator.getChanges();
9157 viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, changes);
9158 return { viewCache: newViewCache, changes: changes };
9159}
9160function viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, accumulator) {
9161 var eventSnap = newViewCache.eventCache;
9162 if (eventSnap.isFullyInitialized()) {
9163 var isLeafOrEmpty = eventSnap.getNode().isLeafNode() || eventSnap.getNode().isEmpty();
9164 var oldCompleteSnap = viewCacheGetCompleteEventSnap(oldViewCache);
9165 if (accumulator.length > 0 ||
9166 !oldViewCache.eventCache.isFullyInitialized() ||
9167 (isLeafOrEmpty && !eventSnap.getNode().equals(oldCompleteSnap)) ||
9168 !eventSnap.getNode().getPriority().equals(oldCompleteSnap.getPriority())) {
9169 accumulator.push(changeValue(viewCacheGetCompleteEventSnap(newViewCache)));
9170 }
9171 }
9172}
9173function viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, viewCache, changePath, writesCache, source, accumulator) {
9174 var oldEventSnap = viewCache.eventCache;
9175 if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
9176 // we have a shadowing write, ignore changes
9177 return viewCache;
9178 }
9179 else {
9180 var newEventCache = void 0, serverNode = void 0;
9181 if (pathIsEmpty(changePath)) {
9182 // TODO: figure out how this plays with "sliding ack windows"
9183 util.assert(viewCache.serverCache.isFullyInitialized(), 'If change path is empty, we must have complete server data');
9184 if (viewCache.serverCache.isFiltered()) {
9185 // We need to special case this, because we need to only apply writes to complete children, or
9186 // we might end up raising events for incomplete children. If the server data is filtered deep
9187 // writes cannot be guaranteed to be complete
9188 var serverCache = viewCacheGetCompleteServerSnap(viewCache);
9189 var completeChildren = serverCache instanceof ChildrenNode
9190 ? serverCache
9191 : ChildrenNode.EMPTY_NODE;
9192 var completeEventChildren = writeTreeRefCalcCompleteEventChildren(writesCache, completeChildren);
9193 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeEventChildren, accumulator);
9194 }
9195 else {
9196 var completeNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9197 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeNode, accumulator);
9198 }
9199 }
9200 else {
9201 var childKey = pathGetFront(changePath);
9202 if (childKey === '.priority') {
9203 util.assert(pathGetLength(changePath) === 1, "Can't have a priority with additional path components");
9204 var oldEventNode = oldEventSnap.getNode();
9205 serverNode = viewCache.serverCache.getNode();
9206 // we might have overwrites for this priority
9207 var updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventNode, serverNode);
9208 if (updatedPriority != null) {
9209 newEventCache = viewProcessor.filter.updatePriority(oldEventNode, updatedPriority);
9210 }
9211 else {
9212 // priority didn't change, keep old node
9213 newEventCache = oldEventSnap.getNode();
9214 }
9215 }
9216 else {
9217 var childChangePath = pathPopFront(changePath);
9218 // update child
9219 var newEventChild = void 0;
9220 if (oldEventSnap.isCompleteForChild(childKey)) {
9221 serverNode = viewCache.serverCache.getNode();
9222 var eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventSnap.getNode(), serverNode);
9223 if (eventChildUpdate != null) {
9224 newEventChild = oldEventSnap
9225 .getNode()
9226 .getImmediateChild(childKey)
9227 .updateChild(childChangePath, eventChildUpdate);
9228 }
9229 else {
9230 // Nothing changed, just keep the old child
9231 newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
9232 }
9233 }
9234 else {
9235 newEventChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9236 }
9237 if (newEventChild != null) {
9238 newEventCache = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newEventChild, childChangePath, source, accumulator);
9239 }
9240 else {
9241 // no complete child available or no change
9242 newEventCache = oldEventSnap.getNode();
9243 }
9244 }
9245 }
9246 return viewCacheUpdateEventSnap(viewCache, newEventCache, oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath), viewProcessor.filter.filtersNodes());
9247 }
9248}
9249function viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, filterServerNode, accumulator) {
9250 var oldServerSnap = oldViewCache.serverCache;
9251 var newServerCache;
9252 var serverFilter = filterServerNode
9253 ? viewProcessor.filter
9254 : viewProcessor.filter.getIndexedFilter();
9255 if (pathIsEmpty(changePath)) {
9256 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), changedSnap, null);
9257 }
9258 else if (serverFilter.filtersNodes() && !oldServerSnap.isFiltered()) {
9259 // we want to filter the server node, but we didn't filter the server node yet, so simulate a full update
9260 var newServerNode = oldServerSnap
9261 .getNode()
9262 .updateChild(changePath, changedSnap);
9263 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), newServerNode, null);
9264 }
9265 else {
9266 var childKey = pathGetFront(changePath);
9267 if (!oldServerSnap.isCompleteForPath(changePath) &&
9268 pathGetLength(changePath) > 1) {
9269 // We don't update incomplete nodes with updates intended for other listeners
9270 return oldViewCache;
9271 }
9272 var childChangePath = pathPopFront(changePath);
9273 var childNode = oldServerSnap.getNode().getImmediateChild(childKey);
9274 var newChildNode = childNode.updateChild(childChangePath, changedSnap);
9275 if (childKey === '.priority') {
9276 newServerCache = serverFilter.updatePriority(oldServerSnap.getNode(), newChildNode);
9277 }
9278 else {
9279 newServerCache = serverFilter.updateChild(oldServerSnap.getNode(), childKey, newChildNode, childChangePath, NO_COMPLETE_CHILD_SOURCE, null);
9280 }
9281 }
9282 var newViewCache = viewCacheUpdateServerSnap(oldViewCache, newServerCache, oldServerSnap.isFullyInitialized() || pathIsEmpty(changePath), serverFilter.filtersNodes());
9283 var source = new WriteTreeCompleteChildSource(writesCache, newViewCache, completeCache);
9284 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, changePath, writesCache, source, accumulator);
9285}
9286function viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, accumulator) {
9287 var oldEventSnap = oldViewCache.eventCache;
9288 var newViewCache, newEventCache;
9289 var source = new WriteTreeCompleteChildSource(writesCache, oldViewCache, completeCache);
9290 if (pathIsEmpty(changePath)) {
9291 newEventCache = viewProcessor.filter.updateFullNode(oldViewCache.eventCache.getNode(), changedSnap, accumulator);
9292 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, true, viewProcessor.filter.filtersNodes());
9293 }
9294 else {
9295 var childKey = pathGetFront(changePath);
9296 if (childKey === '.priority') {
9297 newEventCache = viewProcessor.filter.updatePriority(oldViewCache.eventCache.getNode(), changedSnap);
9298 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, oldEventSnap.isFullyInitialized(), oldEventSnap.isFiltered());
9299 }
9300 else {
9301 var childChangePath = pathPopFront(changePath);
9302 var oldChild = oldEventSnap.getNode().getImmediateChild(childKey);
9303 var newChild = void 0;
9304 if (pathIsEmpty(childChangePath)) {
9305 // Child overwrite, we can replace the child
9306 newChild = changedSnap;
9307 }
9308 else {
9309 var childNode = source.getCompleteChild(childKey);
9310 if (childNode != null) {
9311 if (pathGetBack(childChangePath) === '.priority' &&
9312 childNode.getChild(pathParent(childChangePath)).isEmpty()) {
9313 // This is a priority update on an empty node. If this node exists on the server, the
9314 // server will send down the priority in the update, so ignore for now
9315 newChild = childNode;
9316 }
9317 else {
9318 newChild = childNode.updateChild(childChangePath, changedSnap);
9319 }
9320 }
9321 else {
9322 // There is no complete child node available
9323 newChild = ChildrenNode.EMPTY_NODE;
9324 }
9325 }
9326 if (!oldChild.equals(newChild)) {
9327 var newEventSnap = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newChild, childChangePath, source, accumulator);
9328 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventSnap, oldEventSnap.isFullyInitialized(), viewProcessor.filter.filtersNodes());
9329 }
9330 else {
9331 newViewCache = oldViewCache;
9332 }
9333 }
9334 }
9335 return newViewCache;
9336}
9337function viewProcessorCacheHasChild(viewCache, childKey) {
9338 return viewCache.eventCache.isCompleteForChild(childKey);
9339}
9340function viewProcessorApplyUserMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, accumulator) {
9341 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9342 // window leaving room for new items. It's important we process these changes first, so we
9343 // iterate the changes twice, first processing any that affect items currently in view.
9344 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9345 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9346 // not the other.
9347 var curViewCache = viewCache;
9348 changedChildren.foreach(function (relativePath, childNode) {
9349 var writePath = pathChild(path, relativePath);
9350 if (viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9351 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9352 }
9353 });
9354 changedChildren.foreach(function (relativePath, childNode) {
9355 var writePath = pathChild(path, relativePath);
9356 if (!viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9357 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9358 }
9359 });
9360 return curViewCache;
9361}
9362function viewProcessorApplyMerge(viewProcessor, node, merge) {
9363 merge.foreach(function (relativePath, childNode) {
9364 node = node.updateChild(relativePath, childNode);
9365 });
9366 return node;
9367}
9368function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, filterServerNode, accumulator) {
9369 // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
9370 // wait for the complete data update coming soon.
9371 if (viewCache.serverCache.getNode().isEmpty() &&
9372 !viewCache.serverCache.isFullyInitialized()) {
9373 return viewCache;
9374 }
9375 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9376 // window leaving room for new items. It's important we process these changes first, so we
9377 // iterate the changes twice, first processing any that affect items currently in view.
9378 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9379 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9380 // not the other.
9381 var curViewCache = viewCache;
9382 var viewMergeTree;
9383 if (pathIsEmpty(path)) {
9384 viewMergeTree = changedChildren;
9385 }
9386 else {
9387 viewMergeTree = new ImmutableTree(null).setTree(path, changedChildren);
9388 }
9389 var serverNode = viewCache.serverCache.getNode();
9390 viewMergeTree.children.inorderTraversal(function (childKey, childTree) {
9391 if (serverNode.hasChild(childKey)) {
9392 var serverChild = viewCache.serverCache
9393 .getNode()
9394 .getImmediateChild(childKey);
9395 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childTree);
9396 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9397 }
9398 });
9399 viewMergeTree.children.inorderTraversal(function (childKey, childMergeTree) {
9400 var isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9401 childMergeTree.value === undefined;
9402 if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9403 var serverChild = viewCache.serverCache
9404 .getNode()
9405 .getImmediateChild(childKey);
9406 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childMergeTree);
9407 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9408 }
9409 });
9410 return curViewCache;
9411}
9412function viewProcessorAckUserWrite(viewProcessor, viewCache, ackPath, affectedTree, writesCache, completeCache, accumulator) {
9413 if (writeTreeRefShadowingWrite(writesCache, ackPath) != null) {
9414 return viewCache;
9415 }
9416 // Only filter server node if it is currently filtered
9417 var filterServerNode = viewCache.serverCache.isFiltered();
9418 // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
9419 // now that it won't be shadowed.
9420 var serverCache = viewCache.serverCache;
9421 if (affectedTree.value != null) {
9422 // This is an overwrite.
9423 if ((pathIsEmpty(ackPath) && serverCache.isFullyInitialized()) ||
9424 serverCache.isCompleteForPath(ackPath)) {
9425 return viewProcessorApplyServerOverwrite(viewProcessor, viewCache, ackPath, serverCache.getNode().getChild(ackPath), writesCache, completeCache, filterServerNode, accumulator);
9426 }
9427 else if (pathIsEmpty(ackPath)) {
9428 // This is a goofy edge case where we are acking data at this location but don't have full data. We
9429 // should just re-apply whatever we have in our cache as a merge.
9430 var changedChildren_1 = new ImmutableTree(null);
9431 serverCache.getNode().forEachChild(KEY_INDEX, function (name, node) {
9432 changedChildren_1 = changedChildren_1.set(new Path(name), node);
9433 });
9434 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_1, writesCache, completeCache, filterServerNode, accumulator);
9435 }
9436 else {
9437 return viewCache;
9438 }
9439 }
9440 else {
9441 // This is a merge.
9442 var changedChildren_2 = new ImmutableTree(null);
9443 affectedTree.foreach(function (mergePath, value) {
9444 var serverCachePath = pathChild(ackPath, mergePath);
9445 if (serverCache.isCompleteForPath(serverCachePath)) {
9446 changedChildren_2 = changedChildren_2.set(mergePath, serverCache.getNode().getChild(serverCachePath));
9447 }
9448 });
9449 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_2, writesCache, completeCache, filterServerNode, accumulator);
9450 }
9451}
9452function viewProcessorListenComplete(viewProcessor, viewCache, path, writesCache, accumulator) {
9453 var oldServerNode = viewCache.serverCache;
9454 var newViewCache = viewCacheUpdateServerSnap(viewCache, oldServerNode.getNode(), oldServerNode.isFullyInitialized() || pathIsEmpty(path), oldServerNode.isFiltered());
9455 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, path, writesCache, NO_COMPLETE_CHILD_SOURCE, accumulator);
9456}
9457function viewProcessorRevertUserWrite(viewProcessor, viewCache, path, writesCache, completeServerCache, accumulator) {
9458 var complete;
9459 if (writeTreeRefShadowingWrite(writesCache, path) != null) {
9460 return viewCache;
9461 }
9462 else {
9463 var source = new WriteTreeCompleteChildSource(writesCache, viewCache, completeServerCache);
9464 var oldEventCache = viewCache.eventCache.getNode();
9465 var newEventCache = void 0;
9466 if (pathIsEmpty(path) || pathGetFront(path) === '.priority') {
9467 var newNode = void 0;
9468 if (viewCache.serverCache.isFullyInitialized()) {
9469 newNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9470 }
9471 else {
9472 var serverChildren = viewCache.serverCache.getNode();
9473 util.assert(serverChildren instanceof ChildrenNode, 'serverChildren would be complete if leaf node');
9474 newNode = writeTreeRefCalcCompleteEventChildren(writesCache, serverChildren);
9475 }
9476 newNode = newNode;
9477 newEventCache = viewProcessor.filter.updateFullNode(oldEventCache, newNode, accumulator);
9478 }
9479 else {
9480 var childKey = pathGetFront(path);
9481 var newChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9482 if (newChild == null &&
9483 viewCache.serverCache.isCompleteForChild(childKey)) {
9484 newChild = oldEventCache.getImmediateChild(childKey);
9485 }
9486 if (newChild != null) {
9487 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, newChild, pathPopFront(path), source, accumulator);
9488 }
9489 else if (viewCache.eventCache.getNode().hasChild(childKey)) {
9490 // No complete child available, delete the existing one, if any
9491 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, ChildrenNode.EMPTY_NODE, pathPopFront(path), source, accumulator);
9492 }
9493 else {
9494 newEventCache = oldEventCache;
9495 }
9496 if (newEventCache.isEmpty() &&
9497 viewCache.serverCache.isFullyInitialized()) {
9498 // We might have reverted all child writes. Maybe the old event was a leaf node
9499 complete = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9500 if (complete.isLeafNode()) {
9501 newEventCache = viewProcessor.filter.updateFullNode(newEventCache, complete, accumulator);
9502 }
9503 }
9504 }
9505 complete =
9506 viewCache.serverCache.isFullyInitialized() ||
9507 writeTreeRefShadowingWrite(writesCache, newEmptyPath()) != null;
9508 return viewCacheUpdateEventSnap(viewCache, newEventCache, complete, viewProcessor.filter.filtersNodes());
9509 }
9510}
9511
9512/**
9513 * @license
9514 * Copyright 2017 Google LLC
9515 *
9516 * Licensed under the Apache License, Version 2.0 (the "License");
9517 * you may not use this file except in compliance with the License.
9518 * You may obtain a copy of the License at
9519 *
9520 * http://www.apache.org/licenses/LICENSE-2.0
9521 *
9522 * Unless required by applicable law or agreed to in writing, software
9523 * distributed under the License is distributed on an "AS IS" BASIS,
9524 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9525 * See the License for the specific language governing permissions and
9526 * limitations under the License.
9527 */
9528/**
9529 * A view represents a specific location and query that has 1 or more event registrations.
9530 *
9531 * It does several things:
9532 * - Maintains the list of event registrations for this location/query.
9533 * - Maintains a cache of the data visible for this location/query.
9534 * - Applies new operations (via applyOperation), updates the cache, and based on the event
9535 * registrations returns the set of events to be raised.
9536 */
9537var View = /** @class */ (function () {
9538 function View(query_, initialViewCache) {
9539 this.query_ = query_;
9540 this.eventRegistrations_ = [];
9541 var params = this.query_._queryParams;
9542 var indexFilter = new IndexedFilter(params.getIndex());
9543 var filter = queryParamsGetNodeFilter(params);
9544 this.processor_ = newViewProcessor(filter);
9545 var initialServerCache = initialViewCache.serverCache;
9546 var initialEventCache = initialViewCache.eventCache;
9547 // Don't filter server node with other filter than index, wait for tagged listen
9548 var serverSnap = indexFilter.updateFullNode(ChildrenNode.EMPTY_NODE, initialServerCache.getNode(), null);
9549 var eventSnap = filter.updateFullNode(ChildrenNode.EMPTY_NODE, initialEventCache.getNode(), null);
9550 var newServerCache = new CacheNode(serverSnap, initialServerCache.isFullyInitialized(), indexFilter.filtersNodes());
9551 var newEventCache = new CacheNode(eventSnap, initialEventCache.isFullyInitialized(), filter.filtersNodes());
9552 this.viewCache_ = newViewCache(newEventCache, newServerCache);
9553 this.eventGenerator_ = new EventGenerator(this.query_);
9554 }
9555 Object.defineProperty(View.prototype, "query", {
9556 get: function () {
9557 return this.query_;
9558 },
9559 enumerable: false,
9560 configurable: true
9561 });
9562 return View;
9563}());
9564function viewGetServerCache(view) {
9565 return view.viewCache_.serverCache.getNode();
9566}
9567function viewGetCompleteNode(view) {
9568 return viewCacheGetCompleteEventSnap(view.viewCache_);
9569}
9570function viewGetCompleteServerCache(view, path) {
9571 var cache = viewCacheGetCompleteServerSnap(view.viewCache_);
9572 if (cache) {
9573 // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
9574 // we need to see if it contains the child we're interested in.
9575 if (view.query._queryParams.loadsAllData() ||
9576 (!pathIsEmpty(path) &&
9577 !cache.getImmediateChild(pathGetFront(path)).isEmpty())) {
9578 return cache.getChild(path);
9579 }
9580 }
9581 return null;
9582}
9583function viewIsEmpty(view) {
9584 return view.eventRegistrations_.length === 0;
9585}
9586function viewAddEventRegistration(view, eventRegistration) {
9587 view.eventRegistrations_.push(eventRegistration);
9588}
9589/**
9590 * @param eventRegistration - If null, remove all callbacks.
9591 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9592 * @returns Cancel events, if cancelError was provided.
9593 */
9594function viewRemoveEventRegistration(view, eventRegistration, cancelError) {
9595 var cancelEvents = [];
9596 if (cancelError) {
9597 util.assert(eventRegistration == null, 'A cancel should cancel all event registrations.');
9598 var path_1 = view.query._path;
9599 view.eventRegistrations_.forEach(function (registration) {
9600 var maybeEvent = registration.createCancelEvent(cancelError, path_1);
9601 if (maybeEvent) {
9602 cancelEvents.push(maybeEvent);
9603 }
9604 });
9605 }
9606 if (eventRegistration) {
9607 var remaining = [];
9608 for (var i = 0; i < view.eventRegistrations_.length; ++i) {
9609 var existing = view.eventRegistrations_[i];
9610 if (!existing.matches(eventRegistration)) {
9611 remaining.push(existing);
9612 }
9613 else if (eventRegistration.hasAnyCallback()) {
9614 // We're removing just this one
9615 remaining = remaining.concat(view.eventRegistrations_.slice(i + 1));
9616 break;
9617 }
9618 }
9619 view.eventRegistrations_ = remaining;
9620 }
9621 else {
9622 view.eventRegistrations_ = [];
9623 }
9624 return cancelEvents;
9625}
9626/**
9627 * Applies the given Operation, updates our cache, and returns the appropriate events.
9628 */
9629function viewApplyOperation(view, operation, writesCache, completeServerCache) {
9630 if (operation.type === OperationType.MERGE &&
9631 operation.source.queryId !== null) {
9632 util.assert(viewCacheGetCompleteServerSnap(view.viewCache_), 'We should always have a full cache before handling merges');
9633 util.assert(viewCacheGetCompleteEventSnap(view.viewCache_), 'Missing event cache, even though we have a server cache');
9634 }
9635 var oldViewCache = view.viewCache_;
9636 var result = viewProcessorApplyOperation(view.processor_, oldViewCache, operation, writesCache, completeServerCache);
9637 viewProcessorAssertIndexed(view.processor_, result.viewCache);
9638 util.assert(result.viewCache.serverCache.isFullyInitialized() ||
9639 !oldViewCache.serverCache.isFullyInitialized(), 'Once a server snap is complete, it should never go back');
9640 view.viewCache_ = result.viewCache;
9641 return viewGenerateEventsForChanges_(view, result.changes, result.viewCache.eventCache.getNode(), null);
9642}
9643function viewGetInitialEvents(view, registration) {
9644 var eventSnap = view.viewCache_.eventCache;
9645 var initialChanges = [];
9646 if (!eventSnap.getNode().isLeafNode()) {
9647 var eventNode = eventSnap.getNode();
9648 eventNode.forEachChild(PRIORITY_INDEX, function (key, childNode) {
9649 initialChanges.push(changeChildAdded(key, childNode));
9650 });
9651 }
9652 if (eventSnap.isFullyInitialized()) {
9653 initialChanges.push(changeValue(eventSnap.getNode()));
9654 }
9655 return viewGenerateEventsForChanges_(view, initialChanges, eventSnap.getNode(), registration);
9656}
9657function viewGenerateEventsForChanges_(view, changes, eventCache, eventRegistration) {
9658 var registrations = eventRegistration
9659 ? [eventRegistration]
9660 : view.eventRegistrations_;
9661 return eventGeneratorGenerateEventsForChanges(view.eventGenerator_, changes, eventCache, registrations);
9662}
9663
9664/**
9665 * @license
9666 * Copyright 2017 Google LLC
9667 *
9668 * Licensed under the Apache License, Version 2.0 (the "License");
9669 * you may not use this file except in compliance with the License.
9670 * You may obtain a copy of the License at
9671 *
9672 * http://www.apache.org/licenses/LICENSE-2.0
9673 *
9674 * Unless required by applicable law or agreed to in writing, software
9675 * distributed under the License is distributed on an "AS IS" BASIS,
9676 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9677 * See the License for the specific language governing permissions and
9678 * limitations under the License.
9679 */
9680var referenceConstructor;
9681/**
9682 * SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
9683 * maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
9684 * and user writes (set, transaction, update).
9685 *
9686 * It's responsible for:
9687 * - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
9688 * - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
9689 * applyUserOverwrite, etc.)
9690 */
9691var SyncPoint = /** @class */ (function () {
9692 function SyncPoint() {
9693 /**
9694 * The Views being tracked at this location in the tree, stored as a map where the key is a
9695 * queryId and the value is the View for that query.
9696 *
9697 * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
9698 */
9699 this.views = new Map();
9700 }
9701 return SyncPoint;
9702}());
9703function syncPointSetReferenceConstructor(val) {
9704 util.assert(!referenceConstructor, '__referenceConstructor has already been defined');
9705 referenceConstructor = val;
9706}
9707function syncPointGetReferenceConstructor() {
9708 util.assert(referenceConstructor, 'Reference.ts has not been loaded');
9709 return referenceConstructor;
9710}
9711function syncPointIsEmpty(syncPoint) {
9712 return syncPoint.views.size === 0;
9713}
9714function syncPointApplyOperation(syncPoint, operation, writesCache, optCompleteServerCache) {
9715 var e_1, _a;
9716 var queryId = operation.source.queryId;
9717 if (queryId !== null) {
9718 var view = syncPoint.views.get(queryId);
9719 util.assert(view != null, 'SyncTree gave us an op for an invalid query.');
9720 return viewApplyOperation(view, operation, writesCache, optCompleteServerCache);
9721 }
9722 else {
9723 var events = [];
9724 try {
9725 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9726 var view = _c.value;
9727 events = events.concat(viewApplyOperation(view, operation, writesCache, optCompleteServerCache));
9728 }
9729 }
9730 catch (e_1_1) { e_1 = { error: e_1_1 }; }
9731 finally {
9732 try {
9733 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9734 }
9735 finally { if (e_1) throw e_1.error; }
9736 }
9737 return events;
9738 }
9739}
9740/**
9741 * Get a view for the specified query.
9742 *
9743 * @param query - The query to return a view for
9744 * @param writesCache
9745 * @param serverCache
9746 * @param serverCacheComplete
9747 * @returns Events to raise.
9748 */
9749function syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete) {
9750 var queryId = query._queryIdentifier;
9751 var view = syncPoint.views.get(queryId);
9752 if (!view) {
9753 // TODO: make writesCache take flag for complete server node
9754 var eventCache = writeTreeRefCalcCompleteEventCache(writesCache, serverCacheComplete ? serverCache : null);
9755 var eventCacheComplete = false;
9756 if (eventCache) {
9757 eventCacheComplete = true;
9758 }
9759 else if (serverCache instanceof ChildrenNode) {
9760 eventCache = writeTreeRefCalcCompleteEventChildren(writesCache, serverCache);
9761 eventCacheComplete = false;
9762 }
9763 else {
9764 eventCache = ChildrenNode.EMPTY_NODE;
9765 eventCacheComplete = false;
9766 }
9767 var viewCache = newViewCache(new CacheNode(eventCache, eventCacheComplete, false), new CacheNode(serverCache, serverCacheComplete, false));
9768 return new View(query, viewCache);
9769 }
9770 return view;
9771}
9772/**
9773 * Add an event callback for the specified query.
9774 *
9775 * @param query
9776 * @param eventRegistration
9777 * @param writesCache
9778 * @param serverCache - Complete server cache, if we have it.
9779 * @param serverCacheComplete
9780 * @returns Events to raise.
9781 */
9782function syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete) {
9783 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9784 if (!syncPoint.views.has(query._queryIdentifier)) {
9785 syncPoint.views.set(query._queryIdentifier, view);
9786 }
9787 // This is guaranteed to exist now, we just created anything that was missing
9788 viewAddEventRegistration(view, eventRegistration);
9789 return viewGetInitialEvents(view, eventRegistration);
9790}
9791/**
9792 * Remove event callback(s). Return cancelEvents if a cancelError is specified.
9793 *
9794 * If query is the default query, we'll check all views for the specified eventRegistration.
9795 * If eventRegistration is null, we'll remove all callbacks for the specified view(s).
9796 *
9797 * @param eventRegistration - If null, remove all callbacks.
9798 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9799 * @returns removed queries and any cancel events
9800 */
9801function syncPointRemoveEventRegistration(syncPoint, query, eventRegistration, cancelError) {
9802 var e_2, _a;
9803 var queryId = query._queryIdentifier;
9804 var removed = [];
9805 var cancelEvents = [];
9806 var hadCompleteView = syncPointHasCompleteView(syncPoint);
9807 if (queryId === 'default') {
9808 try {
9809 // When you do ref.off(...), we search all views for the registration to remove.
9810 for (var _b = tslib.__values(syncPoint.views.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
9811 var _d = tslib.__read(_c.value, 2), viewQueryId = _d[0], view = _d[1];
9812 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9813 if (viewIsEmpty(view)) {
9814 syncPoint.views.delete(viewQueryId);
9815 // We'll deal with complete views later.
9816 if (!view.query._queryParams.loadsAllData()) {
9817 removed.push(view.query);
9818 }
9819 }
9820 }
9821 }
9822 catch (e_2_1) { e_2 = { error: e_2_1 }; }
9823 finally {
9824 try {
9825 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9826 }
9827 finally { if (e_2) throw e_2.error; }
9828 }
9829 }
9830 else {
9831 // remove the callback from the specific view.
9832 var view = syncPoint.views.get(queryId);
9833 if (view) {
9834 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9835 if (viewIsEmpty(view)) {
9836 syncPoint.views.delete(queryId);
9837 // We'll deal with complete views later.
9838 if (!view.query._queryParams.loadsAllData()) {
9839 removed.push(view.query);
9840 }
9841 }
9842 }
9843 }
9844 if (hadCompleteView && !syncPointHasCompleteView(syncPoint)) {
9845 // We removed our last complete view.
9846 removed.push(new (syncPointGetReferenceConstructor())(query._repo, query._path));
9847 }
9848 return { removed: removed, events: cancelEvents };
9849}
9850function syncPointGetQueryViews(syncPoint) {
9851 var e_3, _a;
9852 var result = [];
9853 try {
9854 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9855 var view = _c.value;
9856 if (!view.query._queryParams.loadsAllData()) {
9857 result.push(view);
9858 }
9859 }
9860 }
9861 catch (e_3_1) { e_3 = { error: e_3_1 }; }
9862 finally {
9863 try {
9864 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9865 }
9866 finally { if (e_3) throw e_3.error; }
9867 }
9868 return result;
9869}
9870/**
9871 * @param path - The path to the desired complete snapshot
9872 * @returns A complete cache, if it exists
9873 */
9874function syncPointGetCompleteServerCache(syncPoint, path) {
9875 var e_4, _a;
9876 var serverCache = null;
9877 try {
9878 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9879 var view = _c.value;
9880 serverCache = serverCache || viewGetCompleteServerCache(view, path);
9881 }
9882 }
9883 catch (e_4_1) { e_4 = { error: e_4_1 }; }
9884 finally {
9885 try {
9886 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9887 }
9888 finally { if (e_4) throw e_4.error; }
9889 }
9890 return serverCache;
9891}
9892function syncPointViewForQuery(syncPoint, query) {
9893 var params = query._queryParams;
9894 if (params.loadsAllData()) {
9895 return syncPointGetCompleteView(syncPoint);
9896 }
9897 else {
9898 var queryId = query._queryIdentifier;
9899 return syncPoint.views.get(queryId);
9900 }
9901}
9902function syncPointViewExistsForQuery(syncPoint, query) {
9903 return syncPointViewForQuery(syncPoint, query) != null;
9904}
9905function syncPointHasCompleteView(syncPoint) {
9906 return syncPointGetCompleteView(syncPoint) != null;
9907}
9908function syncPointGetCompleteView(syncPoint) {
9909 var e_5, _a;
9910 try {
9911 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9912 var view = _c.value;
9913 if (view.query._queryParams.loadsAllData()) {
9914 return view;
9915 }
9916 }
9917 }
9918 catch (e_5_1) { e_5 = { error: e_5_1 }; }
9919 finally {
9920 try {
9921 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9922 }
9923 finally { if (e_5) throw e_5.error; }
9924 }
9925 return null;
9926}
9927
9928/**
9929 * @license
9930 * Copyright 2017 Google LLC
9931 *
9932 * Licensed under the Apache License, Version 2.0 (the "License");
9933 * you may not use this file except in compliance with the License.
9934 * You may obtain a copy of the License at
9935 *
9936 * http://www.apache.org/licenses/LICENSE-2.0
9937 *
9938 * Unless required by applicable law or agreed to in writing, software
9939 * distributed under the License is distributed on an "AS IS" BASIS,
9940 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9941 * See the License for the specific language governing permissions and
9942 * limitations under the License.
9943 */
9944var referenceConstructor$1;
9945function syncTreeSetReferenceConstructor(val) {
9946 util.assert(!referenceConstructor$1, '__referenceConstructor has already been defined');
9947 referenceConstructor$1 = val;
9948}
9949function syncTreeGetReferenceConstructor() {
9950 util.assert(referenceConstructor$1, 'Reference.ts has not been loaded');
9951 return referenceConstructor$1;
9952}
9953/**
9954 * Static tracker for next query tag.
9955 */
9956var syncTreeNextQueryTag_ = 1;
9957/**
9958 * SyncTree is the central class for managing event callback registration, data caching, views
9959 * (query processing), and event generation. There are typically two SyncTree instances for
9960 * each Repo, one for the normal Firebase data, and one for the .info data.
9961 *
9962 * It has a number of responsibilities, including:
9963 * - Tracking all user event callbacks (registered via addEventRegistration() and removeEventRegistration()).
9964 * - Applying and caching data changes for user set(), transaction(), and update() calls
9965 * (applyUserOverwrite(), applyUserMerge()).
9966 * - Applying and caching data changes for server data changes (applyServerOverwrite(),
9967 * applyServerMerge()).
9968 * - Generating user-facing events for server and user changes (all of the apply* methods
9969 * return the set of events that need to be raised as a result).
9970 * - Maintaining the appropriate set of server listens to ensure we are always subscribed
9971 * to the correct set of paths and queries to satisfy the current set of user event
9972 * callbacks (listens are started/stopped using the provided listenProvider).
9973 *
9974 * NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
9975 * events are returned to the caller rather than raised synchronously.
9976 *
9977 */
9978var SyncTree = /** @class */ (function () {
9979 /**
9980 * @param listenProvider_ - Used by SyncTree to start / stop listening
9981 * to server data.
9982 */
9983 function SyncTree(listenProvider_) {
9984 this.listenProvider_ = listenProvider_;
9985 /**
9986 * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
9987 */
9988 this.syncPointTree_ = new ImmutableTree(null);
9989 /**
9990 * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
9991 */
9992 this.pendingWriteTree_ = newWriteTree();
9993 this.tagToQueryMap = new Map();
9994 this.queryToTagMap = new Map();
9995 }
9996 return SyncTree;
9997}());
9998/**
9999 * Apply the data changes for a user-generated set() or transaction() call.
10000 *
10001 * @returns Events to raise.
10002 */
10003function syncTreeApplyUserOverwrite(syncTree, path, newData, writeId, visible) {
10004 // Record pending write.
10005 writeTreeAddOverwrite(syncTree.pendingWriteTree_, path, newData, writeId, visible);
10006 if (!visible) {
10007 return [];
10008 }
10009 else {
10010 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceUser(), path, newData));
10011 }
10012}
10013/**
10014 * Apply the data from a user-generated update() call
10015 *
10016 * @returns Events to raise.
10017 */
10018function syncTreeApplyUserMerge(syncTree, path, changedChildren, writeId) {
10019 // Record pending merge.
10020 writeTreeAddMerge(syncTree.pendingWriteTree_, path, changedChildren, writeId);
10021 var changeTree = ImmutableTree.fromObject(changedChildren);
10022 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceUser(), path, changeTree));
10023}
10024/**
10025 * Acknowledge a pending user write that was previously registered with applyUserOverwrite() or applyUserMerge().
10026 *
10027 * @param revert - True if the given write failed and needs to be reverted
10028 * @returns Events to raise.
10029 */
10030function syncTreeAckUserWrite(syncTree, writeId, revert) {
10031 if (revert === void 0) { revert = false; }
10032 var write = writeTreeGetWrite(syncTree.pendingWriteTree_, writeId);
10033 var needToReevaluate = writeTreeRemoveWrite(syncTree.pendingWriteTree_, writeId);
10034 if (!needToReevaluate) {
10035 return [];
10036 }
10037 else {
10038 var affectedTree_1 = new ImmutableTree(null);
10039 if (write.snap != null) {
10040 // overwrite
10041 affectedTree_1 = affectedTree_1.set(newEmptyPath(), true);
10042 }
10043 else {
10044 each(write.children, function (pathString) {
10045 affectedTree_1 = affectedTree_1.set(new Path(pathString), true);
10046 });
10047 }
10048 return syncTreeApplyOperationToSyncPoints_(syncTree, new AckUserWrite(write.path, affectedTree_1, revert));
10049 }
10050}
10051/**
10052 * Apply new server data for the specified path..
10053 *
10054 * @returns Events to raise.
10055 */
10056function syncTreeApplyServerOverwrite(syncTree, path, newData) {
10057 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceServer(), path, newData));
10058}
10059/**
10060 * Apply new server data to be merged in at the specified path.
10061 *
10062 * @returns Events to raise.
10063 */
10064function syncTreeApplyServerMerge(syncTree, path, changedChildren) {
10065 var changeTree = ImmutableTree.fromObject(changedChildren);
10066 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceServer(), path, changeTree));
10067}
10068/**
10069 * Apply a listen complete for a query
10070 *
10071 * @returns Events to raise.
10072 */
10073function syncTreeApplyListenComplete(syncTree, path) {
10074 return syncTreeApplyOperationToSyncPoints_(syncTree, new ListenComplete(newOperationSourceServer(), path));
10075}
10076/**
10077 * Apply a listen complete for a tagged query
10078 *
10079 * @returns Events to raise.
10080 */
10081function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
10082 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10083 if (queryKey) {
10084 var r = syncTreeParseQueryKey_(queryKey);
10085 var queryPath = r.path, queryId = r.queryId;
10086 var relativePath = newRelativePath(queryPath, path);
10087 var op = new ListenComplete(newOperationSourceServerTaggedQuery(queryId), relativePath);
10088 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10089 }
10090 else {
10091 // We've already removed the query. No big deal, ignore the update
10092 return [];
10093 }
10094}
10095/**
10096 * Remove event callback(s).
10097 *
10098 * If query is the default query, we'll check all queries for the specified eventRegistration.
10099 * If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
10100 *
10101 * @param eventRegistration - If null, all callbacks are removed.
10102 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
10103 * @returns Cancel events, if cancelError was provided.
10104 */
10105function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError) {
10106 // Find the syncPoint first. Then deal with whether or not it has matching listeners
10107 var path = query._path;
10108 var maybeSyncPoint = syncTree.syncPointTree_.get(path);
10109 var cancelEvents = [];
10110 // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
10111 // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
10112 // not loadsAllData().
10113 if (maybeSyncPoint &&
10114 (query._queryIdentifier === 'default' ||
10115 syncPointViewExistsForQuery(maybeSyncPoint, query))) {
10116 var removedAndEvents = syncPointRemoveEventRegistration(maybeSyncPoint, query, eventRegistration, cancelError);
10117 if (syncPointIsEmpty(maybeSyncPoint)) {
10118 syncTree.syncPointTree_ = syncTree.syncPointTree_.remove(path);
10119 }
10120 var removed = removedAndEvents.removed;
10121 cancelEvents = removedAndEvents.events;
10122 // We may have just removed one of many listeners and can short-circuit this whole process
10123 // We may also not have removed a default listener, in which case all of the descendant listeners should already be
10124 // properly set up.
10125 //
10126 // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
10127 // queryId === 'default'
10128 var removingDefault = -1 !==
10129 removed.findIndex(function (query) {
10130 return query._queryParams.loadsAllData();
10131 });
10132 var covered = syncTree.syncPointTree_.findOnPath(path, function (relativePath, parentSyncPoint) {
10133 return syncPointHasCompleteView(parentSyncPoint);
10134 });
10135 if (removingDefault && !covered) {
10136 var subtree = syncTree.syncPointTree_.subtree(path);
10137 // There are potentially child listeners. Determine what if any listens we need to send before executing the
10138 // removal
10139 if (!subtree.isEmpty()) {
10140 // We need to fold over our subtree and collect the listeners to send
10141 var newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
10142 // Ok, we've collected all the listens we need. Set them up.
10143 for (var i = 0; i < newViews.length; ++i) {
10144 var view = newViews[i], newQuery = view.query;
10145 var listener = syncTreeCreateListenerForView_(syncTree, view);
10146 syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery_(syncTree, newQuery), listener.hashFn, listener.onComplete);
10147 }
10148 }
10149 }
10150 // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
10151 // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
10152 // Also, note that if we have a cancelError, it's already been removed at the provider level.
10153 if (!covered && removed.length > 0 && !cancelError) {
10154 // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
10155 // default. Otherwise, we need to iterate through and cancel each individual query
10156 if (removingDefault) {
10157 // We don't tag default listeners
10158 var defaultTag = null;
10159 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
10160 }
10161 else {
10162 removed.forEach(function (queryToRemove) {
10163 var tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
10164 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
10165 });
10166 }
10167 }
10168 // Now, clear all of the tags we're tracking for the removed listens
10169 syncTreeRemoveTags_(syncTree, removed);
10170 }
10171 return cancelEvents;
10172}
10173/**
10174 * Apply new server data for the specified tagged query.
10175 *
10176 * @returns Events to raise.
10177 */
10178function syncTreeApplyTaggedQueryOverwrite(syncTree, path, snap, tag) {
10179 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10180 if (queryKey != null) {
10181 var r = syncTreeParseQueryKey_(queryKey);
10182 var queryPath = r.path, queryId = r.queryId;
10183 var relativePath = newRelativePath(queryPath, path);
10184 var op = new Overwrite(newOperationSourceServerTaggedQuery(queryId), relativePath, snap);
10185 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10186 }
10187 else {
10188 // Query must have been removed already
10189 return [];
10190 }
10191}
10192/**
10193 * Apply server data to be merged in for the specified tagged query.
10194 *
10195 * @returns Events to raise.
10196 */
10197function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
10198 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10199 if (queryKey) {
10200 var r = syncTreeParseQueryKey_(queryKey);
10201 var queryPath = r.path, queryId = r.queryId;
10202 var relativePath = newRelativePath(queryPath, path);
10203 var changeTree = ImmutableTree.fromObject(changedChildren);
10204 var op = new Merge(newOperationSourceServerTaggedQuery(queryId), relativePath, changeTree);
10205 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10206 }
10207 else {
10208 // We've already removed the query. No big deal, ignore the update
10209 return [];
10210 }
10211}
10212/**
10213 * Add an event callback for the specified query.
10214 *
10215 * @returns Events to raise.
10216 */
10217function syncTreeAddEventRegistration(syncTree, query, eventRegistration) {
10218 var path = query._path;
10219 var serverCache = null;
10220 var foundAncestorDefaultView = false;
10221 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10222 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10223 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10224 var relativePath = newRelativePath(pathToSyncPoint, path);
10225 serverCache =
10226 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10227 foundAncestorDefaultView =
10228 foundAncestorDefaultView || syncPointHasCompleteView(sp);
10229 });
10230 var syncPoint = syncTree.syncPointTree_.get(path);
10231 if (!syncPoint) {
10232 syncPoint = new SyncPoint();
10233 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10234 }
10235 else {
10236 foundAncestorDefaultView =
10237 foundAncestorDefaultView || syncPointHasCompleteView(syncPoint);
10238 serverCache =
10239 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10240 }
10241 var serverCacheComplete;
10242 if (serverCache != null) {
10243 serverCacheComplete = true;
10244 }
10245 else {
10246 serverCacheComplete = false;
10247 serverCache = ChildrenNode.EMPTY_NODE;
10248 var subtree = syncTree.syncPointTree_.subtree(path);
10249 subtree.foreachChild(function (childName, childSyncPoint) {
10250 var completeCache = syncPointGetCompleteServerCache(childSyncPoint, newEmptyPath());
10251 if (completeCache) {
10252 serverCache = serverCache.updateImmediateChild(childName, completeCache);
10253 }
10254 });
10255 }
10256 var viewAlreadyExists = syncPointViewExistsForQuery(syncPoint, query);
10257 if (!viewAlreadyExists && !query._queryParams.loadsAllData()) {
10258 // We need to track a tag for this query
10259 var queryKey = syncTreeMakeQueryKey_(query);
10260 util.assert(!syncTree.queryToTagMap.has(queryKey), 'View does not exist, but we have a tag');
10261 var tag = syncTreeGetNextQueryTag_();
10262 syncTree.queryToTagMap.set(queryKey, tag);
10263 syncTree.tagToQueryMap.set(tag, queryKey);
10264 }
10265 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
10266 var events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
10267 if (!viewAlreadyExists && !foundAncestorDefaultView) {
10268 var view = syncPointViewForQuery(syncPoint, query);
10269 events = events.concat(syncTreeSetupListener_(syncTree, query, view));
10270 }
10271 return events;
10272}
10273/**
10274 * Returns a complete cache, if we have one, of the data at a particular path. If the location does not have a
10275 * listener above it, we will get a false "null". This shouldn't be a problem because transactions will always
10276 * have a listener above, and atomic operations would correctly show a jitter of <increment value> ->
10277 * <incremented total> as the write is applied locally and then acknowledged at the server.
10278 *
10279 * Note: this method will *include* hidden writes from transaction with applyLocally set to false.
10280 *
10281 * @param path - The path to the data we want
10282 * @param writeIdsToExclude - A specific set to be excluded
10283 */
10284function syncTreeCalcCompleteEventCache(syncTree, path, writeIdsToExclude) {
10285 var includeHiddenSets = true;
10286 var writeTree = syncTree.pendingWriteTree_;
10287 var serverCache = syncTree.syncPointTree_.findOnPath(path, function (pathSoFar, syncPoint) {
10288 var relativePath = newRelativePath(pathSoFar, path);
10289 var serverCache = syncPointGetCompleteServerCache(syncPoint, relativePath);
10290 if (serverCache) {
10291 return serverCache;
10292 }
10293 });
10294 return writeTreeCalcCompleteEventCache(writeTree, path, serverCache, writeIdsToExclude, includeHiddenSets);
10295}
10296function syncTreeGetServerValue(syncTree, query) {
10297 var path = query._path;
10298 var serverCache = null;
10299 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10300 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10301 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10302 var relativePath = newRelativePath(pathToSyncPoint, path);
10303 serverCache =
10304 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10305 });
10306 var syncPoint = syncTree.syncPointTree_.get(path);
10307 if (!syncPoint) {
10308 syncPoint = new SyncPoint();
10309 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10310 }
10311 else {
10312 serverCache =
10313 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10314 }
10315 var serverCacheComplete = serverCache != null;
10316 var serverCacheNode = serverCacheComplete
10317 ? new CacheNode(serverCache, true, false)
10318 : null;
10319 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, query._path);
10320 var view = syncPointGetView(syncPoint, query, writesCache, serverCacheComplete ? serverCacheNode.getNode() : ChildrenNode.EMPTY_NODE, serverCacheComplete);
10321 return viewGetCompleteNode(view);
10322}
10323/**
10324 * A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
10325 *
10326 * NOTES:
10327 * - Descendant SyncPoints will be visited first (since we raise events depth-first).
10328 *
10329 * - We call applyOperation() on each SyncPoint passing three things:
10330 * 1. A version of the Operation that has been made relative to the SyncPoint location.
10331 * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
10332 * 3. A snapshot Node with cached server data, if we have it.
10333 *
10334 * - We concatenate all of the events returned by each SyncPoint and return the result.
10335 */
10336function syncTreeApplyOperationToSyncPoints_(syncTree, operation) {
10337 return syncTreeApplyOperationHelper_(operation, syncTree.syncPointTree_,
10338 /*serverCache=*/ null, writeTreeChildWrites(syncTree.pendingWriteTree_, newEmptyPath()));
10339}
10340/**
10341 * Recursive helper for applyOperationToSyncPoints_
10342 */
10343function syncTreeApplyOperationHelper_(operation, syncPointTree, serverCache, writesCache) {
10344 if (pathIsEmpty(operation.path)) {
10345 return syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache);
10346 }
10347 else {
10348 var syncPoint = syncPointTree.get(newEmptyPath());
10349 // If we don't have cached server data, see if we can get it from this SyncPoint.
10350 if (serverCache == null && syncPoint != null) {
10351 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10352 }
10353 var events = [];
10354 var childName = pathGetFront(operation.path);
10355 var childOperation = operation.operationForChild(childName);
10356 var childTree = syncPointTree.children.get(childName);
10357 if (childTree && childOperation) {
10358 var childServerCache = serverCache
10359 ? serverCache.getImmediateChild(childName)
10360 : null;
10361 var childWritesCache = writeTreeRefChild(writesCache, childName);
10362 events = events.concat(syncTreeApplyOperationHelper_(childOperation, childTree, childServerCache, childWritesCache));
10363 }
10364 if (syncPoint) {
10365 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10366 }
10367 return events;
10368 }
10369}
10370/**
10371 * Recursive helper for applyOperationToSyncPoints_
10372 */
10373function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache) {
10374 var syncPoint = syncPointTree.get(newEmptyPath());
10375 // If we don't have cached server data, see if we can get it from this SyncPoint.
10376 if (serverCache == null && syncPoint != null) {
10377 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10378 }
10379 var events = [];
10380 syncPointTree.children.inorderTraversal(function (childName, childTree) {
10381 var childServerCache = serverCache
10382 ? serverCache.getImmediateChild(childName)
10383 : null;
10384 var childWritesCache = writeTreeRefChild(writesCache, childName);
10385 var childOperation = operation.operationForChild(childName);
10386 if (childOperation) {
10387 events = events.concat(syncTreeApplyOperationDescendantsHelper_(childOperation, childTree, childServerCache, childWritesCache));
10388 }
10389 });
10390 if (syncPoint) {
10391 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10392 }
10393 return events;
10394}
10395function syncTreeCreateListenerForView_(syncTree, view) {
10396 var query = view.query;
10397 var tag = syncTreeTagForQuery_(syncTree, query);
10398 return {
10399 hashFn: function () {
10400 var cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
10401 return cache.hash();
10402 },
10403 onComplete: function (status) {
10404 if (status === 'ok') {
10405 if (tag) {
10406 return syncTreeApplyTaggedListenComplete(syncTree, query._path, tag);
10407 }
10408 else {
10409 return syncTreeApplyListenComplete(syncTree, query._path);
10410 }
10411 }
10412 else {
10413 // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
10414 // Note that this may need to be scoped to just this listener if we change permissions on filtered children
10415 var error = errorForServerCode(status, query);
10416 return syncTreeRemoveEventRegistration(syncTree, query,
10417 /*eventRegistration*/ null, error);
10418 }
10419 }
10420 };
10421}
10422/**
10423 * Return the tag associated with the given query.
10424 */
10425function syncTreeTagForQuery_(syncTree, query) {
10426 var queryKey = syncTreeMakeQueryKey_(query);
10427 return syncTree.queryToTagMap.get(queryKey);
10428}
10429/**
10430 * Given a query, computes a "queryKey" suitable for use in our queryToTagMap_.
10431 */
10432function syncTreeMakeQueryKey_(query) {
10433 return query._path.toString() + '$' + query._queryIdentifier;
10434}
10435/**
10436 * Return the query associated with the given tag, if we have one
10437 */
10438function syncTreeQueryKeyForTag_(syncTree, tag) {
10439 return syncTree.tagToQueryMap.get(tag);
10440}
10441/**
10442 * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId.
10443 */
10444function syncTreeParseQueryKey_(queryKey) {
10445 var splitIndex = queryKey.indexOf('$');
10446 util.assert(splitIndex !== -1 && splitIndex < queryKey.length - 1, 'Bad queryKey.');
10447 return {
10448 queryId: queryKey.substr(splitIndex + 1),
10449 path: new Path(queryKey.substr(0, splitIndex))
10450 };
10451}
10452/**
10453 * A helper method to apply tagged operations
10454 */
10455function syncTreeApplyTaggedOperation_(syncTree, queryPath, operation) {
10456 var syncPoint = syncTree.syncPointTree_.get(queryPath);
10457 util.assert(syncPoint, "Missing sync point for query tag that we're tracking");
10458 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, queryPath);
10459 return syncPointApplyOperation(syncPoint, operation, writesCache, null);
10460}
10461/**
10462 * This collapses multiple unfiltered views into a single view, since we only need a single
10463 * listener for them.
10464 */
10465function syncTreeCollectDistinctViewsForSubTree_(subtree) {
10466 return subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10467 if (maybeChildSyncPoint && syncPointHasCompleteView(maybeChildSyncPoint)) {
10468 var completeView = syncPointGetCompleteView(maybeChildSyncPoint);
10469 return [completeView];
10470 }
10471 else {
10472 // No complete view here, flatten any deeper listens into an array
10473 var views_1 = [];
10474 if (maybeChildSyncPoint) {
10475 views_1 = syncPointGetQueryViews(maybeChildSyncPoint);
10476 }
10477 each(childMap, function (_key, childViews) {
10478 views_1 = views_1.concat(childViews);
10479 });
10480 return views_1;
10481 }
10482 });
10483}
10484/**
10485 * Normalizes a query to a query we send the server for listening
10486 *
10487 * @returns The normalized query
10488 */
10489function syncTreeQueryForListening_(query) {
10490 if (query._queryParams.loadsAllData() && !query._queryParams.isDefault()) {
10491 // We treat queries that load all data as default queries
10492 // Cast is necessary because ref() technically returns Firebase which is actually fb.api.Firebase which inherits
10493 // from Query
10494 return new (syncTreeGetReferenceConstructor())(query._repo, query._path);
10495 }
10496 else {
10497 return query;
10498 }
10499}
10500function syncTreeRemoveTags_(syncTree, queries) {
10501 for (var j = 0; j < queries.length; ++j) {
10502 var removedQuery = queries[j];
10503 if (!removedQuery._queryParams.loadsAllData()) {
10504 // We should have a tag for this
10505 var removedQueryKey = syncTreeMakeQueryKey_(removedQuery);
10506 var removedQueryTag = syncTree.queryToTagMap.get(removedQueryKey);
10507 syncTree.queryToTagMap.delete(removedQueryKey);
10508 syncTree.tagToQueryMap.delete(removedQueryTag);
10509 }
10510 }
10511}
10512/**
10513 * Static accessor for query tags.
10514 */
10515function syncTreeGetNextQueryTag_() {
10516 return syncTreeNextQueryTag_++;
10517}
10518/**
10519 * For a given new listen, manage the de-duplication of outstanding subscriptions.
10520 *
10521 * @returns This method can return events to support synchronous data sources
10522 */
10523function syncTreeSetupListener_(syncTree, query, view) {
10524 var path = query._path;
10525 var tag = syncTreeTagForQuery_(syncTree, query);
10526 var listener = syncTreeCreateListenerForView_(syncTree, view);
10527 var events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10528 var subtree = syncTree.syncPointTree_.subtree(path);
10529 // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
10530 // may need to shadow other listens as well.
10531 if (tag) {
10532 util.assert(!syncPointHasCompleteView(subtree.value), "If we're adding a query, it shouldn't be shadowed");
10533 }
10534 else {
10535 // Shadow everything at or below this location, this is a default listener.
10536 var queriesToStop = subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10537 if (!pathIsEmpty(relativePath) &&
10538 maybeChildSyncPoint &&
10539 syncPointHasCompleteView(maybeChildSyncPoint)) {
10540 return [syncPointGetCompleteView(maybeChildSyncPoint).query];
10541 }
10542 else {
10543 // No default listener here, flatten any deeper queries into an array
10544 var queries_1 = [];
10545 if (maybeChildSyncPoint) {
10546 queries_1 = queries_1.concat(syncPointGetQueryViews(maybeChildSyncPoint).map(function (view) { return view.query; }));
10547 }
10548 each(childMap, function (_key, childQueries) {
10549 queries_1 = queries_1.concat(childQueries);
10550 });
10551 return queries_1;
10552 }
10553 });
10554 for (var i = 0; i < queriesToStop.length; ++i) {
10555 var queryToStop = queriesToStop[i];
10556 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery_(syncTree, queryToStop));
10557 }
10558 }
10559 return events;
10560}
10561
10562/**
10563 * @license
10564 * Copyright 2017 Google LLC
10565 *
10566 * Licensed under the Apache License, Version 2.0 (the "License");
10567 * you may not use this file except in compliance with the License.
10568 * You may obtain a copy of the License at
10569 *
10570 * http://www.apache.org/licenses/LICENSE-2.0
10571 *
10572 * Unless required by applicable law or agreed to in writing, software
10573 * distributed under the License is distributed on an "AS IS" BASIS,
10574 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10575 * See the License for the specific language governing permissions and
10576 * limitations under the License.
10577 */
10578var ExistingValueProvider = /** @class */ (function () {
10579 function ExistingValueProvider(node_) {
10580 this.node_ = node_;
10581 }
10582 ExistingValueProvider.prototype.getImmediateChild = function (childName) {
10583 var child = this.node_.getImmediateChild(childName);
10584 return new ExistingValueProvider(child);
10585 };
10586 ExistingValueProvider.prototype.node = function () {
10587 return this.node_;
10588 };
10589 return ExistingValueProvider;
10590}());
10591var DeferredValueProvider = /** @class */ (function () {
10592 function DeferredValueProvider(syncTree, path) {
10593 this.syncTree_ = syncTree;
10594 this.path_ = path;
10595 }
10596 DeferredValueProvider.prototype.getImmediateChild = function (childName) {
10597 var childPath = pathChild(this.path_, childName);
10598 return new DeferredValueProvider(this.syncTree_, childPath);
10599 };
10600 DeferredValueProvider.prototype.node = function () {
10601 return syncTreeCalcCompleteEventCache(this.syncTree_, this.path_);
10602 };
10603 return DeferredValueProvider;
10604}());
10605/**
10606 * Generate placeholders for deferred values.
10607 */
10608var generateWithValues = function (values) {
10609 values = values || {};
10610 values['timestamp'] = values['timestamp'] || new Date().getTime();
10611 return values;
10612};
10613/**
10614 * Value to use when firing local events. When writing server values, fire
10615 * local events with an approximate value, otherwise return value as-is.
10616 */
10617var resolveDeferredLeafValue = function (value, existingVal, serverValues) {
10618 if (!value || typeof value !== 'object') {
10619 return value;
10620 }
10621 util.assert('.sv' in value, 'Unexpected leaf node or priority contents');
10622 if (typeof value['.sv'] === 'string') {
10623 return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
10624 }
10625 else if (typeof value['.sv'] === 'object') {
10626 return resolveComplexDeferredValue(value['.sv'], existingVal);
10627 }
10628 else {
10629 util.assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
10630 }
10631};
10632var resolveScalarDeferredValue = function (op, existing, serverValues) {
10633 switch (op) {
10634 case 'timestamp':
10635 return serverValues['timestamp'];
10636 default:
10637 util.assert(false, 'Unexpected server value: ' + op);
10638 }
10639};
10640var resolveComplexDeferredValue = function (op, existing, unused) {
10641 if (!op.hasOwnProperty('increment')) {
10642 util.assert(false, 'Unexpected server value: ' + JSON.stringify(op, null, 2));
10643 }
10644 var delta = op['increment'];
10645 if (typeof delta !== 'number') {
10646 util.assert(false, 'Unexpected increment value: ' + delta);
10647 }
10648 var existingNode = existing.node();
10649 util.assert(existingNode !== null && typeof existingNode !== 'undefined', 'Expected ChildrenNode.EMPTY_NODE for nulls');
10650 // Incrementing a non-number sets the value to the incremented amount
10651 if (!existingNode.isLeafNode()) {
10652 return delta;
10653 }
10654 var leaf = existingNode;
10655 var existingVal = leaf.getValue();
10656 if (typeof existingVal !== 'number') {
10657 return delta;
10658 }
10659 // No need to do over/underflow arithmetic here because JS only handles floats under the covers
10660 return existingVal + delta;
10661};
10662/**
10663 * Recursively replace all deferred values and priorities in the tree with the
10664 * specified generated replacement values.
10665 * @param path - path to which write is relative
10666 * @param node - new data written at path
10667 * @param syncTree - current data
10668 */
10669var resolveDeferredValueTree = function (path, node, syncTree, serverValues) {
10670 return resolveDeferredValue(node, new DeferredValueProvider(syncTree, path), serverValues);
10671};
10672/**
10673 * Recursively replace all deferred values and priorities in the node with the
10674 * specified generated replacement values. If there are no server values in the node,
10675 * it'll be returned as-is.
10676 */
10677var resolveDeferredValueSnapshot = function (node, existing, serverValues) {
10678 return resolveDeferredValue(node, new ExistingValueProvider(existing), serverValues);
10679};
10680function resolveDeferredValue(node, existingVal, serverValues) {
10681 var rawPri = node.getPriority().val();
10682 var priority = resolveDeferredLeafValue(rawPri, existingVal.getImmediateChild('.priority'), serverValues);
10683 var newNode;
10684 if (node.isLeafNode()) {
10685 var leafNode = node;
10686 var value = resolveDeferredLeafValue(leafNode.getValue(), existingVal, serverValues);
10687 if (value !== leafNode.getValue() ||
10688 priority !== leafNode.getPriority().val()) {
10689 return new LeafNode(value, nodeFromJSON$1(priority));
10690 }
10691 else {
10692 return node;
10693 }
10694 }
10695 else {
10696 var childrenNode = node;
10697 newNode = childrenNode;
10698 if (priority !== childrenNode.getPriority().val()) {
10699 newNode = newNode.updatePriority(new LeafNode(priority));
10700 }
10701 childrenNode.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
10702 var newChildNode = resolveDeferredValue(childNode, existingVal.getImmediateChild(childName), serverValues);
10703 if (newChildNode !== childNode) {
10704 newNode = newNode.updateImmediateChild(childName, newChildNode);
10705 }
10706 });
10707 return newNode;
10708 }
10709}
10710
10711/**
10712 * @license
10713 * Copyright 2017 Google LLC
10714 *
10715 * Licensed under the Apache License, Version 2.0 (the "License");
10716 * you may not use this file except in compliance with the License.
10717 * You may obtain a copy of the License at
10718 *
10719 * http://www.apache.org/licenses/LICENSE-2.0
10720 *
10721 * Unless required by applicable law or agreed to in writing, software
10722 * distributed under the License is distributed on an "AS IS" BASIS,
10723 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10724 * See the License for the specific language governing permissions and
10725 * limitations under the License.
10726 */
10727/**
10728 * A light-weight tree, traversable by path. Nodes can have both values and children.
10729 * Nodes are not enumerated (by forEachChild) unless they have a value or non-empty
10730 * children.
10731 */
10732var Tree = /** @class */ (function () {
10733 /**
10734 * @param name - Optional name of the node.
10735 * @param parent - Optional parent node.
10736 * @param node - Optional node to wrap.
10737 */
10738 function Tree(name, parent, node) {
10739 if (name === void 0) { name = ''; }
10740 if (parent === void 0) { parent = null; }
10741 if (node === void 0) { node = { children: {}, childCount: 0 }; }
10742 this.name = name;
10743 this.parent = parent;
10744 this.node = node;
10745 }
10746 return Tree;
10747}());
10748/**
10749 * Returns a sub-Tree for the given path.
10750 *
10751 * @param pathObj - Path to look up.
10752 * @returns Tree for path.
10753 */
10754function treeSubTree(tree, pathObj) {
10755 // TODO: Require pathObj to be Path?
10756 var path = pathObj instanceof Path ? pathObj : new Path(pathObj);
10757 var child = tree, next = pathGetFront(path);
10758 while (next !== null) {
10759 var childNode = util.safeGet(child.node.children, next) || {
10760 children: {},
10761 childCount: 0
10762 };
10763 child = new Tree(next, child, childNode);
10764 path = pathPopFront(path);
10765 next = pathGetFront(path);
10766 }
10767 return child;
10768}
10769/**
10770 * Returns the data associated with this tree node.
10771 *
10772 * @returns The data or null if no data exists.
10773 */
10774function treeGetValue(tree) {
10775 return tree.node.value;
10776}
10777/**
10778 * Sets data to this tree node.
10779 *
10780 * @param value - Value to set.
10781 */
10782function treeSetValue(tree, value) {
10783 tree.node.value = value;
10784 treeUpdateParents(tree);
10785}
10786/**
10787 * @returns Whether the tree has any children.
10788 */
10789function treeHasChildren(tree) {
10790 return tree.node.childCount > 0;
10791}
10792/**
10793 * @returns Whethe rthe tree is empty (no value or children).
10794 */
10795function treeIsEmpty(tree) {
10796 return treeGetValue(tree) === undefined && !treeHasChildren(tree);
10797}
10798/**
10799 * Calls action for each child of this tree node.
10800 *
10801 * @param action - Action to be called for each child.
10802 */
10803function treeForEachChild(tree, action) {
10804 each(tree.node.children, function (child, childTree) {
10805 action(new Tree(child, tree, childTree));
10806 });
10807}
10808/**
10809 * Does a depth-first traversal of this node's descendants, calling action for each one.
10810 *
10811 * @param action - Action to be called for each child.
10812 * @param includeSelf - Whether to call action on this node as well. Defaults to
10813 * false.
10814 * @param childrenFirst - Whether to call action on children before calling it on
10815 * parent.
10816 */
10817function treeForEachDescendant(tree, action, includeSelf, childrenFirst) {
10818 if (includeSelf && !childrenFirst) {
10819 action(tree);
10820 }
10821 treeForEachChild(tree, function (child) {
10822 treeForEachDescendant(child, action, true, childrenFirst);
10823 });
10824 if (includeSelf && childrenFirst) {
10825 action(tree);
10826 }
10827}
10828/**
10829 * Calls action on each ancestor node.
10830 *
10831 * @param action - Action to be called on each parent; return
10832 * true to abort.
10833 * @param includeSelf - Whether to call action on this node as well.
10834 * @returns true if the action callback returned true.
10835 */
10836function treeForEachAncestor(tree, action, includeSelf) {
10837 var node = includeSelf ? tree : tree.parent;
10838 while (node !== null) {
10839 if (action(node)) {
10840 return true;
10841 }
10842 node = node.parent;
10843 }
10844 return false;
10845}
10846/**
10847 * @returns The path of this tree node, as a Path.
10848 */
10849function treeGetPath(tree) {
10850 return new Path(tree.parent === null
10851 ? tree.name
10852 : treeGetPath(tree.parent) + '/' + tree.name);
10853}
10854/**
10855 * Adds or removes this child from its parent based on whether it's empty or not.
10856 */
10857function treeUpdateParents(tree) {
10858 if (tree.parent !== null) {
10859 treeUpdateChild(tree.parent, tree.name, tree);
10860 }
10861}
10862/**
10863 * Adds or removes the passed child to this tree node, depending on whether it's empty.
10864 *
10865 * @param childName - The name of the child to update.
10866 * @param child - The child to update.
10867 */
10868function treeUpdateChild(tree, childName, child) {
10869 var childEmpty = treeIsEmpty(child);
10870 var childExists = util.contains(tree.node.children, childName);
10871 if (childEmpty && childExists) {
10872 delete tree.node.children[childName];
10873 tree.node.childCount--;
10874 treeUpdateParents(tree);
10875 }
10876 else if (!childEmpty && !childExists) {
10877 tree.node.children[childName] = child.node;
10878 tree.node.childCount++;
10879 treeUpdateParents(tree);
10880 }
10881}
10882
10883/**
10884 * @license
10885 * Copyright 2017 Google LLC
10886 *
10887 * Licensed under the Apache License, Version 2.0 (the "License");
10888 * you may not use this file except in compliance with the License.
10889 * You may obtain a copy of the License at
10890 *
10891 * http://www.apache.org/licenses/LICENSE-2.0
10892 *
10893 * Unless required by applicable law or agreed to in writing, software
10894 * distributed under the License is distributed on an "AS IS" BASIS,
10895 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10896 * See the License for the specific language governing permissions and
10897 * limitations under the License.
10898 */
10899/**
10900 * True for invalid Firebase keys
10901 */
10902var INVALID_KEY_REGEX_ = /[\[\].#$\/\u0000-\u001F\u007F]/;
10903/**
10904 * True for invalid Firebase paths.
10905 * Allows '/' in paths.
10906 */
10907var INVALID_PATH_REGEX_ = /[\[\].#$\u0000-\u001F\u007F]/;
10908/**
10909 * Maximum number of characters to allow in leaf value
10910 */
10911var MAX_LEAF_SIZE_ = 10 * 1024 * 1024;
10912var isValidKey = function (key) {
10913 return (typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX_.test(key));
10914};
10915var isValidPathString = function (pathString) {
10916 return (typeof pathString === 'string' &&
10917 pathString.length !== 0 &&
10918 !INVALID_PATH_REGEX_.test(pathString));
10919};
10920var isValidRootPathString = function (pathString) {
10921 if (pathString) {
10922 // Allow '/.info/' at the beginning.
10923 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10924 }
10925 return isValidPathString(pathString);
10926};
10927var isValidPriority = function (priority) {
10928 return (priority === null ||
10929 typeof priority === 'string' ||
10930 (typeof priority === 'number' && !isInvalidJSONNumber(priority)) ||
10931 (priority &&
10932 typeof priority === 'object' &&
10933 // eslint-disable-next-line @typescript-eslint/no-explicit-any
10934 util.contains(priority, '.sv')));
10935};
10936/**
10937 * Pre-validate a datum passed as an argument to Firebase function.
10938 */
10939var validateFirebaseDataArg = function (fnName, value, path, optional) {
10940 if (optional && value === undefined) {
10941 return;
10942 }
10943 validateFirebaseData(util.errorPrefix(fnName, 'value'), value, path);
10944};
10945/**
10946 * Validate a data object client-side before sending to server.
10947 */
10948var validateFirebaseData = function (errorPrefix, data, path_) {
10949 var path = path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;
10950 if (data === undefined) {
10951 throw new Error(errorPrefix + 'contains undefined ' + validationPathToErrorString(path));
10952 }
10953 if (typeof data === 'function') {
10954 throw new Error(errorPrefix +
10955 'contains a function ' +
10956 validationPathToErrorString(path) +
10957 ' with contents = ' +
10958 data.toString());
10959 }
10960 if (isInvalidJSONNumber(data)) {
10961 throw new Error(errorPrefix +
10962 'contains ' +
10963 data.toString() +
10964 ' ' +
10965 validationPathToErrorString(path));
10966 }
10967 // Check max leaf size, but try to avoid the utf8 conversion if we can.
10968 if (typeof data === 'string' &&
10969 data.length > MAX_LEAF_SIZE_ / 3 &&
10970 util.stringLength(data) > MAX_LEAF_SIZE_) {
10971 throw new Error(errorPrefix +
10972 'contains a string greater than ' +
10973 MAX_LEAF_SIZE_ +
10974 ' utf8 bytes ' +
10975 validationPathToErrorString(path) +
10976 " ('" +
10977 data.substring(0, 50) +
10978 "...')");
10979 }
10980 // TODO = Perf = Consider combining the recursive validation of keys into NodeFromJSON
10981 // to save extra walking of large objects.
10982 if (data && typeof data === 'object') {
10983 var hasDotValue_1 = false;
10984 var hasActualChild_1 = false;
10985 each(data, function (key, value) {
10986 if (key === '.value') {
10987 hasDotValue_1 = true;
10988 }
10989 else if (key !== '.priority' && key !== '.sv') {
10990 hasActualChild_1 = true;
10991 if (!isValidKey(key)) {
10992 throw new Error(errorPrefix +
10993 ' contains an invalid key (' +
10994 key +
10995 ') ' +
10996 validationPathToErrorString(path) +
10997 '. Keys must be non-empty strings ' +
10998 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
10999 }
11000 }
11001 validationPathPush(path, key);
11002 validateFirebaseData(errorPrefix, value, path);
11003 validationPathPop(path);
11004 });
11005 if (hasDotValue_1 && hasActualChild_1) {
11006 throw new Error(errorPrefix +
11007 ' contains ".value" child ' +
11008 validationPathToErrorString(path) +
11009 ' in addition to actual children.');
11010 }
11011 }
11012};
11013/**
11014 * Pre-validate paths passed in the firebase function.
11015 */
11016var validateFirebaseMergePaths = function (errorPrefix, mergePaths) {
11017 var i, curPath;
11018 for (i = 0; i < mergePaths.length; i++) {
11019 curPath = mergePaths[i];
11020 var keys = pathSlice(curPath);
11021 for (var j = 0; j < keys.length; j++) {
11022 if (keys[j] === '.priority' && j === keys.length - 1) ;
11023 else if (!isValidKey(keys[j])) {
11024 throw new Error(errorPrefix +
11025 'contains an invalid key (' +
11026 keys[j] +
11027 ') in path ' +
11028 curPath.toString() +
11029 '. Keys must be non-empty strings ' +
11030 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11031 }
11032 }
11033 }
11034 // Check that update keys are not descendants of each other.
11035 // We rely on the property that sorting guarantees that ancestors come
11036 // right before descendants.
11037 mergePaths.sort(pathCompare);
11038 var prevPath = null;
11039 for (i = 0; i < mergePaths.length; i++) {
11040 curPath = mergePaths[i];
11041 if (prevPath !== null && pathContains(prevPath, curPath)) {
11042 throw new Error(errorPrefix +
11043 'contains a path ' +
11044 prevPath.toString() +
11045 ' that is ancestor of another path ' +
11046 curPath.toString());
11047 }
11048 prevPath = curPath;
11049 }
11050};
11051/**
11052 * pre-validate an object passed as an argument to firebase function (
11053 * must be an object - e.g. for firebase.update()).
11054 */
11055var validateFirebaseMergeDataArg = function (fnName, data, path, optional) {
11056 if (optional && data === undefined) {
11057 return;
11058 }
11059 var errorPrefix = util.errorPrefix(fnName, 'values');
11060 if (!(data && typeof data === 'object') || Array.isArray(data)) {
11061 throw new Error(errorPrefix + ' must be an object containing the children to replace.');
11062 }
11063 var mergePaths = [];
11064 each(data, function (key, value) {
11065 var curPath = new Path(key);
11066 validateFirebaseData(errorPrefix, value, pathChild(path, curPath));
11067 if (pathGetBack(curPath) === '.priority') {
11068 if (!isValidPriority(value)) {
11069 throw new Error(errorPrefix +
11070 "contains an invalid value for '" +
11071 curPath.toString() +
11072 "', which must be a valid " +
11073 'Firebase priority (a string, finite number, server value, or null).');
11074 }
11075 }
11076 mergePaths.push(curPath);
11077 });
11078 validateFirebaseMergePaths(errorPrefix, mergePaths);
11079};
11080var validatePriority = function (fnName, priority, optional) {
11081 if (optional && priority === undefined) {
11082 return;
11083 }
11084 if (isInvalidJSONNumber(priority)) {
11085 throw new Error(util.errorPrefix(fnName, 'priority') +
11086 'is ' +
11087 priority.toString() +
11088 ', but must be a valid Firebase priority (a string, finite number, ' +
11089 'server value, or null).');
11090 }
11091 // Special case to allow importing data with a .sv.
11092 if (!isValidPriority(priority)) {
11093 throw new Error(util.errorPrefix(fnName, 'priority') +
11094 'must be a valid Firebase priority ' +
11095 '(a string, finite number, server value, or null).');
11096 }
11097};
11098var validateEventType = function (fnName, eventType, optional) {
11099 if (optional && eventType === undefined) {
11100 return;
11101 }
11102 switch (eventType) {
11103 case 'value':
11104 case 'child_added':
11105 case 'child_removed':
11106 case 'child_changed':
11107 case 'child_moved':
11108 break;
11109 default:
11110 throw new Error(util.errorPrefix(fnName, 'eventType') +
11111 'must be a valid event type = "value", "child_added", "child_removed", ' +
11112 '"child_changed", or "child_moved".');
11113 }
11114};
11115var validateKey = function (fnName, argumentName, key, optional) {
11116 if (optional && key === undefined) {
11117 return;
11118 }
11119 if (!isValidKey(key)) {
11120 throw new Error(util.errorPrefix(fnName, argumentName) +
11121 'was an invalid key = "' +
11122 key +
11123 '". Firebase keys must be non-empty strings and ' +
11124 'can\'t contain ".", "#", "$", "/", "[", or "]").');
11125 }
11126};
11127var validatePathString = function (fnName, argumentName, pathString, optional) {
11128 if (optional && pathString === undefined) {
11129 return;
11130 }
11131 if (!isValidPathString(pathString)) {
11132 throw new Error(util.errorPrefix(fnName, argumentName) +
11133 'was an invalid path = "' +
11134 pathString +
11135 '". Paths must be non-empty strings and ' +
11136 'can\'t contain ".", "#", "$", "[", or "]"');
11137 }
11138};
11139var validateRootPathString = function (fnName, argumentName, pathString, optional) {
11140 if (pathString) {
11141 // Allow '/.info/' at the beginning.
11142 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
11143 }
11144 validatePathString(fnName, argumentName, pathString, optional);
11145};
11146var validateWritablePath = function (fnName, path) {
11147 if (pathGetFront(path) === '.info') {
11148 throw new Error(fnName + " failed = Can't modify data under /.info/");
11149 }
11150};
11151var validateUrl = function (fnName, parsedUrl) {
11152 // TODO = Validate server better.
11153 var pathString = parsedUrl.path.toString();
11154 if (!(typeof parsedUrl.repoInfo.host === 'string') ||
11155 parsedUrl.repoInfo.host.length === 0 ||
11156 (!isValidKey(parsedUrl.repoInfo.namespace) &&
11157 parsedUrl.repoInfo.host.split(':')[0] !== 'localhost') ||
11158 (pathString.length !== 0 && !isValidRootPathString(pathString))) {
11159 throw new Error(util.errorPrefix(fnName, 'url') +
11160 'must be a valid firebase URL and ' +
11161 'the path can\'t contain ".", "#", "$", "[", or "]".');
11162 }
11163};
11164var validateBoolean = function (fnName, argumentName, bool, optional) {
11165 if (optional && bool === undefined) {
11166 return;
11167 }
11168 if (typeof bool !== 'boolean') {
11169 throw new Error(util.errorPrefix(fnName, argumentName) + 'must be a boolean.');
11170 }
11171};
11172
11173/**
11174 * @license
11175 * Copyright 2017 Google LLC
11176 *
11177 * Licensed under the Apache License, Version 2.0 (the "License");
11178 * you may not use this file except in compliance with the License.
11179 * You may obtain a copy of the License at
11180 *
11181 * http://www.apache.org/licenses/LICENSE-2.0
11182 *
11183 * Unless required by applicable law or agreed to in writing, software
11184 * distributed under the License is distributed on an "AS IS" BASIS,
11185 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11186 * See the License for the specific language governing permissions and
11187 * limitations under the License.
11188 */
11189/**
11190 * The event queue serves a few purposes:
11191 * 1. It ensures we maintain event order in the face of event callbacks doing operations that result in more
11192 * events being queued.
11193 * 2. raiseQueuedEvents() handles being called reentrantly nicely. That is, if in the course of raising events,
11194 * raiseQueuedEvents() is called again, the "inner" call will pick up raising events where the "outer" call
11195 * left off, ensuring that the events are still raised synchronously and in order.
11196 * 3. You can use raiseEventsAtPath and raiseEventsForChangedPath to ensure only relevant previously-queued
11197 * events are raised synchronously.
11198 *
11199 * NOTE: This can all go away if/when we move to async events.
11200 *
11201 */
11202var EventQueue = /** @class */ (function () {
11203 function EventQueue() {
11204 this.eventLists_ = [];
11205 /**
11206 * Tracks recursion depth of raiseQueuedEvents_, for debugging purposes.
11207 */
11208 this.recursionDepth_ = 0;
11209 }
11210 return EventQueue;
11211}());
11212/**
11213 * @param eventDataList - The new events to queue.
11214 */
11215function eventQueueQueueEvents(eventQueue, eventDataList) {
11216 // We group events by path, storing them in a single EventList, to make it easier to skip over them quickly.
11217 var currList = null;
11218 for (var i = 0; i < eventDataList.length; i++) {
11219 var data = eventDataList[i];
11220 var path = data.getPath();
11221 if (currList !== null && !pathEquals(path, currList.path)) {
11222 eventQueue.eventLists_.push(currList);
11223 currList = null;
11224 }
11225 if (currList === null) {
11226 currList = { events: [], path: path };
11227 }
11228 currList.events.push(data);
11229 }
11230 if (currList) {
11231 eventQueue.eventLists_.push(currList);
11232 }
11233}
11234/**
11235 * Queues the specified events and synchronously raises all events (including previously queued ones)
11236 * for the specified path.
11237 *
11238 * It is assumed that the new events are all for the specified path.
11239 *
11240 * @param path - The path to raise events for.
11241 * @param eventDataList - The new events to raise.
11242 */
11243function eventQueueRaiseEventsAtPath(eventQueue, path, eventDataList) {
11244 eventQueueQueueEvents(eventQueue, eventDataList);
11245 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11246 return pathEquals(eventPath, path);
11247 });
11248}
11249/**
11250 * Queues the specified events and synchronously raises all events (including previously queued ones) for
11251 * locations related to the specified change path (i.e. all ancestors and descendants).
11252 *
11253 * It is assumed that the new events are all related (ancestor or descendant) to the specified path.
11254 *
11255 * @param changedPath - The path to raise events for.
11256 * @param eventDataList - The events to raise
11257 */
11258function eventQueueRaiseEventsForChangedPath(eventQueue, changedPath, eventDataList) {
11259 eventQueueQueueEvents(eventQueue, eventDataList);
11260 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11261 return pathContains(eventPath, changedPath) ||
11262 pathContains(changedPath, eventPath);
11263 });
11264}
11265function eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, predicate) {
11266 eventQueue.recursionDepth_++;
11267 var sentAll = true;
11268 for (var i = 0; i < eventQueue.eventLists_.length; i++) {
11269 var eventList = eventQueue.eventLists_[i];
11270 if (eventList) {
11271 var eventPath = eventList.path;
11272 if (predicate(eventPath)) {
11273 eventListRaise(eventQueue.eventLists_[i]);
11274 eventQueue.eventLists_[i] = null;
11275 }
11276 else {
11277 sentAll = false;
11278 }
11279 }
11280 }
11281 if (sentAll) {
11282 eventQueue.eventLists_ = [];
11283 }
11284 eventQueue.recursionDepth_--;
11285}
11286/**
11287 * Iterates through the list and raises each event
11288 */
11289function eventListRaise(eventList) {
11290 for (var i = 0; i < eventList.events.length; i++) {
11291 var eventData = eventList.events[i];
11292 if (eventData !== null) {
11293 eventList.events[i] = null;
11294 var eventFn = eventData.getEventRunner();
11295 if (logger) {
11296 log('event: ' + eventData.toString());
11297 }
11298 exceptionGuard(eventFn);
11299 }
11300 }
11301}
11302
11303/**
11304 * @license
11305 * Copyright 2017 Google LLC
11306 *
11307 * Licensed under the Apache License, Version 2.0 (the "License");
11308 * you may not use this file except in compliance with the License.
11309 * You may obtain a copy of the License at
11310 *
11311 * http://www.apache.org/licenses/LICENSE-2.0
11312 *
11313 * Unless required by applicable law or agreed to in writing, software
11314 * distributed under the License is distributed on an "AS IS" BASIS,
11315 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11316 * See the License for the specific language governing permissions and
11317 * limitations under the License.
11318 */
11319var INTERRUPT_REASON = 'repo_interrupt';
11320/**
11321 * If a transaction does not succeed after 25 retries, we abort it. Among other
11322 * things this ensure that if there's ever a bug causing a mismatch between
11323 * client / server hashes for some data, we won't retry indefinitely.
11324 */
11325var MAX_TRANSACTION_RETRIES = 25;
11326/**
11327 * A connection to a single data repository.
11328 */
11329var Repo = /** @class */ (function () {
11330 function Repo(repoInfo_, forceRestClient_, authTokenProvider_, appCheckProvider_) {
11331 this.repoInfo_ = repoInfo_;
11332 this.forceRestClient_ = forceRestClient_;
11333 this.authTokenProvider_ = authTokenProvider_;
11334 this.appCheckProvider_ = appCheckProvider_;
11335 this.dataUpdateCount = 0;
11336 this.statsListener_ = null;
11337 this.eventQueue_ = new EventQueue();
11338 this.nextWriteId_ = 1;
11339 this.interceptServerDataCallback_ = null;
11340 /** A list of data pieces and paths to be set when this client disconnects. */
11341 this.onDisconnect_ = newSparseSnapshotTree();
11342 /** Stores queues of outstanding transactions for Firebase locations. */
11343 this.transactionQueueTree_ = new Tree();
11344 // TODO: This should be @private but it's used by test_access.js and internal.js
11345 this.persistentConnection_ = null;
11346 // This key is intentionally not updated if RepoInfo is later changed or replaced
11347 this.key = this.repoInfo_.toURLString();
11348 }
11349 /**
11350 * @returns The URL corresponding to the root of this Firebase.
11351 */
11352 Repo.prototype.toString = function () {
11353 return ((this.repoInfo_.secure ? 'https://' : 'http://') + this.repoInfo_.host);
11354 };
11355 return Repo;
11356}());
11357function repoStart(repo, appId, authOverride) {
11358 repo.stats_ = statsManagerGetCollection(repo.repoInfo_);
11359 if (repo.forceRestClient_ || beingCrawled()) {
11360 repo.server_ = new ReadonlyRestClient(repo.repoInfo_, function (pathString, data, isMerge, tag) {
11361 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11362 }, repo.authTokenProvider_, repo.appCheckProvider_);
11363 // Minor hack: Fire onConnect immediately, since there's no actual connection.
11364 setTimeout(function () { return repoOnConnectStatus(repo, /* connectStatus= */ true); }, 0);
11365 }
11366 else {
11367 // Validate authOverride
11368 if (typeof authOverride !== 'undefined' && authOverride !== null) {
11369 if (typeof authOverride !== 'object') {
11370 throw new Error('Only objects are supported for option databaseAuthVariableOverride');
11371 }
11372 try {
11373 util.stringify(authOverride);
11374 }
11375 catch (e) {
11376 throw new Error('Invalid authOverride provided: ' + e);
11377 }
11378 }
11379 repo.persistentConnection_ = new PersistentConnection(repo.repoInfo_, appId, function (pathString, data, isMerge, tag) {
11380 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11381 }, function (connectStatus) {
11382 repoOnConnectStatus(repo, connectStatus);
11383 }, function (updates) {
11384 repoOnServerInfoUpdate(repo, updates);
11385 }, repo.authTokenProvider_, repo.appCheckProvider_, authOverride);
11386 repo.server_ = repo.persistentConnection_;
11387 }
11388 repo.authTokenProvider_.addTokenChangeListener(function (token) {
11389 repo.server_.refreshAuthToken(token);
11390 });
11391 repo.appCheckProvider_.addTokenChangeListener(function (result) {
11392 repo.server_.refreshAppCheckToken(result.token);
11393 });
11394 // In the case of multiple Repos for the same repoInfo (i.e. there are multiple Firebase.Contexts being used),
11395 // we only want to create one StatsReporter. As such, we'll report stats over the first Repo created.
11396 repo.statsReporter_ = statsManagerGetOrCreateReporter(repo.repoInfo_, function () { return new StatsReporter(repo.stats_, repo.server_); });
11397 // Used for .info.
11398 repo.infoData_ = new SnapshotHolder();
11399 repo.infoSyncTree_ = new SyncTree({
11400 startListening: function (query, tag, currentHashFn, onComplete) {
11401 var infoEvents = [];
11402 var node = repo.infoData_.getNode(query._path);
11403 // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
11404 // on initial data...
11405 if (!node.isEmpty()) {
11406 infoEvents = syncTreeApplyServerOverwrite(repo.infoSyncTree_, query._path, node);
11407 setTimeout(function () {
11408 onComplete('ok');
11409 }, 0);
11410 }
11411 return infoEvents;
11412 },
11413 stopListening: function () { }
11414 });
11415 repoUpdateInfo(repo, 'connected', false);
11416 repo.serverSyncTree_ = new SyncTree({
11417 startListening: function (query, tag, currentHashFn, onComplete) {
11418 repo.server_.listen(query, currentHashFn, tag, function (status, data) {
11419 var events = onComplete(status, data);
11420 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11421 });
11422 // No synchronous events for network-backed sync trees
11423 return [];
11424 },
11425 stopListening: function (query, tag) {
11426 repo.server_.unlisten(query, tag);
11427 }
11428 });
11429}
11430/**
11431 * @returns The time in milliseconds, taking the server offset into account if we have one.
11432 */
11433function repoServerTime(repo) {
11434 var offsetNode = repo.infoData_.getNode(new Path('.info/serverTimeOffset'));
11435 var offset = offsetNode.val() || 0;
11436 return new Date().getTime() + offset;
11437}
11438/**
11439 * Generate ServerValues using some variables from the repo object.
11440 */
11441function repoGenerateServerValues(repo) {
11442 return generateWithValues({
11443 timestamp: repoServerTime(repo)
11444 });
11445}
11446/**
11447 * Called by realtime when we get new messages from the server.
11448 */
11449function repoOnDataUpdate(repo, pathString, data, isMerge, tag) {
11450 // For testing.
11451 repo.dataUpdateCount++;
11452 var path = new Path(pathString);
11453 data = repo.interceptServerDataCallback_
11454 ? repo.interceptServerDataCallback_(pathString, data)
11455 : data;
11456 var events = [];
11457 if (tag) {
11458 if (isMerge) {
11459 var taggedChildren = util.map(data, function (raw) { return nodeFromJSON$1(raw); });
11460 events = syncTreeApplyTaggedQueryMerge(repo.serverSyncTree_, path, taggedChildren, tag);
11461 }
11462 else {
11463 var taggedSnap = nodeFromJSON$1(data);
11464 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, path, taggedSnap, tag);
11465 }
11466 }
11467 else if (isMerge) {
11468 var changedChildren = util.map(data, function (raw) { return nodeFromJSON$1(raw); });
11469 events = syncTreeApplyServerMerge(repo.serverSyncTree_, path, changedChildren);
11470 }
11471 else {
11472 var snap = nodeFromJSON$1(data);
11473 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap);
11474 }
11475 var affectedPath = path;
11476 if (events.length > 0) {
11477 // Since we have a listener outstanding for each transaction, receiving any events
11478 // is a proxy for some change having occurred.
11479 affectedPath = repoRerunTransactions(repo, path);
11480 }
11481 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, events);
11482}
11483// TODO: This should be @private but it's used by test_access.js and internal.js
11484function repoInterceptServerData(repo, callback) {
11485 repo.interceptServerDataCallback_ = callback;
11486}
11487function repoOnConnectStatus(repo, connectStatus) {
11488 repoUpdateInfo(repo, 'connected', connectStatus);
11489 if (connectStatus === false) {
11490 repoRunOnDisconnectEvents(repo);
11491 }
11492}
11493function repoOnServerInfoUpdate(repo, updates) {
11494 each(updates, function (key, value) {
11495 repoUpdateInfo(repo, key, value);
11496 });
11497}
11498function repoUpdateInfo(repo, pathString, value) {
11499 var path = new Path('/.info/' + pathString);
11500 var newNode = nodeFromJSON$1(value);
11501 repo.infoData_.updateSnapshot(path, newNode);
11502 var events = syncTreeApplyServerOverwrite(repo.infoSyncTree_, path, newNode);
11503 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11504}
11505function repoGetNextWriteId(repo) {
11506 return repo.nextWriteId_++;
11507}
11508/**
11509 * The purpose of `getValue` is to return the latest known value
11510 * satisfying `query`.
11511 *
11512 * This method will first check for in-memory cached values
11513 * belonging to active listeners. If they are found, such values
11514 * are considered to be the most up-to-date.
11515 *
11516 * If the client is not connected, this method will try to
11517 * establish a connection and request the value for `query`. If
11518 * the client is not able to retrieve the query result, it reports
11519 * an error.
11520 *
11521 * @param query - The query to surface a value for.
11522 */
11523function repoGetValue(repo, query) {
11524 // Only active queries are cached. There is no persisted cache.
11525 var cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11526 if (cached != null) {
11527 return Promise.resolve(cached);
11528 }
11529 return repo.server_.get(query).then(function (payload) {
11530 var node = nodeFromJSON$1(payload).withIndex(query._queryParams.getIndex());
11531 var events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11532 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11533 return Promise.resolve(node);
11534 }, function (err) {
11535 repoLog(repo, 'get for query ' + util.stringify(query) + ' failed: ' + err);
11536 return Promise.reject(new Error(err));
11537 });
11538}
11539function repoSetWithPriority(repo, path, newVal, newPriority, onComplete) {
11540 repoLog(repo, 'set', {
11541 path: path.toString(),
11542 value: newVal,
11543 priority: newPriority
11544 });
11545 // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
11546 // (b) store unresolved paths on JSON parse
11547 var serverValues = repoGenerateServerValues(repo);
11548 var newNodeUnresolved = nodeFromJSON$1(newVal, newPriority);
11549 var existing = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path);
11550 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, existing, serverValues);
11551 var writeId = repoGetNextWriteId(repo);
11552 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, writeId, true);
11553 eventQueueQueueEvents(repo.eventQueue_, events);
11554 repo.server_.put(path.toString(), newNodeUnresolved.val(/*export=*/ true), function (status, errorReason) {
11555 var success = status === 'ok';
11556 if (!success) {
11557 warn('set at ' + path + ' failed: ' + status);
11558 }
11559 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId, !success);
11560 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, clearEvents);
11561 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11562 });
11563 var affectedPath = repoAbortTransactions(repo, path);
11564 repoRerunTransactions(repo, affectedPath);
11565 // We queued the events above, so just flush the queue here
11566 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, []);
11567}
11568function repoUpdate(repo, path, childrenToMerge, onComplete) {
11569 repoLog(repo, 'update', { path: path.toString(), value: childrenToMerge });
11570 // Start with our existing data and merge each child into it.
11571 var empty = true;
11572 var serverValues = repoGenerateServerValues(repo);
11573 var changedChildren = {};
11574 each(childrenToMerge, function (changedKey, changedValue) {
11575 empty = false;
11576 changedChildren[changedKey] = resolveDeferredValueTree(pathChild(path, changedKey), nodeFromJSON$1(changedValue), repo.serverSyncTree_, serverValues);
11577 });
11578 if (!empty) {
11579 var writeId_1 = repoGetNextWriteId(repo);
11580 var events = syncTreeApplyUserMerge(repo.serverSyncTree_, path, changedChildren, writeId_1);
11581 eventQueueQueueEvents(repo.eventQueue_, events);
11582 repo.server_.merge(path.toString(), childrenToMerge, function (status, errorReason) {
11583 var success = status === 'ok';
11584 if (!success) {
11585 warn('update at ' + path + ' failed: ' + status);
11586 }
11587 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId_1, !success);
11588 var affectedPath = clearEvents.length > 0 ? repoRerunTransactions(repo, path) : path;
11589 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, clearEvents);
11590 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11591 });
11592 each(childrenToMerge, function (changedPath) {
11593 var affectedPath = repoAbortTransactions(repo, pathChild(path, changedPath));
11594 repoRerunTransactions(repo, affectedPath);
11595 });
11596 // We queued the events above, so just flush the queue here
11597 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, []);
11598 }
11599 else {
11600 log("update() called with empty data. Don't do anything.");
11601 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11602 }
11603}
11604/**
11605 * Applies all of the changes stored up in the onDisconnect_ tree.
11606 */
11607function repoRunOnDisconnectEvents(repo) {
11608 repoLog(repo, 'onDisconnectEvents');
11609 var serverValues = repoGenerateServerValues(repo);
11610 var resolvedOnDisconnectTree = newSparseSnapshotTree();
11611 sparseSnapshotTreeForEachTree(repo.onDisconnect_, newEmptyPath(), function (path, node) {
11612 var resolved = resolveDeferredValueTree(path, node, repo.serverSyncTree_, serverValues);
11613 sparseSnapshotTreeRemember(resolvedOnDisconnectTree, path, resolved);
11614 });
11615 var events = [];
11616 sparseSnapshotTreeForEachTree(resolvedOnDisconnectTree, newEmptyPath(), function (path, snap) {
11617 events = events.concat(syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap));
11618 var affectedPath = repoAbortTransactions(repo, path);
11619 repoRerunTransactions(repo, affectedPath);
11620 });
11621 repo.onDisconnect_ = newSparseSnapshotTree();
11622 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, newEmptyPath(), events);
11623}
11624function repoOnDisconnectCancel(repo, path, onComplete) {
11625 repo.server_.onDisconnectCancel(path.toString(), function (status, errorReason) {
11626 if (status === 'ok') {
11627 sparseSnapshotTreeForget(repo.onDisconnect_, path);
11628 }
11629 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11630 });
11631}
11632function repoOnDisconnectSet(repo, path, value, onComplete) {
11633 var newNode = nodeFromJSON$1(value);
11634 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11635 if (status === 'ok') {
11636 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11637 }
11638 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11639 });
11640}
11641function repoOnDisconnectSetWithPriority(repo, path, value, priority, onComplete) {
11642 var newNode = nodeFromJSON$1(value, priority);
11643 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11644 if (status === 'ok') {
11645 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11646 }
11647 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11648 });
11649}
11650function repoOnDisconnectUpdate(repo, path, childrenToMerge, onComplete) {
11651 if (util.isEmpty(childrenToMerge)) {
11652 log("onDisconnect().update() called with empty data. Don't do anything.");
11653 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11654 return;
11655 }
11656 repo.server_.onDisconnectMerge(path.toString(), childrenToMerge, function (status, errorReason) {
11657 if (status === 'ok') {
11658 each(childrenToMerge, function (childName, childNode) {
11659 var newChildNode = nodeFromJSON$1(childNode);
11660 sparseSnapshotTreeRemember(repo.onDisconnect_, pathChild(path, childName), newChildNode);
11661 });
11662 }
11663 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11664 });
11665}
11666function repoAddEventCallbackForQuery(repo, query, eventRegistration) {
11667 var events;
11668 if (pathGetFront(query._path) === '.info') {
11669 events = syncTreeAddEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11670 }
11671 else {
11672 events = syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11673 }
11674 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11675}
11676function repoRemoveEventCallbackForQuery(repo, query, eventRegistration) {
11677 // These are guaranteed not to raise events, since we're not passing in a cancelError. However, we can future-proof
11678 // a little bit by handling the return values anyways.
11679 var events;
11680 if (pathGetFront(query._path) === '.info') {
11681 events = syncTreeRemoveEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11682 }
11683 else {
11684 events = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11685 }
11686 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11687}
11688function repoInterrupt(repo) {
11689 if (repo.persistentConnection_) {
11690 repo.persistentConnection_.interrupt(INTERRUPT_REASON);
11691 }
11692}
11693function repoResume(repo) {
11694 if (repo.persistentConnection_) {
11695 repo.persistentConnection_.resume(INTERRUPT_REASON);
11696 }
11697}
11698function repoStats(repo, showDelta) {
11699 if (showDelta === void 0) { showDelta = false; }
11700 if (typeof console === 'undefined') {
11701 return;
11702 }
11703 var stats;
11704 if (showDelta) {
11705 if (!repo.statsListener_) {
11706 repo.statsListener_ = new StatsListener(repo.stats_);
11707 }
11708 stats = repo.statsListener_.get();
11709 }
11710 else {
11711 stats = repo.stats_.get();
11712 }
11713 var longestName = Object.keys(stats).reduce(function (previousValue, currentValue) {
11714 return Math.max(currentValue.length, previousValue);
11715 }, 0);
11716 each(stats, function (stat, value) {
11717 var paddedStat = stat;
11718 // pad stat names to be the same length (plus 2 extra spaces).
11719 for (var i = stat.length; i < longestName + 2; i++) {
11720 paddedStat += ' ';
11721 }
11722 console.log(paddedStat + value);
11723 });
11724}
11725function repoStatsIncrementCounter(repo, metric) {
11726 repo.stats_.incrementCounter(metric);
11727 statsReporterIncludeStat(repo.statsReporter_, metric);
11728}
11729function repoLog(repo) {
11730 var varArgs = [];
11731 for (var _i = 1; _i < arguments.length; _i++) {
11732 varArgs[_i - 1] = arguments[_i];
11733 }
11734 var prefix = '';
11735 if (repo.persistentConnection_) {
11736 prefix = repo.persistentConnection_.id + ':';
11737 }
11738 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
11739}
11740function repoCallOnCompleteCallback(repo, callback, status, errorReason) {
11741 if (callback) {
11742 exceptionGuard(function () {
11743 if (status === 'ok') {
11744 callback(null);
11745 }
11746 else {
11747 var code = (status || 'error').toUpperCase();
11748 var message = code;
11749 if (errorReason) {
11750 message += ': ' + errorReason;
11751 }
11752 var error = new Error(message);
11753 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11754 error.code = code;
11755 callback(error);
11756 }
11757 });
11758 }
11759}
11760/**
11761 * Creates a new transaction, adds it to the transactions we're tracking, and
11762 * sends it to the server if possible.
11763 *
11764 * @param path - Path at which to do transaction.
11765 * @param transactionUpdate - Update callback.
11766 * @param onComplete - Completion callback.
11767 * @param unwatcher - Function that will be called when the transaction no longer
11768 * need data updates for `path`.
11769 * @param applyLocally - Whether or not to make intermediate results visible
11770 */
11771function repoStartTransaction(repo, path, transactionUpdate, onComplete, unwatcher, applyLocally) {
11772 repoLog(repo, 'transaction on ' + path);
11773 // Initialize transaction.
11774 var transaction = {
11775 path: path,
11776 update: transactionUpdate,
11777 onComplete: onComplete,
11778 // One of TransactionStatus enums.
11779 status: null,
11780 // Used when combining transactions at different locations to figure out
11781 // which one goes first.
11782 order: LUIDGenerator(),
11783 // Whether to raise local events for this transaction.
11784 applyLocally: applyLocally,
11785 // Count of how many times we've retried the transaction.
11786 retryCount: 0,
11787 // Function to call to clean up our .on() listener.
11788 unwatcher: unwatcher,
11789 // Stores why a transaction was aborted.
11790 abortReason: null,
11791 currentWriteId: null,
11792 currentInputSnapshot: null,
11793 currentOutputSnapshotRaw: null,
11794 currentOutputSnapshotResolved: null
11795 };
11796 // Run transaction initially.
11797 var currentState = repoGetLatestState(repo, path, undefined);
11798 transaction.currentInputSnapshot = currentState;
11799 var newVal = transaction.update(currentState.val());
11800 if (newVal === undefined) {
11801 // Abort transaction.
11802 transaction.unwatcher();
11803 transaction.currentOutputSnapshotRaw = null;
11804 transaction.currentOutputSnapshotResolved = null;
11805 if (transaction.onComplete) {
11806 transaction.onComplete(null, false, transaction.currentInputSnapshot);
11807 }
11808 }
11809 else {
11810 validateFirebaseData('transaction failed: Data returned ', newVal, transaction.path);
11811 // Mark as run and add to our queue.
11812 transaction.status = 0 /* RUN */;
11813 var queueNode = treeSubTree(repo.transactionQueueTree_, path);
11814 var nodeQueue = treeGetValue(queueNode) || [];
11815 nodeQueue.push(transaction);
11816 treeSetValue(queueNode, nodeQueue);
11817 // Update visibleData and raise events
11818 // Note: We intentionally raise events after updating all of our
11819 // transaction state, since the user could start new transactions from the
11820 // event callbacks.
11821 var priorityForNode = void 0;
11822 if (typeof newVal === 'object' &&
11823 newVal !== null &&
11824 util.contains(newVal, '.priority')) {
11825 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11826 priorityForNode = util.safeGet(newVal, '.priority');
11827 util.assert(isValidPriority(priorityForNode), 'Invalid priority returned by transaction. ' +
11828 'Priority must be a valid string, finite number, server value, or null.');
11829 }
11830 else {
11831 var currentNode = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path) ||
11832 ChildrenNode.EMPTY_NODE;
11833 priorityForNode = currentNode.getPriority().val();
11834 }
11835 var serverValues = repoGenerateServerValues(repo);
11836 var newNodeUnresolved = nodeFromJSON$1(newVal, priorityForNode);
11837 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, currentState, serverValues);
11838 transaction.currentOutputSnapshotRaw = newNodeUnresolved;
11839 transaction.currentOutputSnapshotResolved = newNode;
11840 transaction.currentWriteId = repoGetNextWriteId(repo);
11841 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, transaction.currentWriteId, transaction.applyLocally);
11842 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11843 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11844 }
11845}
11846/**
11847 * @param excludeSets - A specific set to exclude
11848 */
11849function repoGetLatestState(repo, path, excludeSets) {
11850 return (syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path, excludeSets) ||
11851 ChildrenNode.EMPTY_NODE);
11852}
11853/**
11854 * Sends any already-run transactions that aren't waiting for outstanding
11855 * transactions to complete.
11856 *
11857 * Externally it's called with no arguments, but it calls itself recursively
11858 * with a particular transactionQueueTree node to recurse through the tree.
11859 *
11860 * @param node - transactionQueueTree node to start at.
11861 */
11862function repoSendReadyTransactions(repo, node) {
11863 if (node === void 0) { node = repo.transactionQueueTree_; }
11864 // Before recursing, make sure any completed transactions are removed.
11865 if (!node) {
11866 repoPruneCompletedTransactionsBelowNode(repo, node);
11867 }
11868 if (treeGetValue(node)) {
11869 var queue = repoBuildTransactionQueue(repo, node);
11870 util.assert(queue.length > 0, 'Sending zero length transaction queue');
11871 var allRun = queue.every(function (transaction) { return transaction.status === 0 /* RUN */; });
11872 // If they're all run (and not sent), we can send them. Else, we must wait.
11873 if (allRun) {
11874 repoSendTransactionQueue(repo, treeGetPath(node), queue);
11875 }
11876 }
11877 else if (treeHasChildren(node)) {
11878 treeForEachChild(node, function (childNode) {
11879 repoSendReadyTransactions(repo, childNode);
11880 });
11881 }
11882}
11883/**
11884 * Given a list of run transactions, send them to the server and then handle
11885 * the result (success or failure).
11886 *
11887 * @param path - The location of the queue.
11888 * @param queue - Queue of transactions under the specified location.
11889 */
11890function repoSendTransactionQueue(repo, path, queue) {
11891 // Mark transactions as sent and increment retry count!
11892 var setsToIgnore = queue.map(function (txn) {
11893 return txn.currentWriteId;
11894 });
11895 var latestState = repoGetLatestState(repo, path, setsToIgnore);
11896 var snapToSend = latestState;
11897 var latestHash = latestState.hash();
11898 for (var i = 0; i < queue.length; i++) {
11899 var txn = queue[i];
11900 util.assert(txn.status === 0 /* RUN */, 'tryToSendTransactionQueue_: items in queue should all be run.');
11901 txn.status = 1 /* SENT */;
11902 txn.retryCount++;
11903 var relativePath = newRelativePath(path, txn.path);
11904 // If we've gotten to this point, the output snapshot must be defined.
11905 snapToSend = snapToSend.updateChild(relativePath /** @type {!Node} */, txn.currentOutputSnapshotRaw);
11906 }
11907 var dataToSend = snapToSend.val(true);
11908 var pathToSend = path;
11909 // Send the put.
11910 repo.server_.put(pathToSend.toString(), dataToSend, function (status) {
11911 repoLog(repo, 'transaction put response', {
11912 path: pathToSend.toString(),
11913 status: status
11914 });
11915 var events = [];
11916 if (status === 'ok') {
11917 // Queue up the callbacks and fire them after cleaning up all of our
11918 // transaction state, since the callback could trigger more
11919 // transactions or sets.
11920 var callbacks = [];
11921 var _loop_1 = function (i) {
11922 queue[i].status = 2 /* COMPLETED */;
11923 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId));
11924 if (queue[i].onComplete) {
11925 // We never unset the output snapshot, and given that this
11926 // transaction is complete, it should be set
11927 callbacks.push(function () {
11928 return queue[i].onComplete(null, true, queue[i].currentOutputSnapshotResolved);
11929 });
11930 }
11931 queue[i].unwatcher();
11932 };
11933 for (var i = 0; i < queue.length; i++) {
11934 _loop_1(i);
11935 }
11936 // Now remove the completed transactions.
11937 repoPruneCompletedTransactionsBelowNode(repo, treeSubTree(repo.transactionQueueTree_, path));
11938 // There may be pending transactions that we can now send.
11939 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11940 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11941 // Finally, trigger onComplete callbacks.
11942 for (var i = 0; i < callbacks.length; i++) {
11943 exceptionGuard(callbacks[i]);
11944 }
11945 }
11946 else {
11947 // transactions are no longer sent. Update their status appropriately.
11948 if (status === 'datastale') {
11949 for (var i = 0; i < queue.length; i++) {
11950 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) {
11951 queue[i].status = 4 /* NEEDS_ABORT */;
11952 }
11953 else {
11954 queue[i].status = 0 /* RUN */;
11955 }
11956 }
11957 }
11958 else {
11959 warn('transaction at ' + pathToSend.toString() + ' failed: ' + status);
11960 for (var i = 0; i < queue.length; i++) {
11961 queue[i].status = 4 /* NEEDS_ABORT */;
11962 queue[i].abortReason = status;
11963 }
11964 }
11965 repoRerunTransactions(repo, path);
11966 }
11967 }, latestHash);
11968}
11969/**
11970 * Finds all transactions dependent on the data at changedPath and reruns them.
11971 *
11972 * Should be called any time cached data changes.
11973 *
11974 * Return the highest path that was affected by rerunning transactions. This
11975 * is the path at which events need to be raised for.
11976 *
11977 * @param changedPath - The path in mergedData that changed.
11978 * @returns The rootmost path that was affected by rerunning transactions.
11979 */
11980function repoRerunTransactions(repo, changedPath) {
11981 var rootMostTransactionNode = repoGetAncestorTransactionNode(repo, changedPath);
11982 var path = treeGetPath(rootMostTransactionNode);
11983 var queue = repoBuildTransactionQueue(repo, rootMostTransactionNode);
11984 repoRerunTransactionQueue(repo, queue, path);
11985 return path;
11986}
11987/**
11988 * Does all the work of rerunning transactions (as well as cleans up aborted
11989 * transactions and whatnot).
11990 *
11991 * @param queue - The queue of transactions to run.
11992 * @param path - The path the queue is for.
11993 */
11994function repoRerunTransactionQueue(repo, queue, path) {
11995 if (queue.length === 0) {
11996 return; // Nothing to do!
11997 }
11998 // Queue up the callbacks and fire them after cleaning up all of our
11999 // transaction state, since the callback could trigger more transactions or
12000 // sets.
12001 var callbacks = [];
12002 var events = [];
12003 // Ignore all of the sets we're going to re-run.
12004 var txnsToRerun = queue.filter(function (q) {
12005 return q.status === 0 /* RUN */;
12006 });
12007 var setsToIgnore = txnsToRerun.map(function (q) {
12008 return q.currentWriteId;
12009 });
12010 var _loop_2 = function (i) {
12011 var transaction = queue[i];
12012 var relativePath = newRelativePath(path, transaction.path);
12013 var abortTransaction = false, abortReason;
12014 util.assert(relativePath !== null, 'rerunTransactionsUnderNode_: relativePath should not be null.');
12015 if (transaction.status === 4 /* NEEDS_ABORT */) {
12016 abortTransaction = true;
12017 abortReason = transaction.abortReason;
12018 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12019 }
12020 else if (transaction.status === 0 /* RUN */) {
12021 if (transaction.retryCount >= MAX_TRANSACTION_RETRIES) {
12022 abortTransaction = true;
12023 abortReason = 'maxretry';
12024 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12025 }
12026 else {
12027 // This code reruns a transaction
12028 var currentNode = repoGetLatestState(repo, transaction.path, setsToIgnore);
12029 transaction.currentInputSnapshot = currentNode;
12030 var newData = queue[i].update(currentNode.val());
12031 if (newData !== undefined) {
12032 validateFirebaseData('transaction failed: Data returned ', newData, transaction.path);
12033 var newDataNode = nodeFromJSON$1(newData);
12034 var hasExplicitPriority = typeof newData === 'object' &&
12035 newData != null &&
12036 util.contains(newData, '.priority');
12037 if (!hasExplicitPriority) {
12038 // Keep the old priority if there wasn't a priority explicitly specified.
12039 newDataNode = newDataNode.updatePriority(currentNode.getPriority());
12040 }
12041 var oldWriteId = transaction.currentWriteId;
12042 var serverValues = repoGenerateServerValues(repo);
12043 var newNodeResolved = resolveDeferredValueSnapshot(newDataNode, currentNode, serverValues);
12044 transaction.currentOutputSnapshotRaw = newDataNode;
12045 transaction.currentOutputSnapshotResolved = newNodeResolved;
12046 transaction.currentWriteId = repoGetNextWriteId(repo);
12047 // Mutates setsToIgnore in place
12048 setsToIgnore.splice(setsToIgnore.indexOf(oldWriteId), 1);
12049 events = events.concat(syncTreeApplyUserOverwrite(repo.serverSyncTree_, transaction.path, newNodeResolved, transaction.currentWriteId, transaction.applyLocally));
12050 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, oldWriteId, true));
12051 }
12052 else {
12053 abortTransaction = true;
12054 abortReason = 'nodata';
12055 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12056 }
12057 }
12058 }
12059 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
12060 events = [];
12061 if (abortTransaction) {
12062 // Abort.
12063 queue[i].status = 2 /* COMPLETED */;
12064 // Removing a listener can trigger pruning which can muck with
12065 // mergedData/visibleData (as it prunes data). So defer the unwatcher
12066 // until we're done.
12067 (function (unwatcher) {
12068 setTimeout(unwatcher, Math.floor(0));
12069 })(queue[i].unwatcher);
12070 if (queue[i].onComplete) {
12071 if (abortReason === 'nodata') {
12072 callbacks.push(function () {
12073 return queue[i].onComplete(null, false, queue[i].currentInputSnapshot);
12074 });
12075 }
12076 else {
12077 callbacks.push(function () {
12078 return queue[i].onComplete(new Error(abortReason), false, null);
12079 });
12080 }
12081 }
12082 }
12083 };
12084 for (var i = 0; i < queue.length; i++) {
12085 _loop_2(i);
12086 }
12087 // Clean up completed transactions.
12088 repoPruneCompletedTransactionsBelowNode(repo, repo.transactionQueueTree_);
12089 // Now fire callbacks, now that we're in a good, known state.
12090 for (var i = 0; i < callbacks.length; i++) {
12091 exceptionGuard(callbacks[i]);
12092 }
12093 // Try to send the transaction result to the server.
12094 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
12095}
12096/**
12097 * Returns the rootmost ancestor node of the specified path that has a pending
12098 * transaction on it, or just returns the node for the given path if there are
12099 * no pending transactions on any ancestor.
12100 *
12101 * @param path - The location to start at.
12102 * @returns The rootmost node with a transaction.
12103 */
12104function repoGetAncestorTransactionNode(repo, path) {
12105 var front;
12106 // Start at the root and walk deeper into the tree towards path until we
12107 // find a node with pending transactions.
12108 var transactionNode = repo.transactionQueueTree_;
12109 front = pathGetFront(path);
12110 while (front !== null && treeGetValue(transactionNode) === undefined) {
12111 transactionNode = treeSubTree(transactionNode, front);
12112 path = pathPopFront(path);
12113 front = pathGetFront(path);
12114 }
12115 return transactionNode;
12116}
12117/**
12118 * Builds the queue of all transactions at or below the specified
12119 * transactionNode.
12120 *
12121 * @param transactionNode
12122 * @returns The generated queue.
12123 */
12124function repoBuildTransactionQueue(repo, transactionNode) {
12125 // Walk any child transaction queues and aggregate them into a single queue.
12126 var transactionQueue = [];
12127 repoAggregateTransactionQueuesForNode(repo, transactionNode, transactionQueue);
12128 // Sort them by the order the transactions were created.
12129 transactionQueue.sort(function (a, b) { return a.order - b.order; });
12130 return transactionQueue;
12131}
12132function repoAggregateTransactionQueuesForNode(repo, node, queue) {
12133 var nodeQueue = treeGetValue(node);
12134 if (nodeQueue) {
12135 for (var i = 0; i < nodeQueue.length; i++) {
12136 queue.push(nodeQueue[i]);
12137 }
12138 }
12139 treeForEachChild(node, function (child) {
12140 repoAggregateTransactionQueuesForNode(repo, child, queue);
12141 });
12142}
12143/**
12144 * Remove COMPLETED transactions at or below this node in the transactionQueueTree_.
12145 */
12146function repoPruneCompletedTransactionsBelowNode(repo, node) {
12147 var queue = treeGetValue(node);
12148 if (queue) {
12149 var to = 0;
12150 for (var from = 0; from < queue.length; from++) {
12151 if (queue[from].status !== 2 /* COMPLETED */) {
12152 queue[to] = queue[from];
12153 to++;
12154 }
12155 }
12156 queue.length = to;
12157 treeSetValue(node, queue.length > 0 ? queue : undefined);
12158 }
12159 treeForEachChild(node, function (childNode) {
12160 repoPruneCompletedTransactionsBelowNode(repo, childNode);
12161 });
12162}
12163/**
12164 * Aborts all transactions on ancestors or descendants of the specified path.
12165 * Called when doing a set() or update() since we consider them incompatible
12166 * with transactions.
12167 *
12168 * @param path - Path for which we want to abort related transactions.
12169 */
12170function repoAbortTransactions(repo, path) {
12171 var affectedPath = treeGetPath(repoGetAncestorTransactionNode(repo, path));
12172 var transactionNode = treeSubTree(repo.transactionQueueTree_, path);
12173 treeForEachAncestor(transactionNode, function (node) {
12174 repoAbortTransactionsOnNode(repo, node);
12175 });
12176 repoAbortTransactionsOnNode(repo, transactionNode);
12177 treeForEachDescendant(transactionNode, function (node) {
12178 repoAbortTransactionsOnNode(repo, node);
12179 });
12180 return affectedPath;
12181}
12182/**
12183 * Abort transactions stored in this transaction queue node.
12184 *
12185 * @param node - Node to abort transactions for.
12186 */
12187function repoAbortTransactionsOnNode(repo, node) {
12188 var queue = treeGetValue(node);
12189 if (queue) {
12190 // Queue up the callbacks and fire them after cleaning up all of our
12191 // transaction state, since the callback could trigger more transactions
12192 // or sets.
12193 var callbacks = [];
12194 // Go through queue. Any already-sent transactions must be marked for
12195 // abort, while the unsent ones can be immediately aborted and removed.
12196 var events = [];
12197 var lastSent = -1;
12198 for (var i = 0; i < queue.length; i++) {
12199 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) ;
12200 else if (queue[i].status === 1 /* SENT */) {
12201 util.assert(lastSent === i - 1, 'All SENT items should be at beginning of queue.');
12202 lastSent = i;
12203 // Mark transaction for abort when it comes back.
12204 queue[i].status = 3 /* SENT_NEEDS_ABORT */;
12205 queue[i].abortReason = 'set';
12206 }
12207 else {
12208 util.assert(queue[i].status === 0 /* RUN */, 'Unexpected transaction status in abort');
12209 // We can abort it immediately.
12210 queue[i].unwatcher();
12211 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId, true));
12212 if (queue[i].onComplete) {
12213 callbacks.push(queue[i].onComplete.bind(null, new Error('set'), false, null));
12214 }
12215 }
12216 }
12217 if (lastSent === -1) {
12218 // We're not waiting for any sent transactions. We can clear the queue.
12219 treeSetValue(node, undefined);
12220 }
12221 else {
12222 // Remove the transactions we aborted.
12223 queue.length = lastSent + 1;
12224 }
12225 // Now fire the callbacks.
12226 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, treeGetPath(node), events);
12227 for (var i = 0; i < callbacks.length; i++) {
12228 exceptionGuard(callbacks[i]);
12229 }
12230 }
12231}
12232
12233/**
12234 * @license
12235 * Copyright 2017 Google LLC
12236 *
12237 * Licensed under the Apache License, Version 2.0 (the "License");
12238 * you may not use this file except in compliance with the License.
12239 * You may obtain a copy of the License at
12240 *
12241 * http://www.apache.org/licenses/LICENSE-2.0
12242 *
12243 * Unless required by applicable law or agreed to in writing, software
12244 * distributed under the License is distributed on an "AS IS" BASIS,
12245 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12246 * See the License for the specific language governing permissions and
12247 * limitations under the License.
12248 */
12249function decodePath(pathString) {
12250 var pathStringDecoded = '';
12251 var pieces = pathString.split('/');
12252 for (var i = 0; i < pieces.length; i++) {
12253 if (pieces[i].length > 0) {
12254 var piece = pieces[i];
12255 try {
12256 piece = decodeURIComponent(piece.replace(/\+/g, ' '));
12257 }
12258 catch (e) { }
12259 pathStringDecoded += '/' + piece;
12260 }
12261 }
12262 return pathStringDecoded;
12263}
12264/**
12265 * @returns key value hash
12266 */
12267function decodeQuery(queryString) {
12268 var e_1, _a;
12269 var results = {};
12270 if (queryString.charAt(0) === '?') {
12271 queryString = queryString.substring(1);
12272 }
12273 try {
12274 for (var _b = tslib.__values(queryString.split('&')), _c = _b.next(); !_c.done; _c = _b.next()) {
12275 var segment = _c.value;
12276 if (segment.length === 0) {
12277 continue;
12278 }
12279 var kv = segment.split('=');
12280 if (kv.length === 2) {
12281 results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
12282 }
12283 else {
12284 warn("Invalid query segment '" + segment + "' in query '" + queryString + "'");
12285 }
12286 }
12287 }
12288 catch (e_1_1) { e_1 = { error: e_1_1 }; }
12289 finally {
12290 try {
12291 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
12292 }
12293 finally { if (e_1) throw e_1.error; }
12294 }
12295 return results;
12296}
12297var parseRepoInfo = function (dataURL, nodeAdmin) {
12298 var parsedUrl = parseDatabaseURL(dataURL), namespace = parsedUrl.namespace;
12299 if (parsedUrl.domain === 'firebase.com') {
12300 fatal(parsedUrl.host +
12301 ' is no longer supported. ' +
12302 'Please use <YOUR FIREBASE>.firebaseio.com instead');
12303 }
12304 // Catch common error of uninitialized namespace value.
12305 if ((!namespace || namespace === 'undefined') &&
12306 parsedUrl.domain !== 'localhost') {
12307 fatal('Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com');
12308 }
12309 if (!parsedUrl.secure) {
12310 warnIfPageIsSecure();
12311 }
12312 var webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
12313 return {
12314 repoInfo: new RepoInfo(parsedUrl.host, parsedUrl.secure, namespace, nodeAdmin, webSocketOnly,
12315 /*persistenceKey=*/ '',
12316 /*includeNamespaceInQueryParams=*/ namespace !== parsedUrl.subdomain),
12317 path: new Path(parsedUrl.pathString)
12318 };
12319};
12320var parseDatabaseURL = function (dataURL) {
12321 // Default to empty strings in the event of a malformed string.
12322 var host = '', domain = '', subdomain = '', pathString = '', namespace = '';
12323 // Always default to SSL, unless otherwise specified.
12324 var secure = true, scheme = 'https', port = 443;
12325 // Don't do any validation here. The caller is responsible for validating the result of parsing.
12326 if (typeof dataURL === 'string') {
12327 // Parse scheme.
12328 var colonInd = dataURL.indexOf('//');
12329 if (colonInd >= 0) {
12330 scheme = dataURL.substring(0, colonInd - 1);
12331 dataURL = dataURL.substring(colonInd + 2);
12332 }
12333 // Parse host, path, and query string.
12334 var slashInd = dataURL.indexOf('/');
12335 if (slashInd === -1) {
12336 slashInd = dataURL.length;
12337 }
12338 var questionMarkInd = dataURL.indexOf('?');
12339 if (questionMarkInd === -1) {
12340 questionMarkInd = dataURL.length;
12341 }
12342 host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
12343 if (slashInd < questionMarkInd) {
12344 // For pathString, questionMarkInd will always come after slashInd
12345 pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
12346 }
12347 var queryParams = decodeQuery(dataURL.substring(Math.min(dataURL.length, questionMarkInd)));
12348 // If we have a port, use scheme for determining if it's secure.
12349 colonInd = host.indexOf(':');
12350 if (colonInd >= 0) {
12351 secure = scheme === 'https' || scheme === 'wss';
12352 port = parseInt(host.substring(colonInd + 1), 10);
12353 }
12354 else {
12355 colonInd = host.length;
12356 }
12357 var hostWithoutPort = host.slice(0, colonInd);
12358 if (hostWithoutPort.toLowerCase() === 'localhost') {
12359 domain = 'localhost';
12360 }
12361 else if (hostWithoutPort.split('.').length <= 2) {
12362 domain = hostWithoutPort;
12363 }
12364 else {
12365 // Interpret the subdomain of a 3 or more component URL as the namespace name.
12366 var dotInd = host.indexOf('.');
12367 subdomain = host.substring(0, dotInd).toLowerCase();
12368 domain = host.substring(dotInd + 1);
12369 // Normalize namespaces to lowercase to share storage / connection.
12370 namespace = subdomain;
12371 }
12372 // Always treat the value of the `ns` as the namespace name if it is present.
12373 if ('ns' in queryParams) {
12374 namespace = queryParams['ns'];
12375 }
12376 }
12377 return {
12378 host: host,
12379 port: port,
12380 domain: domain,
12381 subdomain: subdomain,
12382 secure: secure,
12383 scheme: scheme,
12384 pathString: pathString,
12385 namespace: namespace
12386 };
12387};
12388
12389/**
12390 * @license
12391 * Copyright 2017 Google LLC
12392 *
12393 * Licensed under the Apache License, Version 2.0 (the "License");
12394 * you may not use this file except in compliance with the License.
12395 * You may obtain a copy of the License at
12396 *
12397 * http://www.apache.org/licenses/LICENSE-2.0
12398 *
12399 * Unless required by applicable law or agreed to in writing, software
12400 * distributed under the License is distributed on an "AS IS" BASIS,
12401 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12402 * See the License for the specific language governing permissions and
12403 * limitations under the License.
12404 */
12405/**
12406 * Encapsulates the data needed to raise an event
12407 */
12408var DataEvent = /** @class */ (function () {
12409 /**
12410 * @param eventType - One of: value, child_added, child_changed, child_moved, child_removed
12411 * @param eventRegistration - The function to call to with the event data. User provided
12412 * @param snapshot - The data backing the event
12413 * @param prevName - Optional, the name of the previous child for child_* events.
12414 */
12415 function DataEvent(eventType, eventRegistration, snapshot, prevName) {
12416 this.eventType = eventType;
12417 this.eventRegistration = eventRegistration;
12418 this.snapshot = snapshot;
12419 this.prevName = prevName;
12420 }
12421 DataEvent.prototype.getPath = function () {
12422 var ref = this.snapshot.ref;
12423 if (this.eventType === 'value') {
12424 return ref._path;
12425 }
12426 else {
12427 return ref.parent._path;
12428 }
12429 };
12430 DataEvent.prototype.getEventType = function () {
12431 return this.eventType;
12432 };
12433 DataEvent.prototype.getEventRunner = function () {
12434 return this.eventRegistration.getEventRunner(this);
12435 };
12436 DataEvent.prototype.toString = function () {
12437 return (this.getPath().toString() +
12438 ':' +
12439 this.eventType +
12440 ':' +
12441 util.stringify(this.snapshot.exportVal()));
12442 };
12443 return DataEvent;
12444}());
12445var CancelEvent = /** @class */ (function () {
12446 function CancelEvent(eventRegistration, error, path) {
12447 this.eventRegistration = eventRegistration;
12448 this.error = error;
12449 this.path = path;
12450 }
12451 CancelEvent.prototype.getPath = function () {
12452 return this.path;
12453 };
12454 CancelEvent.prototype.getEventType = function () {
12455 return 'cancel';
12456 };
12457 CancelEvent.prototype.getEventRunner = function () {
12458 return this.eventRegistration.getEventRunner(this);
12459 };
12460 CancelEvent.prototype.toString = function () {
12461 return this.path.toString() + ':cancel';
12462 };
12463 return CancelEvent;
12464}());
12465
12466/**
12467 * @license
12468 * Copyright 2017 Google LLC
12469 *
12470 * Licensed under the Apache License, Version 2.0 (the "License");
12471 * you may not use this file except in compliance with the License.
12472 * You may obtain a copy of the License at
12473 *
12474 * http://www.apache.org/licenses/LICENSE-2.0
12475 *
12476 * Unless required by applicable law or agreed to in writing, software
12477 * distributed under the License is distributed on an "AS IS" BASIS,
12478 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12479 * See the License for the specific language governing permissions and
12480 * limitations under the License.
12481 */
12482/**
12483 * A wrapper class that converts events from the database@exp SDK to the legacy
12484 * Database SDK. Events are not converted directly as event registration relies
12485 * on reference comparison of the original user callback (see `matches()`) and
12486 * relies on equality of the legacy SDK's `context` object.
12487 */
12488var CallbackContext = /** @class */ (function () {
12489 function CallbackContext(snapshotCallback, cancelCallback) {
12490 this.snapshotCallback = snapshotCallback;
12491 this.cancelCallback = cancelCallback;
12492 }
12493 CallbackContext.prototype.onValue = function (expDataSnapshot, previousChildName) {
12494 this.snapshotCallback.call(null, expDataSnapshot, previousChildName);
12495 };
12496 CallbackContext.prototype.onCancel = function (error) {
12497 util.assert(this.hasCancelCallback, 'Raising a cancel event on a listener with no cancel callback');
12498 return this.cancelCallback.call(null, error);
12499 };
12500 Object.defineProperty(CallbackContext.prototype, "hasCancelCallback", {
12501 get: function () {
12502 return !!this.cancelCallback;
12503 },
12504 enumerable: false,
12505 configurable: true
12506 });
12507 CallbackContext.prototype.matches = function (other) {
12508 return (this.snapshotCallback === other.snapshotCallback ||
12509 (this.snapshotCallback.userCallback ===
12510 other.snapshotCallback.userCallback &&
12511 this.snapshotCallback.context === other.snapshotCallback.context));
12512 };
12513 return CallbackContext;
12514}());
12515
12516/**
12517 * @license
12518 * Copyright 2021 Google LLC
12519 *
12520 * Licensed under the Apache License, Version 2.0 (the "License");
12521 * you may not use this file except in compliance with the License.
12522 * You may obtain a copy of the License at
12523 *
12524 * http://www.apache.org/licenses/LICENSE-2.0
12525 *
12526 * Unless required by applicable law or agreed to in writing, software
12527 * distributed under the License is distributed on an "AS IS" BASIS,
12528 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12529 * See the License for the specific language governing permissions and
12530 * limitations under the License.
12531 */
12532/**
12533 * The `onDisconnect` class allows you to write or clear data when your client
12534 * disconnects from the Database server. These updates occur whether your
12535 * client disconnects cleanly or not, so you can rely on them to clean up data
12536 * even if a connection is dropped or a client crashes.
12537 *
12538 * The `onDisconnect` class is most commonly used to manage presence in
12539 * applications where it is useful to detect how many clients are connected and
12540 * when other clients disconnect. See
12541 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12542 * for more information.
12543 *
12544 * To avoid problems when a connection is dropped before the requests can be
12545 * transferred to the Database server, these functions should be called before
12546 * writing any data.
12547 *
12548 * Note that `onDisconnect` operations are only triggered once. If you want an
12549 * operation to occur each time a disconnect occurs, you'll need to re-establish
12550 * the `onDisconnect` operations each time you reconnect.
12551 */
12552var OnDisconnect = /** @class */ (function () {
12553 /** @hideconstructor */
12554 function OnDisconnect(_repo, _path) {
12555 this._repo = _repo;
12556 this._path = _path;
12557 }
12558 /**
12559 * Cancels all previously queued `onDisconnect()` set or update events for this
12560 * location and all children.
12561 *
12562 * If a write has been queued for this location via a `set()` or `update()` at a
12563 * parent location, the write at this location will be canceled, though writes
12564 * to sibling locations will still occur.
12565 *
12566 * @returns Resolves when synchronization to the server is complete.
12567 */
12568 OnDisconnect.prototype.cancel = function () {
12569 var deferred = new util.Deferred();
12570 repoOnDisconnectCancel(this._repo, this._path, deferred.wrapCallback(function () { }));
12571 return deferred.promise;
12572 };
12573 /**
12574 * Ensures the data at this location is deleted when the client is disconnected
12575 * (due to closing the browser, navigating to a new page, or network issues).
12576 *
12577 * @returns Resolves when synchronization to the server is complete.
12578 */
12579 OnDisconnect.prototype.remove = function () {
12580 validateWritablePath('OnDisconnect.remove', this._path);
12581 var deferred = new util.Deferred();
12582 repoOnDisconnectSet(this._repo, this._path, null, deferred.wrapCallback(function () { }));
12583 return deferred.promise;
12584 };
12585 /**
12586 * Ensures the data at this location is set to the specified value when the
12587 * client is disconnected (due to closing the browser, navigating to a new page,
12588 * or network issues).
12589 *
12590 * `set()` is especially useful for implementing "presence" systems, where a
12591 * value should be changed or cleared when a user disconnects so that they
12592 * appear "offline" to other users. See
12593 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12594 * for more information.
12595 *
12596 * Note that `onDisconnect` operations are only triggered once. If you want an
12597 * operation to occur each time a disconnect occurs, you'll need to re-establish
12598 * the `onDisconnect` operations each time.
12599 *
12600 * @param value - The value to be written to this location on disconnect (can
12601 * be an object, array, string, number, boolean, or null).
12602 * @returns Resolves when synchronization to the Database is complete.
12603 */
12604 OnDisconnect.prototype.set = function (value) {
12605 validateWritablePath('OnDisconnect.set', this._path);
12606 validateFirebaseDataArg('OnDisconnect.set', value, this._path, false);
12607 var deferred = new util.Deferred();
12608 repoOnDisconnectSet(this._repo, this._path, value, deferred.wrapCallback(function () { }));
12609 return deferred.promise;
12610 };
12611 /**
12612 * Ensures the data at this location is set to the specified value and priority
12613 * when the client is disconnected (due to closing the browser, navigating to a
12614 * new page, or network issues).
12615 *
12616 * @param value - The value to be written to this location on disconnect (can
12617 * be an object, array, string, number, boolean, or null).
12618 * @param priority - The priority to be written (string, number, or null).
12619 * @returns Resolves when synchronization to the Database is complete.
12620 */
12621 OnDisconnect.prototype.setWithPriority = function (value, priority) {
12622 validateWritablePath('OnDisconnect.setWithPriority', this._path);
12623 validateFirebaseDataArg('OnDisconnect.setWithPriority', value, this._path, false);
12624 validatePriority('OnDisconnect.setWithPriority', priority, false);
12625 var deferred = new util.Deferred();
12626 repoOnDisconnectSetWithPriority(this._repo, this._path, value, priority, deferred.wrapCallback(function () { }));
12627 return deferred.promise;
12628 };
12629 /**
12630 * Writes multiple values at this location when the client is disconnected (due
12631 * to closing the browser, navigating to a new page, or network issues).
12632 *
12633 * The `values` argument contains multiple property-value pairs that will be
12634 * written to the Database together. Each child property can either be a simple
12635 * property (for example, "name") or a relative path (for example, "name/first")
12636 * from the current location to the data to update.
12637 *
12638 * As opposed to the `set()` method, `update()` can be use to selectively update
12639 * only the referenced properties at the current location (instead of replacing
12640 * all the child properties at the current location).
12641 *
12642 * @param values - Object containing multiple values.
12643 * @returns Resolves when synchronization to the Database is complete.
12644 */
12645 OnDisconnect.prototype.update = function (values) {
12646 validateWritablePath('OnDisconnect.update', this._path);
12647 validateFirebaseMergeDataArg('OnDisconnect.update', values, this._path, false);
12648 var deferred = new util.Deferred();
12649 repoOnDisconnectUpdate(this._repo, this._path, values, deferred.wrapCallback(function () { }));
12650 return deferred.promise;
12651 };
12652 return OnDisconnect;
12653}());
12654
12655/**
12656 * @license
12657 * Copyright 2020 Google LLC
12658 *
12659 * Licensed under the Apache License, Version 2.0 (the "License");
12660 * you may not use this file except in compliance with the License.
12661 * You may obtain a copy of the License at
12662 *
12663 * http://www.apache.org/licenses/LICENSE-2.0
12664 *
12665 * Unless required by applicable law or agreed to in writing, software
12666 * distributed under the License is distributed on an "AS IS" BASIS,
12667 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12668 * See the License for the specific language governing permissions and
12669 * limitations under the License.
12670 */
12671/**
12672 * @internal
12673 */
12674var QueryImpl = /** @class */ (function () {
12675 /**
12676 * @hideconstructor
12677 */
12678 function QueryImpl(_repo, _path, _queryParams, _orderByCalled) {
12679 this._repo = _repo;
12680 this._path = _path;
12681 this._queryParams = _queryParams;
12682 this._orderByCalled = _orderByCalled;
12683 }
12684 Object.defineProperty(QueryImpl.prototype, "key", {
12685 get: function () {
12686 if (pathIsEmpty(this._path)) {
12687 return null;
12688 }
12689 else {
12690 return pathGetBack(this._path);
12691 }
12692 },
12693 enumerable: false,
12694 configurable: true
12695 });
12696 Object.defineProperty(QueryImpl.prototype, "ref", {
12697 get: function () {
12698 return new ReferenceImpl(this._repo, this._path);
12699 },
12700 enumerable: false,
12701 configurable: true
12702 });
12703 Object.defineProperty(QueryImpl.prototype, "_queryIdentifier", {
12704 get: function () {
12705 var obj = queryParamsGetQueryObject(this._queryParams);
12706 var id = ObjectToUniqueKey(obj);
12707 return id === '{}' ? 'default' : id;
12708 },
12709 enumerable: false,
12710 configurable: true
12711 });
12712 Object.defineProperty(QueryImpl.prototype, "_queryObject", {
12713 /**
12714 * An object representation of the query parameters used by this Query.
12715 */
12716 get: function () {
12717 return queryParamsGetQueryObject(this._queryParams);
12718 },
12719 enumerable: false,
12720 configurable: true
12721 });
12722 QueryImpl.prototype.isEqual = function (other) {
12723 other = util.getModularInstance(other);
12724 if (!(other instanceof QueryImpl)) {
12725 return false;
12726 }
12727 var sameRepo = this._repo === other._repo;
12728 var samePath = pathEquals(this._path, other._path);
12729 var sameQueryIdentifier = this._queryIdentifier === other._queryIdentifier;
12730 return sameRepo && samePath && sameQueryIdentifier;
12731 };
12732 QueryImpl.prototype.toJSON = function () {
12733 return this.toString();
12734 };
12735 QueryImpl.prototype.toString = function () {
12736 return this._repo.toString() + pathToUrlEncodedString(this._path);
12737 };
12738 return QueryImpl;
12739}());
12740/**
12741 * Validates that no other order by call has been made
12742 */
12743function validateNoPreviousOrderByCall(query, fnName) {
12744 if (query._orderByCalled === true) {
12745 throw new Error(fnName + ": You can't combine multiple orderBy calls.");
12746 }
12747}
12748/**
12749 * Validates start/end values for queries.
12750 */
12751function validateQueryEndpoints(params) {
12752 var startNode = null;
12753 var endNode = null;
12754 if (params.hasStart()) {
12755 startNode = params.getIndexStartValue();
12756 }
12757 if (params.hasEnd()) {
12758 endNode = params.getIndexEndValue();
12759 }
12760 if (params.getIndex() === KEY_INDEX) {
12761 var tooManyArgsError = 'Query: When ordering by key, you may only pass one argument to ' +
12762 'startAt(), endAt(), or equalTo().';
12763 var wrongArgTypeError = 'Query: When ordering by key, the argument passed to startAt(), startAfter(), ' +
12764 'endAt(), endBefore(), or equalTo() must be a string.';
12765 if (params.hasStart()) {
12766 var startName = params.getIndexStartName();
12767 if (startName !== MIN_NAME) {
12768 throw new Error(tooManyArgsError);
12769 }
12770 else if (typeof startNode !== 'string') {
12771 throw new Error(wrongArgTypeError);
12772 }
12773 }
12774 if (params.hasEnd()) {
12775 var endName = params.getIndexEndName();
12776 if (endName !== MAX_NAME) {
12777 throw new Error(tooManyArgsError);
12778 }
12779 else if (typeof endNode !== 'string') {
12780 throw new Error(wrongArgTypeError);
12781 }
12782 }
12783 }
12784 else if (params.getIndex() === PRIORITY_INDEX) {
12785 if ((startNode != null && !isValidPriority(startNode)) ||
12786 (endNode != null && !isValidPriority(endNode))) {
12787 throw new Error('Query: When ordering by priority, the first argument passed to startAt(), ' +
12788 'startAfter() endAt(), endBefore(), or equalTo() must be a valid priority value ' +
12789 '(null, a number, or a string).');
12790 }
12791 }
12792 else {
12793 util.assert(params.getIndex() instanceof PathIndex ||
12794 params.getIndex() === VALUE_INDEX, 'unknown index type.');
12795 if ((startNode != null && typeof startNode === 'object') ||
12796 (endNode != null && typeof endNode === 'object')) {
12797 throw new Error('Query: First argument passed to startAt(), startAfter(), endAt(), endBefore(), or ' +
12798 'equalTo() cannot be an object.');
12799 }
12800 }
12801}
12802/**
12803 * Validates that limit* has been called with the correct combination of parameters
12804 */
12805function validateLimit(params) {
12806 if (params.hasStart() &&
12807 params.hasEnd() &&
12808 params.hasLimit() &&
12809 !params.hasAnchoredLimit()) {
12810 throw new Error("Query: Can't combine startAt(), startAfter(), endAt(), endBefore(), and limit(). Use " +
12811 'limitToFirst() or limitToLast() instead.');
12812 }
12813}
12814/**
12815 * @internal
12816 */
12817var ReferenceImpl = /** @class */ (function (_super) {
12818 tslib.__extends(ReferenceImpl, _super);
12819 /** @hideconstructor */
12820 function ReferenceImpl(repo, path) {
12821 return _super.call(this, repo, path, new QueryParams(), false) || this;
12822 }
12823 Object.defineProperty(ReferenceImpl.prototype, "parent", {
12824 get: function () {
12825 var parentPath = pathParent(this._path);
12826 return parentPath === null
12827 ? null
12828 : new ReferenceImpl(this._repo, parentPath);
12829 },
12830 enumerable: false,
12831 configurable: true
12832 });
12833 Object.defineProperty(ReferenceImpl.prototype, "root", {
12834 get: function () {
12835 var ref = this;
12836 while (ref.parent !== null) {
12837 ref = ref.parent;
12838 }
12839 return ref;
12840 },
12841 enumerable: false,
12842 configurable: true
12843 });
12844 return ReferenceImpl;
12845}(QueryImpl));
12846/**
12847 * A `DataSnapshot` contains data from a Database location.
12848 *
12849 * Any time you read data from the Database, you receive the data as a
12850 * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach
12851 * with `on()` or `once()`. You can extract the contents of the snapshot as a
12852 * JavaScript object by calling the `val()` method. Alternatively, you can
12853 * traverse into the snapshot by calling `child()` to return child snapshots
12854 * (which you could then call `val()` on).
12855 *
12856 * A `DataSnapshot` is an efficiently generated, immutable copy of the data at
12857 * a Database location. It cannot be modified and will never change (to modify
12858 * data, you always call the `set()` method on a `Reference` directly).
12859 */
12860var DataSnapshot = /** @class */ (function () {
12861 /**
12862 * @param _node - A SnapshotNode to wrap.
12863 * @param ref - The location this snapshot came from.
12864 * @param _index - The iteration order for this snapshot
12865 * @hideconstructor
12866 */
12867 function DataSnapshot(_node,
12868 /**
12869 * The location of this DataSnapshot.
12870 */
12871 ref, _index) {
12872 this._node = _node;
12873 this.ref = ref;
12874 this._index = _index;
12875 }
12876 Object.defineProperty(DataSnapshot.prototype, "priority", {
12877 /**
12878 * Gets the priority value of the data in this `DataSnapshot`.
12879 *
12880 * Applications need not use priority but can order collections by
12881 * ordinary properties (see
12882 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data |Sorting and filtering data}
12883 * ).
12884 */
12885 get: function () {
12886 // typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
12887 return this._node.getPriority().val();
12888 },
12889 enumerable: false,
12890 configurable: true
12891 });
12892 Object.defineProperty(DataSnapshot.prototype, "key", {
12893 /**
12894 * The key (last part of the path) of the location of this `DataSnapshot`.
12895 *
12896 * The last token in a Database location is considered its key. For example,
12897 * "ada" is the key for the /users/ada/ node. Accessing the key on any
12898 * `DataSnapshot` will return the key for the location that generated it.
12899 * However, accessing the key on the root URL of a Database will return
12900 * `null`.
12901 */
12902 get: function () {
12903 return this.ref.key;
12904 },
12905 enumerable: false,
12906 configurable: true
12907 });
12908 Object.defineProperty(DataSnapshot.prototype, "size", {
12909 /** Returns the number of child properties of this `DataSnapshot`. */
12910 get: function () {
12911 return this._node.numChildren();
12912 },
12913 enumerable: false,
12914 configurable: true
12915 });
12916 /**
12917 * Gets another `DataSnapshot` for the location at the specified relative path.
12918 *
12919 * Passing a relative path to the `child()` method of a DataSnapshot returns
12920 * another `DataSnapshot` for the location at the specified relative path. The
12921 * relative path can either be a simple child name (for example, "ada") or a
12922 * deeper, slash-separated path (for example, "ada/name/first"). If the child
12923 * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot`
12924 * whose value is `null`) is returned.
12925 *
12926 * @param path - A relative path to the location of child data.
12927 */
12928 DataSnapshot.prototype.child = function (path) {
12929 var childPath = new Path(path);
12930 var childRef = child(this.ref, path);
12931 return new DataSnapshot(this._node.getChild(childPath), childRef, PRIORITY_INDEX);
12932 };
12933 /**
12934 * Returns true if this `DataSnapshot` contains any data. It is slightly more
12935 * efficient than using `snapshot.val() !== null`.
12936 */
12937 DataSnapshot.prototype.exists = function () {
12938 return !this._node.isEmpty();
12939 };
12940 /**
12941 * Exports the entire contents of the DataSnapshot as a JavaScript object.
12942 *
12943 * The `exportVal()` method is similar to `val()`, except priority information
12944 * is included (if available), making it suitable for backing up your data.
12945 *
12946 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12947 * Array, string, number, boolean, or `null`).
12948 */
12949 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12950 DataSnapshot.prototype.exportVal = function () {
12951 return this._node.val(true);
12952 };
12953 /**
12954 * Enumerates the top-level children in the `DataSnapshot`.
12955 *
12956 * Because of the way JavaScript objects work, the ordering of data in the
12957 * JavaScript object returned by `val()` is not guaranteed to match the
12958 * ordering on the server nor the ordering of `onChildAdded()` events. That is
12959 * where `forEach()` comes in handy. It guarantees the children of a
12960 * `DataSnapshot` will be iterated in their query order.
12961 *
12962 * If no explicit `orderBy*()` method is used, results are returned
12963 * ordered by key (unless priorities are used, in which case, results are
12964 * returned by priority).
12965 *
12966 * @param action - A function that will be called for each child DataSnapshot.
12967 * The callback can return true to cancel further enumeration.
12968 * @returns true if enumeration was canceled due to your callback returning
12969 * true.
12970 */
12971 DataSnapshot.prototype.forEach = function (action) {
12972 var _this = this;
12973 if (this._node.isLeafNode()) {
12974 return false;
12975 }
12976 var childrenNode = this._node;
12977 // Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
12978 return !!childrenNode.forEachChild(this._index, function (key, node) {
12979 return action(new DataSnapshot(node, child(_this.ref, key), PRIORITY_INDEX));
12980 });
12981 };
12982 /**
12983 * Returns true if the specified child path has (non-null) data.
12984 *
12985 * @param path - A relative path to the location of a potential child.
12986 * @returns `true` if data exists at the specified child path; else
12987 * `false`.
12988 */
12989 DataSnapshot.prototype.hasChild = function (path) {
12990 var childPath = new Path(path);
12991 return !this._node.getChild(childPath).isEmpty();
12992 };
12993 /**
12994 * Returns whether or not the `DataSnapshot` has any non-`null` child
12995 * properties.
12996 *
12997 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
12998 * children. If it does, you can enumerate them using `forEach()`. If it
12999 * doesn't, then either this snapshot contains a primitive value (which can be
13000 * retrieved with `val()`) or it is empty (in which case, `val()` will return
13001 * `null`).
13002 *
13003 * @returns true if this snapshot has any children; else false.
13004 */
13005 DataSnapshot.prototype.hasChildren = function () {
13006 if (this._node.isLeafNode()) {
13007 return false;
13008 }
13009 else {
13010 return !this._node.isEmpty();
13011 }
13012 };
13013 /**
13014 * Returns a JSON-serializable representation of this object.
13015 */
13016 DataSnapshot.prototype.toJSON = function () {
13017 return this.exportVal();
13018 };
13019 /**
13020 * Extracts a JavaScript value from a `DataSnapshot`.
13021 *
13022 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
13023 * scalar type (string, number, or boolean), an array, or an object. It may
13024 * also return null, indicating that the `DataSnapshot` is empty (contains no
13025 * data).
13026 *
13027 * @returns The DataSnapshot's contents as a JavaScript value (Object,
13028 * Array, string, number, boolean, or `null`).
13029 */
13030 // eslint-disable-next-line @typescript-eslint/no-explicit-any
13031 DataSnapshot.prototype.val = function () {
13032 return this._node.val();
13033 };
13034 return DataSnapshot;
13035}());
13036/**
13037 *
13038 * Returns a `Reference` representing the location in the Database
13039 * corresponding to the provided path. If no path is provided, the `Reference`
13040 * will point to the root of the Database.
13041 *
13042 * @param db - The database instance to obtain a reference for.
13043 * @param path - Optional path representing the location the returned
13044 * `Reference` will point. If not provided, the returned `Reference` will
13045 * point to the root of the Database.
13046 * @returns If a path is provided, a `Reference`
13047 * pointing to the provided path. Otherwise, a `Reference` pointing to the
13048 * root of the Database.
13049 */
13050function ref(db, path) {
13051 db = util.getModularInstance(db);
13052 db._checkNotDeleted('ref');
13053 return path !== undefined ? child(db._root, path) : db._root;
13054}
13055/**
13056 * Returns a `Reference` representing the location in the Database
13057 * corresponding to the provided Firebase URL.
13058 *
13059 * An exception is thrown if the URL is not a valid Firebase Database URL or it
13060 * has a different domain than the current `Database` instance.
13061 *
13062 * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored
13063 * and are not applied to the returned `Reference`.
13064 *
13065 * @param db - The database instance to obtain a reference for.
13066 * @param url - The Firebase URL at which the returned `Reference` will
13067 * point.
13068 * @returns A `Reference` pointing to the provided
13069 * Firebase URL.
13070 */
13071function refFromURL(db, url) {
13072 db = util.getModularInstance(db);
13073 db._checkNotDeleted('refFromURL');
13074 var parsedURL = parseRepoInfo(url, db._repo.repoInfo_.nodeAdmin);
13075 validateUrl('refFromURL', parsedURL);
13076 var repoInfo = parsedURL.repoInfo;
13077 if (!db._repo.repoInfo_.isCustomHost() &&
13078 repoInfo.host !== db._repo.repoInfo_.host) {
13079 fatal('refFromURL' +
13080 ': Host name does not match the current database: ' +
13081 '(found ' +
13082 repoInfo.host +
13083 ' but expected ' +
13084 db._repo.repoInfo_.host +
13085 ')');
13086 }
13087 return ref(db, parsedURL.path.toString());
13088}
13089/**
13090 * Gets a `Reference` for the location at the specified relative path.
13091 *
13092 * The relative path can either be a simple child name (for example, "ada") or
13093 * a deeper slash-separated path (for example, "ada/name/first").
13094 *
13095 * @param parent - The parent location.
13096 * @param path - A relative path from this location to the desired child
13097 * location.
13098 * @returns The specified child location.
13099 */
13100function child(parent, path) {
13101 parent = util.getModularInstance(parent);
13102 if (pathGetFront(parent._path) === null) {
13103 validateRootPathString('child', 'path', path, false);
13104 }
13105 else {
13106 validatePathString('child', 'path', path, false);
13107 }
13108 return new ReferenceImpl(parent._repo, pathChild(parent._path, path));
13109}
13110/**
13111 * Generates a new child location using a unique key and returns its
13112 * `Reference`.
13113 *
13114 * This is the most common pattern for adding data to a collection of items.
13115 *
13116 * If you provide a value to `push()`, the value is written to the
13117 * generated location. If you don't pass a value, nothing is written to the
13118 * database and the child remains empty (but you can use the `Reference`
13119 * elsewhere).
13120 *
13121 * The unique keys generated by `push()` are ordered by the current time, so the
13122 * resulting list of items is chronologically sorted. The keys are also
13123 * designed to be unguessable (they contain 72 random bits of entropy).
13124 *
13125 * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}
13126 * </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}
13127 *
13128 * @param parent - The parent location.
13129 * @param value - Optional value to be written at the generated location.
13130 * @returns Combined `Promise` and `Reference`; resolves when write is complete,
13131 * but can be used immediately as the `Reference` to the child location.
13132 */
13133function push(parent, value) {
13134 parent = util.getModularInstance(parent);
13135 validateWritablePath('push', parent._path);
13136 validateFirebaseDataArg('push', value, parent._path, true);
13137 var now = repoServerTime(parent._repo);
13138 var name = nextPushId(now);
13139 // push() returns a ThennableReference whose promise is fulfilled with a
13140 // regular Reference. We use child() to create handles to two different
13141 // references. The first is turned into a ThennableReference below by adding
13142 // then() and catch() methods and is used as the return value of push(). The
13143 // second remains a regular Reference and is used as the fulfilled value of
13144 // the first ThennableReference.
13145 var thennablePushRef = child(parent, name);
13146 var pushRef = child(parent, name);
13147 var promise;
13148 if (value != null) {
13149 promise = set(pushRef, value).then(function () { return pushRef; });
13150 }
13151 else {
13152 promise = Promise.resolve(pushRef);
13153 }
13154 thennablePushRef.then = promise.then.bind(promise);
13155 thennablePushRef.catch = promise.then.bind(promise, undefined);
13156 return thennablePushRef;
13157}
13158/**
13159 * Removes the data at this Database location.
13160 *
13161 * Any data at child locations will also be deleted.
13162 *
13163 * The effect of the remove will be visible immediately and the corresponding
13164 * event 'value' will be triggered. Synchronization of the remove to the
13165 * Firebase servers will also be started, and the returned Promise will resolve
13166 * when complete. If provided, the onComplete callback will be called
13167 * asynchronously after synchronization has finished.
13168 *
13169 * @param ref - The location to remove.
13170 * @returns Resolves when remove on server is complete.
13171 */
13172function remove(ref) {
13173 validateWritablePath('remove', ref._path);
13174 return set(ref, null);
13175}
13176/**
13177 * Writes data to this Database location.
13178 *
13179 * This will overwrite any data at this location and all child locations.
13180 *
13181 * The effect of the write will be visible immediately, and the corresponding
13182 * events ("value", "child_added", etc.) will be triggered. Synchronization of
13183 * the data to the Firebase servers will also be started, and the returned
13184 * Promise will resolve when complete. If provided, the `onComplete` callback
13185 * will be called asynchronously after synchronization has finished.
13186 *
13187 * Passing `null` for the new value is equivalent to calling `remove()`; namely,
13188 * all data at this location and all child locations will be deleted.
13189 *
13190 * `set()` will remove any priority stored at this location, so if priority is
13191 * meant to be preserved, you need to use `setWithPriority()` instead.
13192 *
13193 * Note that modifying data with `set()` will cancel any pending transactions
13194 * at that location, so extreme care should be taken if mixing `set()` and
13195 * `transaction()` to modify the same data.
13196 *
13197 * A single `set()` will generate a single "value" event at the location where
13198 * the `set()` was performed.
13199 *
13200 * @param ref - The location to write to.
13201 * @param value - The value to be written (string, number, boolean, object,
13202 * array, or null).
13203 * @returns Resolves when write to server is complete.
13204 */
13205function set(ref, value) {
13206 ref = util.getModularInstance(ref);
13207 validateWritablePath('set', ref._path);
13208 validateFirebaseDataArg('set', value, ref._path, false);
13209 var deferred = new util.Deferred();
13210 repoSetWithPriority(ref._repo, ref._path, value,
13211 /*priority=*/ null, deferred.wrapCallback(function () { }));
13212 return deferred.promise;
13213}
13214/**
13215 * Sets a priority for the data at this Database location.
13216 *
13217 * Applications need not use priority but can order collections by
13218 * ordinary properties (see
13219 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13220 * ).
13221 *
13222 * @param ref - The location to write to.
13223 * @param priority - The priority to be written (string, number, or null).
13224 * @returns Resolves when write to server is complete.
13225 */
13226function setPriority(ref, priority) {
13227 ref = util.getModularInstance(ref);
13228 validateWritablePath('setPriority', ref._path);
13229 validatePriority('setPriority', priority, false);
13230 var deferred = new util.Deferred();
13231 repoSetWithPriority(ref._repo, pathChild(ref._path, '.priority'), priority, null, deferred.wrapCallback(function () { }));
13232 return deferred.promise;
13233}
13234/**
13235 * Writes data the Database location. Like `set()` but also specifies the
13236 * priority for that data.
13237 *
13238 * Applications need not use priority but can order collections by
13239 * ordinary properties (see
13240 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13241 * ).
13242 *
13243 * @param ref - The location to write to.
13244 * @param value - The value to be written (string, number, boolean, object,
13245 * array, or null).
13246 * @param priority - The priority to be written (string, number, or null).
13247 * @returns Resolves when write to server is complete.
13248 */
13249function setWithPriority(ref, value, priority) {
13250 validateWritablePath('setWithPriority', ref._path);
13251 validateFirebaseDataArg('setWithPriority', value, ref._path, false);
13252 validatePriority('setWithPriority', priority, false);
13253 if (ref.key === '.length' || ref.key === '.keys') {
13254 throw 'setWithPriority failed: ' + ref.key + ' is a read-only object.';
13255 }
13256 var deferred = new util.Deferred();
13257 repoSetWithPriority(ref._repo, ref._path, value, priority, deferred.wrapCallback(function () { }));
13258 return deferred.promise;
13259}
13260/**
13261 * Writes multiple values to the Database at once.
13262 *
13263 * The `values` argument contains multiple property-value pairs that will be
13264 * written to the Database together. Each child property can either be a simple
13265 * property (for example, "name") or a relative path (for example,
13266 * "name/first") from the current location to the data to update.
13267 *
13268 * As opposed to the `set()` method, `update()` can be use to selectively update
13269 * only the referenced properties at the current location (instead of replacing
13270 * all the child properties at the current location).
13271 *
13272 * The effect of the write will be visible immediately, and the corresponding
13273 * events ('value', 'child_added', etc.) will be triggered. Synchronization of
13274 * the data to the Firebase servers will also be started, and the returned
13275 * Promise will resolve when complete. If provided, the `onComplete` callback
13276 * will be called asynchronously after synchronization has finished.
13277 *
13278 * A single `update()` will generate a single "value" event at the location
13279 * where the `update()` was performed, regardless of how many children were
13280 * modified.
13281 *
13282 * Note that modifying data with `update()` will cancel any pending
13283 * transactions at that location, so extreme care should be taken if mixing
13284 * `update()` and `transaction()` to modify the same data.
13285 *
13286 * Passing `null` to `update()` will remove the data at this location.
13287 *
13288 * See
13289 * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}.
13290 *
13291 * @param ref - The location to write to.
13292 * @param values - Object containing multiple values.
13293 * @returns Resolves when update on server is complete.
13294 */
13295function update(ref, values) {
13296 validateFirebaseMergeDataArg('update', values, ref._path, false);
13297 var deferred = new util.Deferred();
13298 repoUpdate(ref._repo, ref._path, values, deferred.wrapCallback(function () { }));
13299 return deferred.promise;
13300}
13301/**
13302 * Gets the most up-to-date result for this query.
13303 *
13304 * @param query - The query to run.
13305 * @returns A promise which resolves to the resulting DataSnapshot if a value is
13306 * available, or rejects if the client is unable to return a value (e.g., if the
13307 * server is unreachable and there is nothing cached).
13308 */
13309function get(query) {
13310 query = util.getModularInstance(query);
13311 return repoGetValue(query._repo, query).then(function (node) {
13312 return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
13313 });
13314}
13315/**
13316 * Represents registration for 'value' events.
13317 */
13318var ValueEventRegistration = /** @class */ (function () {
13319 function ValueEventRegistration(callbackContext) {
13320 this.callbackContext = callbackContext;
13321 }
13322 ValueEventRegistration.prototype.respondsTo = function (eventType) {
13323 return eventType === 'value';
13324 };
13325 ValueEventRegistration.prototype.createEvent = function (change, query) {
13326 var index = query._queryParams.getIndex();
13327 return new DataEvent('value', this, new DataSnapshot(change.snapshotNode, new ReferenceImpl(query._repo, query._path), index));
13328 };
13329 ValueEventRegistration.prototype.getEventRunner = function (eventData) {
13330 var _this = this;
13331 if (eventData.getEventType() === 'cancel') {
13332 return function () {
13333 return _this.callbackContext.onCancel(eventData.error);
13334 };
13335 }
13336 else {
13337 return function () {
13338 return _this.callbackContext.onValue(eventData.snapshot, null);
13339 };
13340 }
13341 };
13342 ValueEventRegistration.prototype.createCancelEvent = function (error, path) {
13343 if (this.callbackContext.hasCancelCallback) {
13344 return new CancelEvent(this, error, path);
13345 }
13346 else {
13347 return null;
13348 }
13349 };
13350 ValueEventRegistration.prototype.matches = function (other) {
13351 if (!(other instanceof ValueEventRegistration)) {
13352 return false;
13353 }
13354 else if (!other.callbackContext || !this.callbackContext) {
13355 // If no callback specified, we consider it to match any callback.
13356 return true;
13357 }
13358 else {
13359 return other.callbackContext.matches(this.callbackContext);
13360 }
13361 };
13362 ValueEventRegistration.prototype.hasAnyCallback = function () {
13363 return this.callbackContext !== null;
13364 };
13365 return ValueEventRegistration;
13366}());
13367/**
13368 * Represents the registration of a child_x event.
13369 */
13370var ChildEventRegistration = /** @class */ (function () {
13371 function ChildEventRegistration(eventType, callbackContext) {
13372 this.eventType = eventType;
13373 this.callbackContext = callbackContext;
13374 }
13375 ChildEventRegistration.prototype.respondsTo = function (eventType) {
13376 var eventToCheck = eventType === 'children_added' ? 'child_added' : eventType;
13377 eventToCheck =
13378 eventToCheck === 'children_removed' ? 'child_removed' : eventToCheck;
13379 return this.eventType === eventToCheck;
13380 };
13381 ChildEventRegistration.prototype.createCancelEvent = function (error, path) {
13382 if (this.callbackContext.hasCancelCallback) {
13383 return new CancelEvent(this, error, path);
13384 }
13385 else {
13386 return null;
13387 }
13388 };
13389 ChildEventRegistration.prototype.createEvent = function (change, query) {
13390 util.assert(change.childName != null, 'Child events should have a childName.');
13391 var childRef = child(new ReferenceImpl(query._repo, query._path), change.childName);
13392 var index = query._queryParams.getIndex();
13393 return new DataEvent(change.type, this, new DataSnapshot(change.snapshotNode, childRef, index), change.prevName);
13394 };
13395 ChildEventRegistration.prototype.getEventRunner = function (eventData) {
13396 var _this = this;
13397 if (eventData.getEventType() === 'cancel') {
13398 return function () {
13399 return _this.callbackContext.onCancel(eventData.error);
13400 };
13401 }
13402 else {
13403 return function () {
13404 return _this.callbackContext.onValue(eventData.snapshot, eventData.prevName);
13405 };
13406 }
13407 };
13408 ChildEventRegistration.prototype.matches = function (other) {
13409 if (other instanceof ChildEventRegistration) {
13410 return (this.eventType === other.eventType &&
13411 (!this.callbackContext ||
13412 !other.callbackContext ||
13413 this.callbackContext.matches(other.callbackContext)));
13414 }
13415 return false;
13416 };
13417 ChildEventRegistration.prototype.hasAnyCallback = function () {
13418 return !!this.callbackContext;
13419 };
13420 return ChildEventRegistration;
13421}());
13422function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) {
13423 var cancelCallback;
13424 if (typeof cancelCallbackOrListenOptions === 'object') {
13425 cancelCallback = undefined;
13426 options = cancelCallbackOrListenOptions;
13427 }
13428 if (typeof cancelCallbackOrListenOptions === 'function') {
13429 cancelCallback = cancelCallbackOrListenOptions;
13430 }
13431 if (options && options.onlyOnce) {
13432 var userCallback_1 = callback;
13433 var onceCallback = function (dataSnapshot, previousChildName) {
13434 repoRemoveEventCallbackForQuery(query._repo, query, container);
13435 userCallback_1(dataSnapshot, previousChildName);
13436 };
13437 onceCallback.userCallback = callback.userCallback;
13438 onceCallback.context = callback.context;
13439 callback = onceCallback;
13440 }
13441 var callbackContext = new CallbackContext(callback, cancelCallback || undefined);
13442 var container = eventType === 'value'
13443 ? new ValueEventRegistration(callbackContext)
13444 : new ChildEventRegistration(eventType, callbackContext);
13445 repoAddEventCallbackForQuery(query._repo, query, container);
13446 return function () { return repoRemoveEventCallbackForQuery(query._repo, query, container); };
13447}
13448function onValue(query, callback, cancelCallbackOrListenOptions, options) {
13449 return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options);
13450}
13451function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) {
13452 return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options);
13453}
13454function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) {
13455 return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options);
13456}
13457function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) {
13458 return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options);
13459}
13460function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) {
13461 return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options);
13462}
13463/**
13464 * Detaches a callback previously attached with `on()`.
13465 *
13466 * Detach a callback previously attached with `on()`. Note that if `on()` was
13467 * called multiple times with the same eventType and callback, the callback
13468 * will be called multiple times for each event, and `off()` must be called
13469 * multiple times to remove the callback. Calling `off()` on a parent listener
13470 * will not automatically remove listeners registered on child nodes, `off()`
13471 * must also be called on any child listeners to remove the callback.
13472 *
13473 * If a callback is not specified, all callbacks for the specified eventType
13474 * will be removed. Similarly, if no eventType is specified, all callbacks
13475 * for the `Reference` will be removed.
13476 *
13477 * Individual listeners can also be removed by invoking their unsubscribe
13478 * callbacks.
13479 *
13480 * @param query - The query that the listener was registered with.
13481 * @param eventType - One of the following strings: "value", "child_added",
13482 * "child_changed", "child_removed", or "child_moved." If omitted, all callbacks
13483 * for the `Reference` will be removed.
13484 * @param callback - The callback function that was passed to `on()` or
13485 * `undefined` to remove all callbacks.
13486 */
13487function off(query, eventType, callback) {
13488 var container = null;
13489 var expCallback = callback ? new CallbackContext(callback) : null;
13490 if (eventType === 'value') {
13491 container = new ValueEventRegistration(expCallback);
13492 }
13493 else if (eventType) {
13494 container = new ChildEventRegistration(eventType, expCallback);
13495 }
13496 repoRemoveEventCallbackForQuery(query._repo, query, container);
13497}
13498/**
13499 * A `QueryConstraint` is used to narrow the set of documents returned by a
13500 * Database query. `QueryConstraint`s are created by invoking {@link endAt},
13501 * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link
13502 * limitToFirst}, {@link limitToLast}, {@link orderByChild},
13503 * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} ,
13504 * {@link orderByValue} or {@link equalTo} and
13505 * can then be passed to {@link query} to create a new query instance that
13506 * also contains this `QueryConstraint`.
13507 */
13508var QueryConstraint = /** @class */ (function () {
13509 function QueryConstraint() {
13510 }
13511 return QueryConstraint;
13512}());
13513var QueryEndAtConstraint = /** @class */ (function (_super) {
13514 tslib.__extends(QueryEndAtConstraint, _super);
13515 function QueryEndAtConstraint(_value, _key) {
13516 var _this = _super.call(this) || this;
13517 _this._value = _value;
13518 _this._key = _key;
13519 return _this;
13520 }
13521 QueryEndAtConstraint.prototype._apply = function (query) {
13522 validateFirebaseDataArg('endAt', this._value, query._path, true);
13523 var newParams = queryParamsEndAt(query._queryParams, this._value, this._key);
13524 validateLimit(newParams);
13525 validateQueryEndpoints(newParams);
13526 if (query._queryParams.hasEnd()) {
13527 throw new Error('endAt: Starting point was already set (by another call to endAt, ' +
13528 'endBefore or equalTo).');
13529 }
13530 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13531 };
13532 return QueryEndAtConstraint;
13533}(QueryConstraint));
13534/**
13535 * Creates a `QueryConstraint` with the specified ending point.
13536 *
13537 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13538 * allows you to choose arbitrary starting and ending points for your queries.
13539 *
13540 * The ending point is inclusive, so children with exactly the specified value
13541 * will be included in the query. The optional key argument can be used to
13542 * further limit the range of the query. If it is specified, then children that
13543 * have exactly the specified value must also have a key name less than or equal
13544 * to the specified key.
13545 *
13546 * You can read more about `endAt()` in
13547 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13548 *
13549 * @param value - The value to end at. The argument type depends on which
13550 * `orderBy*()` function was used in this query. Specify a value that matches
13551 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13552 * value must be a string.
13553 * @param key - The child key to end at, among the children with the previously
13554 * specified priority. This argument is only allowed if ordering by child,
13555 * value, or priority.
13556 */
13557function endAt(value, key) {
13558 validateKey('endAt', 'key', key, true);
13559 return new QueryEndAtConstraint(value, key);
13560}
13561var QueryEndBeforeConstraint = /** @class */ (function (_super) {
13562 tslib.__extends(QueryEndBeforeConstraint, _super);
13563 function QueryEndBeforeConstraint(_value, _key) {
13564 var _this = _super.call(this) || this;
13565 _this._value = _value;
13566 _this._key = _key;
13567 return _this;
13568 }
13569 QueryEndBeforeConstraint.prototype._apply = function (query) {
13570 validateFirebaseDataArg('endBefore', this._value, query._path, false);
13571 var newParams = queryParamsEndBefore(query._queryParams, this._value, this._key);
13572 validateLimit(newParams);
13573 validateQueryEndpoints(newParams);
13574 if (query._queryParams.hasEnd()) {
13575 throw new Error('endBefore: Starting point was already set (by another call to endAt, ' +
13576 'endBefore or equalTo).');
13577 }
13578 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13579 };
13580 return QueryEndBeforeConstraint;
13581}(QueryConstraint));
13582/**
13583 * Creates a `QueryConstraint` with the specified ending point (exclusive).
13584 *
13585 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13586 * allows you to choose arbitrary starting and ending points for your queries.
13587 *
13588 * The ending point is exclusive. If only a value is provided, children
13589 * with a value less than the specified value will be included in the query.
13590 * If a key is specified, then children must have a value lesss than or equal
13591 * to the specified value and a a key name less than the specified key.
13592 *
13593 * @param value - The value to end before. The argument type depends on which
13594 * `orderBy*()` function was used in this query. Specify a value that matches
13595 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13596 * value must be a string.
13597 * @param key - The child key to end before, among the children with the
13598 * previously specified priority. This argument is only allowed if ordering by
13599 * child, value, or priority.
13600 */
13601function endBefore(value, key) {
13602 validateKey('endBefore', 'key', key, true);
13603 return new QueryEndBeforeConstraint(value, key);
13604}
13605var QueryStartAtConstraint = /** @class */ (function (_super) {
13606 tslib.__extends(QueryStartAtConstraint, _super);
13607 function QueryStartAtConstraint(_value, _key) {
13608 var _this = _super.call(this) || this;
13609 _this._value = _value;
13610 _this._key = _key;
13611 return _this;
13612 }
13613 QueryStartAtConstraint.prototype._apply = function (query) {
13614 validateFirebaseDataArg('startAt', this._value, query._path, true);
13615 var newParams = queryParamsStartAt(query._queryParams, this._value, this._key);
13616 validateLimit(newParams);
13617 validateQueryEndpoints(newParams);
13618 if (query._queryParams.hasStart()) {
13619 throw new Error('startAt: Starting point was already set (by another call to startAt, ' +
13620 'startBefore or equalTo).');
13621 }
13622 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13623 };
13624 return QueryStartAtConstraint;
13625}(QueryConstraint));
13626/**
13627 * Creates a `QueryConstraint` with the specified starting point.
13628 *
13629 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13630 * allows you to choose arbitrary starting and ending points for your queries.
13631 *
13632 * The starting point is inclusive, so children with exactly the specified value
13633 * will be included in the query. The optional key argument can be used to
13634 * further limit the range of the query. If it is specified, then children that
13635 * have exactly the specified value must also have a key name greater than or
13636 * equal to the specified key.
13637 *
13638 * You can read more about `startAt()` in
13639 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13640 *
13641 * @param value - The value to start at. The argument type depends on which
13642 * `orderBy*()` function was used in this query. Specify a value that matches
13643 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13644 * value must be a string.
13645 * @param key - The child key to start at. This argument is only allowed if
13646 * ordering by child, value, or priority.
13647 */
13648function startAt(value, key) {
13649 if (value === void 0) { value = null; }
13650 validateKey('startAt', 'key', key, true);
13651 return new QueryStartAtConstraint(value, key);
13652}
13653var QueryStartAfterConstraint = /** @class */ (function (_super) {
13654 tslib.__extends(QueryStartAfterConstraint, _super);
13655 function QueryStartAfterConstraint(_value, _key) {
13656 var _this = _super.call(this) || this;
13657 _this._value = _value;
13658 _this._key = _key;
13659 return _this;
13660 }
13661 QueryStartAfterConstraint.prototype._apply = function (query) {
13662 validateFirebaseDataArg('startAfter', this._value, query._path, false);
13663 var newParams = queryParamsStartAfter(query._queryParams, this._value, this._key);
13664 validateLimit(newParams);
13665 validateQueryEndpoints(newParams);
13666 if (query._queryParams.hasStart()) {
13667 throw new Error('startAfter: Starting point was already set (by another call to startAt, ' +
13668 'startAfter, or equalTo).');
13669 }
13670 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13671 };
13672 return QueryStartAfterConstraint;
13673}(QueryConstraint));
13674/**
13675 * Creates a `QueryConstraint` with the specified starting point (exclusive).
13676 *
13677 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13678 * allows you to choose arbitrary starting and ending points for your queries.
13679 *
13680 * The starting point is exclusive. If only a value is provided, children
13681 * with a value greater than the specified value will be included in the query.
13682 * If a key is specified, then children must have a value greater than or equal
13683 * to the specified value and a a key name greater than the specified key.
13684 *
13685 * @param value - The value to start after. The argument type depends on which
13686 * `orderBy*()` function was used in this query. Specify a value that matches
13687 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13688 * value must be a string.
13689 * @param key - The child key to start after. This argument is only allowed if
13690 * ordering by child, value, or priority.
13691 */
13692function startAfter(value, key) {
13693 validateKey('startAfter', 'key', key, true);
13694 return new QueryStartAfterConstraint(value, key);
13695}
13696var QueryLimitToFirstConstraint = /** @class */ (function (_super) {
13697 tslib.__extends(QueryLimitToFirstConstraint, _super);
13698 function QueryLimitToFirstConstraint(_limit) {
13699 var _this = _super.call(this) || this;
13700 _this._limit = _limit;
13701 return _this;
13702 }
13703 QueryLimitToFirstConstraint.prototype._apply = function (query) {
13704 if (query._queryParams.hasLimit()) {
13705 throw new Error('limitToFirst: Limit was already set (by another call to limitToFirst ' +
13706 'or limitToLast).');
13707 }
13708 return new QueryImpl(query._repo, query._path, queryParamsLimitToFirst(query._queryParams, this._limit), query._orderByCalled);
13709 };
13710 return QueryLimitToFirstConstraint;
13711}(QueryConstraint));
13712/**
13713 * Creates a new `QueryConstraint` that if limited to the first specific number
13714 * of children.
13715 *
13716 * The `limitToFirst()` method is used to set a maximum number of children to be
13717 * synced for a given callback. If we set a limit of 100, we will initially only
13718 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13719 * stored in our Database, a `child_added` event will fire for each message.
13720 * However, if we have over 100 messages, we will only receive a `child_added`
13721 * event for the first 100 ordered messages. As items change, we will receive
13722 * `child_removed` events for each item that drops out of the active list so
13723 * that the total number stays at 100.
13724 *
13725 * You can read more about `limitToFirst()` in
13726 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13727 *
13728 * @param limit - The maximum number of nodes to include in this query.
13729 */
13730function limitToFirst(limit) {
13731 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13732 throw new Error('limitToFirst: First argument must be a positive integer.');
13733 }
13734 return new QueryLimitToFirstConstraint(limit);
13735}
13736var QueryLimitToLastConstraint = /** @class */ (function (_super) {
13737 tslib.__extends(QueryLimitToLastConstraint, _super);
13738 function QueryLimitToLastConstraint(_limit) {
13739 var _this = _super.call(this) || this;
13740 _this._limit = _limit;
13741 return _this;
13742 }
13743 QueryLimitToLastConstraint.prototype._apply = function (query) {
13744 if (query._queryParams.hasLimit()) {
13745 throw new Error('limitToLast: Limit was already set (by another call to limitToFirst ' +
13746 'or limitToLast).');
13747 }
13748 return new QueryImpl(query._repo, query._path, queryParamsLimitToLast(query._queryParams, this._limit), query._orderByCalled);
13749 };
13750 return QueryLimitToLastConstraint;
13751}(QueryConstraint));
13752/**
13753 * Creates a new `QueryConstraint` that is limited to return only the last
13754 * specified number of children.
13755 *
13756 * The `limitToLast()` method is used to set a maximum number of children to be
13757 * synced for a given callback. If we set a limit of 100, we will initially only
13758 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13759 * stored in our Database, a `child_added` event will fire for each message.
13760 * However, if we have over 100 messages, we will only receive a `child_added`
13761 * event for the last 100 ordered messages. As items change, we will receive
13762 * `child_removed` events for each item that drops out of the active list so
13763 * that the total number stays at 100.
13764 *
13765 * You can read more about `limitToLast()` in
13766 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13767 *
13768 * @param limit - The maximum number of nodes to include in this query.
13769 */
13770function limitToLast(limit) {
13771 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13772 throw new Error('limitToLast: First argument must be a positive integer.');
13773 }
13774 return new QueryLimitToLastConstraint(limit);
13775}
13776var QueryOrderByChildConstraint = /** @class */ (function (_super) {
13777 tslib.__extends(QueryOrderByChildConstraint, _super);
13778 function QueryOrderByChildConstraint(_path) {
13779 var _this = _super.call(this) || this;
13780 _this._path = _path;
13781 return _this;
13782 }
13783 QueryOrderByChildConstraint.prototype._apply = function (query) {
13784 validateNoPreviousOrderByCall(query, 'orderByChild');
13785 var parsedPath = new Path(this._path);
13786 if (pathIsEmpty(parsedPath)) {
13787 throw new Error('orderByChild: cannot pass in empty path. Use orderByValue() instead.');
13788 }
13789 var index = new PathIndex(parsedPath);
13790 var newParams = queryParamsOrderBy(query._queryParams, index);
13791 validateQueryEndpoints(newParams);
13792 return new QueryImpl(query._repo, query._path, newParams,
13793 /*orderByCalled=*/ true);
13794 };
13795 return QueryOrderByChildConstraint;
13796}(QueryConstraint));
13797/**
13798 * Creates a new `QueryConstraint` that orders by the specified child key.
13799 *
13800 * Queries can only order by one key at a time. Calling `orderByChild()`
13801 * multiple times on the same query is an error.
13802 *
13803 * Firebase queries allow you to order your data by any child key on the fly.
13804 * However, if you know in advance what your indexes will be, you can define
13805 * them via the .indexOn rule in your Security Rules for better performance. See
13806 * the{@link https://firebase.google.com/docs/database/security/indexing-data}
13807 * rule for more information.
13808 *
13809 * You can read more about `orderByChild()` in
13810 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13811 *
13812 * @param path - The path to order by.
13813 */
13814function orderByChild(path) {
13815 if (path === '$key') {
13816 throw new Error('orderByChild: "$key" is invalid. Use orderByKey() instead.');
13817 }
13818 else if (path === '$priority') {
13819 throw new Error('orderByChild: "$priority" is invalid. Use orderByPriority() instead.');
13820 }
13821 else if (path === '$value') {
13822 throw new Error('orderByChild: "$value" is invalid. Use orderByValue() instead.');
13823 }
13824 validatePathString('orderByChild', 'path', path, false);
13825 return new QueryOrderByChildConstraint(path);
13826}
13827var QueryOrderByKeyConstraint = /** @class */ (function (_super) {
13828 tslib.__extends(QueryOrderByKeyConstraint, _super);
13829 function QueryOrderByKeyConstraint() {
13830 return _super !== null && _super.apply(this, arguments) || this;
13831 }
13832 QueryOrderByKeyConstraint.prototype._apply = function (query) {
13833 validateNoPreviousOrderByCall(query, 'orderByKey');
13834 var newParams = queryParamsOrderBy(query._queryParams, KEY_INDEX);
13835 validateQueryEndpoints(newParams);
13836 return new QueryImpl(query._repo, query._path, newParams,
13837 /*orderByCalled=*/ true);
13838 };
13839 return QueryOrderByKeyConstraint;
13840}(QueryConstraint));
13841/**
13842 * Creates a new `QueryConstraint` that orders by the key.
13843 *
13844 * Sorts the results of a query by their (ascending) key values.
13845 *
13846 * You can read more about `orderByKey()` in
13847 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13848 */
13849function orderByKey() {
13850 return new QueryOrderByKeyConstraint();
13851}
13852var QueryOrderByPriorityConstraint = /** @class */ (function (_super) {
13853 tslib.__extends(QueryOrderByPriorityConstraint, _super);
13854 function QueryOrderByPriorityConstraint() {
13855 return _super !== null && _super.apply(this, arguments) || this;
13856 }
13857 QueryOrderByPriorityConstraint.prototype._apply = function (query) {
13858 validateNoPreviousOrderByCall(query, 'orderByPriority');
13859 var newParams = queryParamsOrderBy(query._queryParams, PRIORITY_INDEX);
13860 validateQueryEndpoints(newParams);
13861 return new QueryImpl(query._repo, query._path, newParams,
13862 /*orderByCalled=*/ true);
13863 };
13864 return QueryOrderByPriorityConstraint;
13865}(QueryConstraint));
13866/**
13867 * Creates a new `QueryConstraint` that orders by priority.
13868 *
13869 * Applications need not use priority but can order collections by
13870 * ordinary properties (see
13871 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}
13872 * for alternatives to priority.
13873 */
13874function orderByPriority() {
13875 return new QueryOrderByPriorityConstraint();
13876}
13877var QueryOrderByValueConstraint = /** @class */ (function (_super) {
13878 tslib.__extends(QueryOrderByValueConstraint, _super);
13879 function QueryOrderByValueConstraint() {
13880 return _super !== null && _super.apply(this, arguments) || this;
13881 }
13882 QueryOrderByValueConstraint.prototype._apply = function (query) {
13883 validateNoPreviousOrderByCall(query, 'orderByValue');
13884 var newParams = queryParamsOrderBy(query._queryParams, VALUE_INDEX);
13885 validateQueryEndpoints(newParams);
13886 return new QueryImpl(query._repo, query._path, newParams,
13887 /*orderByCalled=*/ true);
13888 };
13889 return QueryOrderByValueConstraint;
13890}(QueryConstraint));
13891/**
13892 * Creates a new `QueryConstraint` that orders by value.
13893 *
13894 * If the children of a query are all scalar values (string, number, or
13895 * boolean), you can order the results by their (ascending) values.
13896 *
13897 * You can read more about `orderByValue()` in
13898 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13899 */
13900function orderByValue() {
13901 return new QueryOrderByValueConstraint();
13902}
13903var QueryEqualToValueConstraint = /** @class */ (function (_super) {
13904 tslib.__extends(QueryEqualToValueConstraint, _super);
13905 function QueryEqualToValueConstraint(_value, _key) {
13906 var _this = _super.call(this) || this;
13907 _this._value = _value;
13908 _this._key = _key;
13909 return _this;
13910 }
13911 QueryEqualToValueConstraint.prototype._apply = function (query) {
13912 validateFirebaseDataArg('equalTo', this._value, query._path, false);
13913 if (query._queryParams.hasStart()) {
13914 throw new Error('equalTo: Starting point was already set (by another call to startAt/startAfter or ' +
13915 'equalTo).');
13916 }
13917 if (query._queryParams.hasEnd()) {
13918 throw new Error('equalTo: Ending point was already set (by another call to endAt/endBefore or ' +
13919 'equalTo).');
13920 }
13921 return new QueryEndAtConstraint(this._value, this._key)._apply(new QueryStartAtConstraint(this._value, this._key)._apply(query));
13922 };
13923 return QueryEqualToValueConstraint;
13924}(QueryConstraint));
13925/**
13926 * Creates a `QueryConstraint` that includes children that match the specified
13927 * value.
13928 *
13929 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13930 * allows you to choose arbitrary starting and ending points for your queries.
13931 *
13932 * The optional key argument can be used to further limit the range of the
13933 * query. If it is specified, then children that have exactly the specified
13934 * value must also have exactly the specified key as their key name. This can be
13935 * used to filter result sets with many matches for the same value.
13936 *
13937 * You can read more about `equalTo()` in
13938 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13939 *
13940 * @param value - The value to match for. The argument type depends on which
13941 * `orderBy*()` function was used in this query. Specify a value that matches
13942 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13943 * value must be a string.
13944 * @param key - The child key to start at, among the children with the
13945 * previously specified priority. This argument is only allowed if ordering by
13946 * child, value, or priority.
13947 */
13948function equalTo(value, key) {
13949 validateKey('equalTo', 'key', key, true);
13950 return new QueryEqualToValueConstraint(value, key);
13951}
13952/**
13953 * Creates a new immutable instance of `Query` that is extended to also include
13954 * additional query constraints.
13955 *
13956 * @param query - The Query instance to use as a base for the new constraints.
13957 * @param queryConstraints - The list of `QueryConstraint`s to apply.
13958 * @throws if any of the provided query constraints cannot be combined with the
13959 * existing or new constraints.
13960 */
13961function query(query) {
13962 var e_1, _a;
13963 var queryConstraints = [];
13964 for (var _i = 1; _i < arguments.length; _i++) {
13965 queryConstraints[_i - 1] = arguments[_i];
13966 }
13967 var queryImpl = util.getModularInstance(query);
13968 try {
13969 for (var queryConstraints_1 = tslib.__values(queryConstraints), queryConstraints_1_1 = queryConstraints_1.next(); !queryConstraints_1_1.done; queryConstraints_1_1 = queryConstraints_1.next()) {
13970 var constraint = queryConstraints_1_1.value;
13971 queryImpl = constraint._apply(queryImpl);
13972 }
13973 }
13974 catch (e_1_1) { e_1 = { error: e_1_1 }; }
13975 finally {
13976 try {
13977 if (queryConstraints_1_1 && !queryConstraints_1_1.done && (_a = queryConstraints_1.return)) _a.call(queryConstraints_1);
13978 }
13979 finally { if (e_1) throw e_1.error; }
13980 }
13981 return queryImpl;
13982}
13983/**
13984 * Define reference constructor in various modules
13985 *
13986 * We are doing this here to avoid several circular
13987 * dependency issues
13988 */
13989syncPointSetReferenceConstructor(ReferenceImpl);
13990syncTreeSetReferenceConstructor(ReferenceImpl);
13991
13992/**
13993 * @license
13994 * Copyright 2020 Google LLC
13995 *
13996 * Licensed under the Apache License, Version 2.0 (the "License");
13997 * you may not use this file except in compliance with the License.
13998 * You may obtain a copy of the License at
13999 *
14000 * http://www.apache.org/licenses/LICENSE-2.0
14001 *
14002 * Unless required by applicable law or agreed to in writing, software
14003 * distributed under the License is distributed on an "AS IS" BASIS,
14004 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14005 * See the License for the specific language governing permissions and
14006 * limitations under the License.
14007 */
14008/**
14009 * This variable is also defined in the firebase node.js admin SDK. Before
14010 * modifying this definition, consult the definition in:
14011 *
14012 * https://github.com/firebase/firebase-admin-node
14013 *
14014 * and make sure the two are consistent.
14015 */
14016var FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST';
14017/**
14018 * Creates and caches Repo instances.
14019 */
14020var repos = {};
14021/**
14022 * If true, new Repos will be created to use ReadonlyRestClient (for testing purposes).
14023 */
14024var useRestClient = false;
14025/**
14026 * Update an existing repo in place to point to a new host/port.
14027 */
14028function repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider) {
14029 repo.repoInfo_ = new RepoInfo(host + ":" + port,
14030 /* secure= */ false, repo.repoInfo_.namespace, repo.repoInfo_.webSocketOnly, repo.repoInfo_.nodeAdmin, repo.repoInfo_.persistenceKey, repo.repoInfo_.includeNamespaceInQueryParams);
14031 if (tokenProvider) {
14032 repo.authTokenProvider_ = tokenProvider;
14033 }
14034}
14035/**
14036 * This function should only ever be called to CREATE a new database instance.
14037 * @internal
14038 */
14039function repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url, nodeAdmin) {
14040 var dbUrl = url || app.options.databaseURL;
14041 if (dbUrl === undefined) {
14042 if (!app.options.projectId) {
14043 fatal("Can't determine Firebase Database URL. Be sure to include " +
14044 ' a Project ID when calling firebase.initializeApp().');
14045 }
14046 log('Using default host for project ', app.options.projectId);
14047 dbUrl = app.options.projectId + "-default-rtdb.firebaseio.com";
14048 }
14049 var parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14050 var repoInfo = parsedUrl.repoInfo;
14051 var isEmulator;
14052 var dbEmulatorHost = undefined;
14053 if (typeof process !== 'undefined') {
14054 dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR];
14055 }
14056 if (dbEmulatorHost) {
14057 isEmulator = true;
14058 dbUrl = "http://" + dbEmulatorHost + "?ns=" + repoInfo.namespace;
14059 parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14060 repoInfo = parsedUrl.repoInfo;
14061 }
14062 else {
14063 isEmulator = !parsedUrl.repoInfo.secure;
14064 }
14065 var authTokenProvider = nodeAdmin && isEmulator
14066 ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER)
14067 : new FirebaseAuthTokenProvider(app.name, app.options, authProvider);
14068 validateUrl('Invalid Firebase Database URL', parsedUrl);
14069 if (!pathIsEmpty(parsedUrl.path)) {
14070 fatal('Database URL must point to the root of a Firebase Database ' +
14071 '(not including a child path).');
14072 }
14073 var repo = repoManagerCreateRepo(repoInfo, app, authTokenProvider, new AppCheckTokenProvider(app.name, appCheckProvider));
14074 return new FirebaseDatabase(repo, app);
14075}
14076/**
14077 * Remove the repo and make sure it is disconnected.
14078 *
14079 */
14080function repoManagerDeleteRepo(repo, appName) {
14081 var appRepos = repos[appName];
14082 // This should never happen...
14083 if (!appRepos || appRepos[repo.key] !== repo) {
14084 fatal("Database " + appName + "(" + repo.repoInfo_ + ") has already been deleted.");
14085 }
14086 repoInterrupt(repo);
14087 delete appRepos[repo.key];
14088}
14089/**
14090 * Ensures a repo doesn't already exist and then creates one using the
14091 * provided app.
14092 *
14093 * @param repoInfo - The metadata about the Repo
14094 * @returns The Repo object for the specified server / repoName.
14095 */
14096function repoManagerCreateRepo(repoInfo, app, authTokenProvider, appCheckProvider) {
14097 var appRepos = repos[app.name];
14098 if (!appRepos) {
14099 appRepos = {};
14100 repos[app.name] = appRepos;
14101 }
14102 var repo = appRepos[repoInfo.toURLString()];
14103 if (repo) {
14104 fatal('Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.');
14105 }
14106 repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider);
14107 appRepos[repoInfo.toURLString()] = repo;
14108 return repo;
14109}
14110/**
14111 * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos.
14112 */
14113function repoManagerForceRestClient(forceRestClient) {
14114 useRestClient = forceRestClient;
14115}
14116/**
14117 * Class representing a Firebase Realtime Database.
14118 */
14119var FirebaseDatabase = /** @class */ (function () {
14120 /** @hideconstructor */
14121 function FirebaseDatabase(_repoInternal,
14122 /** The FirebaseApp associated with this Realtime Database instance. */
14123 app) {
14124 this._repoInternal = _repoInternal;
14125 this.app = app;
14126 /** Represents a database instance. */
14127 this['type'] = 'database';
14128 /** Track if the instance has been used (root or repo accessed) */
14129 this._instanceStarted = false;
14130 }
14131 Object.defineProperty(FirebaseDatabase.prototype, "_repo", {
14132 get: function () {
14133 if (!this._instanceStarted) {
14134 repoStart(this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride']);
14135 this._instanceStarted = true;
14136 }
14137 return this._repoInternal;
14138 },
14139 enumerable: false,
14140 configurable: true
14141 });
14142 Object.defineProperty(FirebaseDatabase.prototype, "_root", {
14143 get: function () {
14144 if (!this._rootInternal) {
14145 this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath());
14146 }
14147 return this._rootInternal;
14148 },
14149 enumerable: false,
14150 configurable: true
14151 });
14152 FirebaseDatabase.prototype._delete = function () {
14153 this._checkNotDeleted('delete');
14154 repoManagerDeleteRepo(this._repo, this.app.name);
14155 this._repoInternal = null;
14156 this._rootInternal = null;
14157 return Promise.resolve();
14158 };
14159 FirebaseDatabase.prototype._checkNotDeleted = function (apiName) {
14160 if (this._rootInternal === null) {
14161 fatal('Cannot call ' + apiName + ' on a deleted database.');
14162 }
14163 };
14164 return FirebaseDatabase;
14165}());
14166/**
14167 * Modify the provided instance to communicate with the Realtime Database
14168 * emulator.
14169 *
14170 * <p>Note: This method must be called before performing any other operation.
14171 *
14172 * @param db - The instance to modify.
14173 * @param host - The emulator host (ex: localhost)
14174 * @param port - The emulator port (ex: 8080)
14175 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
14176 */
14177function useDatabaseEmulator(db, host, port, options) {
14178 if (options === void 0) { options = {}; }
14179 db = util.getModularInstance(db);
14180 db._checkNotDeleted('useEmulator');
14181 if (db._instanceStarted) {
14182 fatal('Cannot call useEmulator() after instance has already been initialized.');
14183 }
14184 var repo = db._repoInternal;
14185 var tokenProvider = undefined;
14186 if (repo.repoInfo_.nodeAdmin) {
14187 if (options.mockUserToken) {
14188 fatal('mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".');
14189 }
14190 tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
14191 }
14192 else if (options.mockUserToken) {
14193 var token = util.createMockUserToken(options.mockUserToken, db.app.options.projectId);
14194 tokenProvider = new EmulatorTokenProvider(token);
14195 }
14196 // Modify the repo to apply emulator settings
14197 repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider);
14198}
14199/**
14200 * Disconnects from the server (all Database operations will be completed
14201 * offline).
14202 *
14203 * The client automatically maintains a persistent connection to the Database
14204 * server, which will remain active indefinitely and reconnect when
14205 * disconnected. However, the `goOffline()` and `goOnline()` methods may be used
14206 * to control the client connection in cases where a persistent connection is
14207 * undesirable.
14208 *
14209 * While offline, the client will no longer receive data updates from the
14210 * Database. However, all Database operations performed locally will continue to
14211 * immediately fire events, allowing your application to continue behaving
14212 * normally. Additionally, each operation performed locally will automatically
14213 * be queued and retried upon reconnection to the Database server.
14214 *
14215 * To reconnect to the Database and begin receiving remote events, see
14216 * `goOnline()`.
14217 *
14218 * @param db - The instance to disconnect.
14219 */
14220function goOffline(db) {
14221 db = util.getModularInstance(db);
14222 db._checkNotDeleted('goOffline');
14223 repoInterrupt(db._repo);
14224}
14225/**
14226 * Reconnects to the server and synchronizes the offline Database state
14227 * with the server state.
14228 *
14229 * This method should be used after disabling the active connection with
14230 * `goOffline()`. Once reconnected, the client will transmit the proper data
14231 * and fire the appropriate events so that your client "catches up"
14232 * automatically.
14233 *
14234 * @param db - The instance to reconnect.
14235 */
14236function goOnline(db) {
14237 db = util.getModularInstance(db);
14238 db._checkNotDeleted('goOnline');
14239 repoResume(db._repo);
14240}
14241function enableLogging$1(logger, persistent) {
14242 enableLogging(logger, persistent);
14243}
14244
14245/**
14246 * @license
14247 * Copyright 2020 Google LLC
14248 *
14249 * Licensed under the Apache License, Version 2.0 (the "License");
14250 * you may not use this file except in compliance with the License.
14251 * You may obtain a copy of the License at
14252 *
14253 * http://www.apache.org/licenses/LICENSE-2.0
14254 *
14255 * Unless required by applicable law or agreed to in writing, software
14256 * distributed under the License is distributed on an "AS IS" BASIS,
14257 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14258 * See the License for the specific language governing permissions and
14259 * limitations under the License.
14260 */
14261var SERVER_TIMESTAMP = {
14262 '.sv': 'timestamp'
14263};
14264/**
14265 * Returns a placeholder value for auto-populating the current timestamp (time
14266 * since the Unix epoch, in milliseconds) as determined by the Firebase
14267 * servers.
14268 */
14269function serverTimestamp() {
14270 return SERVER_TIMESTAMP;
14271}
14272/**
14273 * Returns a placeholder value that can be used to atomically increment the
14274 * current database value by the provided delta.
14275 *
14276 * @param delta - the amount to modify the current value atomically.
14277 * @returns A placeholder value for modifying data atomically server-side.
14278 */
14279function increment(delta) {
14280 return {
14281 '.sv': {
14282 'increment': delta
14283 }
14284 };
14285}
14286
14287/**
14288 * @license
14289 * Copyright 2020 Google LLC
14290 *
14291 * Licensed under the Apache License, Version 2.0 (the "License");
14292 * you may not use this file except in compliance with the License.
14293 * You may obtain a copy of the License at
14294 *
14295 * http://www.apache.org/licenses/LICENSE-2.0
14296 *
14297 * Unless required by applicable law or agreed to in writing, software
14298 * distributed under the License is distributed on an "AS IS" BASIS,
14299 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14300 * See the License for the specific language governing permissions and
14301 * limitations under the License.
14302 */
14303/**
14304 * A type for the resolve value of Firebase.transaction.
14305 */
14306var TransactionResult = /** @class */ (function () {
14307 /** @hideconstructor */
14308 function TransactionResult(
14309 /** Whether the transaction was successfully committed. */
14310 committed,
14311 /** The resulting data snapshot. */
14312 snapshot) {
14313 this.committed = committed;
14314 this.snapshot = snapshot;
14315 }
14316 /** Returns a JSON-serializable representation of this object. */
14317 TransactionResult.prototype.toJSON = function () {
14318 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14319 };
14320 return TransactionResult;
14321}());
14322/**
14323 * Atomically modifies the data at this location.
14324 *
14325 * Atomically modify the data at this location. Unlike a normal `set()`, which
14326 * just overwrites the data regardless of its previous value, `transaction()` is
14327 * used to modify the existing value to a new value, ensuring there are no
14328 * conflicts with other clients writing to the same location at the same time.
14329 *
14330 * To accomplish this, you pass `runTransaction()` an update function which is
14331 * used to transform the current value into a new value. If another client
14332 * writes to the location before your new value is successfully written, your
14333 * update function will be called again with the new current value, and the
14334 * write will be retried. This will happen repeatedly until your write succeeds
14335 * without conflict or you abort the transaction by not returning a value from
14336 * your update function.
14337 *
14338 * Note: Modifying data with `set()` will cancel any pending transactions at
14339 * that location, so extreme care should be taken if mixing `set()` and
14340 * `transaction()` to update the same data.
14341 *
14342 * Note: When using transactions with Security and Firebase Rules in place, be
14343 * aware that a client needs `.read` access in addition to `.write` access in
14344 * order to perform a transaction. This is because the client-side nature of
14345 * transactions requires the client to read the data in order to transactionally
14346 * update it.
14347 *
14348 * @param ref - The location to atomically modify.
14349 * @param transactionUpdate - A developer-supplied function which will be passed
14350 * the current data stored at this location (as a JavaScript object). The
14351 * function should return the new value it would like written (as a JavaScript
14352 * object). If `undefined` is returned (i.e. you return with no arguments) the
14353 * transaction will be aborted and the data at this location will not be
14354 * modified.
14355 * @param options - An options object to configure transactions.
14356 * @returns A Promise that can optionally be used instead of the onComplete
14357 * callback to handle success and failure.
14358 */
14359function runTransaction(ref,
14360// eslint-disable-next-line @typescript-eslint/no-explicit-any
14361transactionUpdate, options) {
14362 var _a;
14363 ref = util.getModularInstance(ref);
14364 validateWritablePath('Reference.transaction', ref._path);
14365 if (ref.key === '.length' || ref.key === '.keys') {
14366 throw ('Reference.transaction failed: ' + ref.key + ' is a read-only object.');
14367 }
14368 var applyLocally = (_a = options === null || options === void 0 ? void 0 : options.applyLocally) !== null && _a !== void 0 ? _a : true;
14369 var deferred = new util.Deferred();
14370 var promiseComplete = function (error, committed, node) {
14371 var dataSnapshot = null;
14372 if (error) {
14373 deferred.reject(error);
14374 }
14375 else {
14376 dataSnapshot = new DataSnapshot(node, new ReferenceImpl(ref._repo, ref._path), PRIORITY_INDEX);
14377 deferred.resolve(new TransactionResult(committed, dataSnapshot));
14378 }
14379 };
14380 // Add a watch to make sure we get server updates.
14381 var unwatcher = onValue(ref, function () { });
14382 repoStartTransaction(ref._repo, ref._path, transactionUpdate, promiseComplete, unwatcher, applyLocally);
14383 return deferred.promise;
14384}
14385
14386/**
14387 * @license
14388 * Copyright 2017 Google LLC
14389 *
14390 * Licensed under the Apache License, Version 2.0 (the "License");
14391 * you may not use this file except in compliance with the License.
14392 * You may obtain a copy of the License at
14393 *
14394 * http://www.apache.org/licenses/LICENSE-2.0
14395 *
14396 * Unless required by applicable law or agreed to in writing, software
14397 * distributed under the License is distributed on an "AS IS" BASIS,
14398 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14399 * See the License for the specific language governing permissions and
14400 * limitations under the License.
14401 */
14402var OnDisconnect$1 = /** @class */ (function () {
14403 function OnDisconnect(_delegate) {
14404 this._delegate = _delegate;
14405 }
14406 OnDisconnect.prototype.cancel = function (onComplete) {
14407 util.validateArgCount('OnDisconnect.cancel', 0, 1, arguments.length);
14408 util.validateCallback('OnDisconnect.cancel', 'onComplete', onComplete, true);
14409 var result = this._delegate.cancel();
14410 if (onComplete) {
14411 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14412 }
14413 return result;
14414 };
14415 OnDisconnect.prototype.remove = function (onComplete) {
14416 util.validateArgCount('OnDisconnect.remove', 0, 1, arguments.length);
14417 util.validateCallback('OnDisconnect.remove', 'onComplete', onComplete, true);
14418 var result = this._delegate.remove();
14419 if (onComplete) {
14420 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14421 }
14422 return result;
14423 };
14424 OnDisconnect.prototype.set = function (value, onComplete) {
14425 util.validateArgCount('OnDisconnect.set', 1, 2, arguments.length);
14426 util.validateCallback('OnDisconnect.set', 'onComplete', onComplete, true);
14427 var result = this._delegate.set(value);
14428 if (onComplete) {
14429 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14430 }
14431 return result;
14432 };
14433 OnDisconnect.prototype.setWithPriority = function (value, priority, onComplete) {
14434 util.validateArgCount('OnDisconnect.setWithPriority', 2, 3, arguments.length);
14435 util.validateCallback('OnDisconnect.setWithPriority', 'onComplete', onComplete, true);
14436 var result = this._delegate.setWithPriority(value, priority);
14437 if (onComplete) {
14438 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14439 }
14440 return result;
14441 };
14442 OnDisconnect.prototype.update = function (objectToMerge, onComplete) {
14443 util.validateArgCount('OnDisconnect.update', 1, 2, arguments.length);
14444 if (Array.isArray(objectToMerge)) {
14445 var newObjectToMerge = {};
14446 for (var i = 0; i < objectToMerge.length; ++i) {
14447 newObjectToMerge['' + i] = objectToMerge[i];
14448 }
14449 objectToMerge = newObjectToMerge;
14450 warn('Passing an Array to firebase.database.onDisconnect().update() is deprecated. Use set() if you want to overwrite the ' +
14451 'existing data, or an Object with integer keys if you really do want to only update some of the children.');
14452 }
14453 util.validateCallback('OnDisconnect.update', 'onComplete', onComplete, true);
14454 var result = this._delegate.update(objectToMerge);
14455 if (onComplete) {
14456 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14457 }
14458 return result;
14459 };
14460 return OnDisconnect;
14461}());
14462
14463/**
14464 * @license
14465 * Copyright 2017 Google LLC
14466 *
14467 * Licensed under the Apache License, Version 2.0 (the "License");
14468 * you may not use this file except in compliance with the License.
14469 * You may obtain a copy of the License at
14470 *
14471 * http://www.apache.org/licenses/LICENSE-2.0
14472 *
14473 * Unless required by applicable law or agreed to in writing, software
14474 * distributed under the License is distributed on an "AS IS" BASIS,
14475 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14476 * See the License for the specific language governing permissions and
14477 * limitations under the License.
14478 */
14479var TransactionResult$1 = /** @class */ (function () {
14480 /**
14481 * A type for the resolve value of Firebase.transaction.
14482 */
14483 function TransactionResult(committed, snapshot) {
14484 this.committed = committed;
14485 this.snapshot = snapshot;
14486 }
14487 // Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary
14488 // for end-users
14489 TransactionResult.prototype.toJSON = function () {
14490 util.validateArgCount('TransactionResult.toJSON', 0, 1, arguments.length);
14491 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14492 };
14493 return TransactionResult;
14494}());
14495
14496/**
14497 * @license
14498 * Copyright 2017 Google LLC
14499 *
14500 * Licensed under the Apache License, Version 2.0 (the "License");
14501 * you may not use this file except in compliance with the License.
14502 * You may obtain a copy of the License at
14503 *
14504 * http://www.apache.org/licenses/LICENSE-2.0
14505 *
14506 * Unless required by applicable law or agreed to in writing, software
14507 * distributed under the License is distributed on an "AS IS" BASIS,
14508 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14509 * See the License for the specific language governing permissions and
14510 * limitations under the License.
14511 */
14512/* eslint-enable @typescript-eslint/no-explicit-any */
14513/**
14514 * Class representing a firebase data snapshot. It wraps a SnapshotNode and
14515 * surfaces the public methods (val, forEach, etc.) we want to expose.
14516 */
14517var DataSnapshot$1 = /** @class */ (function () {
14518 function DataSnapshot(_database, _delegate) {
14519 this._database = _database;
14520 this._delegate = _delegate;
14521 }
14522 /**
14523 * Retrieves the snapshot contents as JSON. Returns null if the snapshot is
14524 * empty.
14525 *
14526 * @returns JSON representation of the DataSnapshot contents, or null if empty.
14527 */
14528 DataSnapshot.prototype.val = function () {
14529 util.validateArgCount('DataSnapshot.val', 0, 0, arguments.length);
14530 return this._delegate.val();
14531 };
14532 /**
14533 * Returns the snapshot contents as JSON, including priorities of node. Suitable for exporting
14534 * the entire node contents.
14535 * @returns JSON representation of the DataSnapshot contents, or null if empty.
14536 */
14537 DataSnapshot.prototype.exportVal = function () {
14538 util.validateArgCount('DataSnapshot.exportVal', 0, 0, arguments.length);
14539 return this._delegate.exportVal();
14540 };
14541 // Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary
14542 // for end-users
14543 DataSnapshot.prototype.toJSON = function () {
14544 // Optional spacer argument is unnecessary because we're depending on recursion rather than stringifying the content
14545 util.validateArgCount('DataSnapshot.toJSON', 0, 1, arguments.length);
14546 return this._delegate.toJSON();
14547 };
14548 /**
14549 * Returns whether the snapshot contains a non-null value.
14550 *
14551 * @returns Whether the snapshot contains a non-null value, or is empty.
14552 */
14553 DataSnapshot.prototype.exists = function () {
14554 util.validateArgCount('DataSnapshot.exists', 0, 0, arguments.length);
14555 return this._delegate.exists();
14556 };
14557 /**
14558 * Returns a DataSnapshot of the specified child node's contents.
14559 *
14560 * @param path - Path to a child.
14561 * @returns DataSnapshot for child node.
14562 */
14563 DataSnapshot.prototype.child = function (path) {
14564 util.validateArgCount('DataSnapshot.child', 0, 1, arguments.length);
14565 // Ensure the childPath is a string (can be a number)
14566 path = String(path);
14567 validatePathString('DataSnapshot.child', 'path', path, false);
14568 return new DataSnapshot(this._database, this._delegate.child(path));
14569 };
14570 /**
14571 * Returns whether the snapshot contains a child at the specified path.
14572 *
14573 * @param path - Path to a child.
14574 * @returns Whether the child exists.
14575 */
14576 DataSnapshot.prototype.hasChild = function (path) {
14577 util.validateArgCount('DataSnapshot.hasChild', 1, 1, arguments.length);
14578 validatePathString('DataSnapshot.hasChild', 'path', path, false);
14579 return this._delegate.hasChild(path);
14580 };
14581 /**
14582 * Returns the priority of the object, or null if no priority was set.
14583 *
14584 * @returns The priority.
14585 */
14586 DataSnapshot.prototype.getPriority = function () {
14587 util.validateArgCount('DataSnapshot.getPriority', 0, 0, arguments.length);
14588 return this._delegate.priority;
14589 };
14590 /**
14591 * Iterates through child nodes and calls the specified action for each one.
14592 *
14593 * @param action - Callback function to be called
14594 * for each child.
14595 * @returns True if forEach was canceled by action returning true for
14596 * one of the child nodes.
14597 */
14598 DataSnapshot.prototype.forEach = function (action) {
14599 var _this = this;
14600 util.validateArgCount('DataSnapshot.forEach', 1, 1, arguments.length);
14601 util.validateCallback('DataSnapshot.forEach', 'action', action, false);
14602 return this._delegate.forEach(function (expDataSnapshot) {
14603 return action(new DataSnapshot(_this._database, expDataSnapshot));
14604 });
14605 };
14606 /**
14607 * Returns whether this DataSnapshot has children.
14608 * @returns True if the DataSnapshot contains 1 or more child nodes.
14609 */
14610 DataSnapshot.prototype.hasChildren = function () {
14611 util.validateArgCount('DataSnapshot.hasChildren', 0, 0, arguments.length);
14612 return this._delegate.hasChildren();
14613 };
14614 Object.defineProperty(DataSnapshot.prototype, "key", {
14615 get: function () {
14616 return this._delegate.key;
14617 },
14618 enumerable: false,
14619 configurable: true
14620 });
14621 /**
14622 * Returns the number of children for this DataSnapshot.
14623 * @returns The number of children that this DataSnapshot contains.
14624 */
14625 DataSnapshot.prototype.numChildren = function () {
14626 util.validateArgCount('DataSnapshot.numChildren', 0, 0, arguments.length);
14627 return this._delegate.size;
14628 };
14629 /**
14630 * @returns The Firebase reference for the location this snapshot's data came
14631 * from.
14632 */
14633 DataSnapshot.prototype.getRef = function () {
14634 util.validateArgCount('DataSnapshot.ref', 0, 0, arguments.length);
14635 return new Reference(this._database, this._delegate.ref);
14636 };
14637 Object.defineProperty(DataSnapshot.prototype, "ref", {
14638 get: function () {
14639 return this.getRef();
14640 },
14641 enumerable: false,
14642 configurable: true
14643 });
14644 return DataSnapshot;
14645}());
14646/**
14647 * A Query represents a filter to be applied to a firebase location. This object purely represents the
14648 * query expression (and exposes our public API to build the query). The actual query logic is in ViewBase.js.
14649 *
14650 * Since every Firebase reference is a query, Firebase inherits from this object.
14651 */
14652var Query = /** @class */ (function () {
14653 function Query(database, _delegate) {
14654 this.database = database;
14655 this._delegate = _delegate;
14656 }
14657 Query.prototype.on = function (eventType, callback, cancelCallbackOrContext, context) {
14658 var _this = this;
14659 var _a;
14660 util.validateArgCount('Query.on', 2, 4, arguments.length);
14661 util.validateCallback('Query.on', 'callback', callback, false);
14662 var ret = Query.getCancelAndContextArgs_('Query.on', cancelCallbackOrContext, context);
14663 var valueCallback = function (expSnapshot, previousChildName) {
14664 callback.call(ret.context, new DataSnapshot$1(_this.database, expSnapshot), previousChildName);
14665 };
14666 valueCallback.userCallback = callback;
14667 valueCallback.context = ret.context;
14668 var cancelCallback = (_a = ret.cancel) === null || _a === void 0 ? void 0 : _a.bind(ret.context);
14669 switch (eventType) {
14670 case 'value':
14671 onValue(this._delegate, valueCallback, cancelCallback);
14672 return callback;
14673 case 'child_added':
14674 onChildAdded(this._delegate, valueCallback, cancelCallback);
14675 return callback;
14676 case 'child_removed':
14677 onChildRemoved(this._delegate, valueCallback, cancelCallback);
14678 return callback;
14679 case 'child_changed':
14680 onChildChanged(this._delegate, valueCallback, cancelCallback);
14681 return callback;
14682 case 'child_moved':
14683 onChildMoved(this._delegate, valueCallback, cancelCallback);
14684 return callback;
14685 default:
14686 throw new Error(util.errorPrefix('Query.on', 'eventType') +
14687 'must be a valid event type = "value", "child_added", "child_removed", ' +
14688 '"child_changed", or "child_moved".');
14689 }
14690 };
14691 Query.prototype.off = function (eventType, callback, context) {
14692 util.validateArgCount('Query.off', 0, 3, arguments.length);
14693 validateEventType('Query.off', eventType, true);
14694 util.validateCallback('Query.off', 'callback', callback, true);
14695 util.validateContextObject('Query.off', 'context', context, true);
14696 if (callback) {
14697 var valueCallback = function () { };
14698 valueCallback.userCallback = callback;
14699 valueCallback.context = context;
14700 off(this._delegate, eventType, valueCallback);
14701 }
14702 else {
14703 off(this._delegate, eventType);
14704 }
14705 };
14706 /**
14707 * Get the server-value for this query, or return a cached value if not connected.
14708 */
14709 Query.prototype.get = function () {
14710 var _this = this;
14711 return get(this._delegate).then(function (expSnapshot) {
14712 return new DataSnapshot$1(_this.database, expSnapshot);
14713 });
14714 };
14715 /**
14716 * Attaches a listener, waits for the first event, and then removes the listener
14717 */
14718 Query.prototype.once = function (eventType, callback, failureCallbackOrContext, context) {
14719 var _this = this;
14720 util.validateArgCount('Query.once', 1, 4, arguments.length);
14721 util.validateCallback('Query.once', 'callback', callback, true);
14722 var ret = Query.getCancelAndContextArgs_('Query.once', failureCallbackOrContext, context);
14723 var deferred = new util.Deferred();
14724 var valueCallback = function (expSnapshot, previousChildName) {
14725 var result = new DataSnapshot$1(_this.database, expSnapshot);
14726 if (callback) {
14727 callback.call(ret.context, result, previousChildName);
14728 }
14729 deferred.resolve(result);
14730 };
14731 valueCallback.userCallback = callback;
14732 valueCallback.context = ret.context;
14733 var cancelCallback = function (error) {
14734 if (ret.cancel) {
14735 ret.cancel.call(ret.context, error);
14736 }
14737 deferred.reject(error);
14738 };
14739 switch (eventType) {
14740 case 'value':
14741 onValue(this._delegate, valueCallback, cancelCallback, {
14742 onlyOnce: true
14743 });
14744 break;
14745 case 'child_added':
14746 onChildAdded(this._delegate, valueCallback, cancelCallback, {
14747 onlyOnce: true
14748 });
14749 break;
14750 case 'child_removed':
14751 onChildRemoved(this._delegate, valueCallback, cancelCallback, {
14752 onlyOnce: true
14753 });
14754 break;
14755 case 'child_changed':
14756 onChildChanged(this._delegate, valueCallback, cancelCallback, {
14757 onlyOnce: true
14758 });
14759 break;
14760 case 'child_moved':
14761 onChildMoved(this._delegate, valueCallback, cancelCallback, {
14762 onlyOnce: true
14763 });
14764 break;
14765 default:
14766 throw new Error(util.errorPrefix('Query.once', 'eventType') +
14767 'must be a valid event type = "value", "child_added", "child_removed", ' +
14768 '"child_changed", or "child_moved".');
14769 }
14770 return deferred.promise;
14771 };
14772 /**
14773 * Set a limit and anchor it to the start of the window.
14774 */
14775 Query.prototype.limitToFirst = function (limit) {
14776 util.validateArgCount('Query.limitToFirst', 1, 1, arguments.length);
14777 return new Query(this.database, query(this._delegate, limitToFirst(limit)));
14778 };
14779 /**
14780 * Set a limit and anchor it to the end of the window.
14781 */
14782 Query.prototype.limitToLast = function (limit) {
14783 util.validateArgCount('Query.limitToLast', 1, 1, arguments.length);
14784 return new Query(this.database, query(this._delegate, limitToLast(limit)));
14785 };
14786 /**
14787 * Given a child path, return a new query ordered by the specified grandchild path.
14788 */
14789 Query.prototype.orderByChild = function (path) {
14790 util.validateArgCount('Query.orderByChild', 1, 1, arguments.length);
14791 return new Query(this.database, query(this._delegate, orderByChild(path)));
14792 };
14793 /**
14794 * Return a new query ordered by the KeyIndex
14795 */
14796 Query.prototype.orderByKey = function () {
14797 util.validateArgCount('Query.orderByKey', 0, 0, arguments.length);
14798 return new Query(this.database, query(this._delegate, orderByKey()));
14799 };
14800 /**
14801 * Return a new query ordered by the PriorityIndex
14802 */
14803 Query.prototype.orderByPriority = function () {
14804 util.validateArgCount('Query.orderByPriority', 0, 0, arguments.length);
14805 return new Query(this.database, query(this._delegate, orderByPriority()));
14806 };
14807 /**
14808 * Return a new query ordered by the ValueIndex
14809 */
14810 Query.prototype.orderByValue = function () {
14811 util.validateArgCount('Query.orderByValue', 0, 0, arguments.length);
14812 return new Query(this.database, query(this._delegate, orderByValue()));
14813 };
14814 Query.prototype.startAt = function (value, name) {
14815 if (value === void 0) { value = null; }
14816 util.validateArgCount('Query.startAt', 0, 2, arguments.length);
14817 return new Query(this.database, query(this._delegate, startAt(value, name)));
14818 };
14819 Query.prototype.startAfter = function (value, name) {
14820 if (value === void 0) { value = null; }
14821 util.validateArgCount('Query.startAfter', 0, 2, arguments.length);
14822 return new Query(this.database, query(this._delegate, startAfter(value, name)));
14823 };
14824 Query.prototype.endAt = function (value, name) {
14825 if (value === void 0) { value = null; }
14826 util.validateArgCount('Query.endAt', 0, 2, arguments.length);
14827 return new Query(this.database, query(this._delegate, endAt(value, name)));
14828 };
14829 Query.prototype.endBefore = function (value, name) {
14830 if (value === void 0) { value = null; }
14831 util.validateArgCount('Query.endBefore', 0, 2, arguments.length);
14832 return new Query(this.database, query(this._delegate, endBefore(value, name)));
14833 };
14834 /**
14835 * Load the selection of children with exactly the specified value, and, optionally,
14836 * the specified name.
14837 */
14838 Query.prototype.equalTo = function (value, name) {
14839 util.validateArgCount('Query.equalTo', 1, 2, arguments.length);
14840 return new Query(this.database, query(this._delegate, equalTo(value, name)));
14841 };
14842 /**
14843 * @returns URL for this location.
14844 */
14845 Query.prototype.toString = function () {
14846 util.validateArgCount('Query.toString', 0, 0, arguments.length);
14847 return this._delegate.toString();
14848 };
14849 // Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary
14850 // for end-users.
14851 Query.prototype.toJSON = function () {
14852 // An optional spacer argument is unnecessary for a string.
14853 util.validateArgCount('Query.toJSON', 0, 1, arguments.length);
14854 return this._delegate.toJSON();
14855 };
14856 /**
14857 * Return true if this query and the provided query are equivalent; otherwise, return false.
14858 */
14859 Query.prototype.isEqual = function (other) {
14860 util.validateArgCount('Query.isEqual', 1, 1, arguments.length);
14861 if (!(other instanceof Query)) {
14862 var error = 'Query.isEqual failed: First argument must be an instance of firebase.database.Query.';
14863 throw new Error(error);
14864 }
14865 return this._delegate.isEqual(other._delegate);
14866 };
14867 /**
14868 * Helper used by .on and .once to extract the context and or cancel arguments.
14869 * @param fnName - The function name (on or once)
14870 *
14871 */
14872 Query.getCancelAndContextArgs_ = function (fnName, cancelOrContext, context) {
14873 var ret = { cancel: undefined, context: undefined };
14874 if (cancelOrContext && context) {
14875 ret.cancel = cancelOrContext;
14876 util.validateCallback(fnName, 'cancel', ret.cancel, true);
14877 ret.context = context;
14878 util.validateContextObject(fnName, 'context', ret.context, true);
14879 }
14880 else if (cancelOrContext) {
14881 // we have either a cancel callback or a context.
14882 if (typeof cancelOrContext === 'object' && cancelOrContext !== null) {
14883 // it's a context!
14884 ret.context = cancelOrContext;
14885 }
14886 else if (typeof cancelOrContext === 'function') {
14887 ret.cancel = cancelOrContext;
14888 }
14889 else {
14890 throw new Error(util.errorPrefix(fnName, 'cancelOrContext') +
14891 ' must either be a cancel callback or a context object.');
14892 }
14893 }
14894 return ret;
14895 };
14896 Object.defineProperty(Query.prototype, "ref", {
14897 get: function () {
14898 return new Reference(this.database, new ReferenceImpl(this._delegate._repo, this._delegate._path));
14899 },
14900 enumerable: false,
14901 configurable: true
14902 });
14903 return Query;
14904}());
14905var Reference = /** @class */ (function (_super) {
14906 tslib.__extends(Reference, _super);
14907 /**
14908 * Call options:
14909 * new Reference(Repo, Path) or
14910 * new Reference(url: string, string|RepoManager)
14911 *
14912 * Externally - this is the firebase.database.Reference type.
14913 */
14914 function Reference(database, _delegate) {
14915 var _this = _super.call(this, database, new QueryImpl(_delegate._repo, _delegate._path, new QueryParams(), false)) || this;
14916 _this.database = database;
14917 _this._delegate = _delegate;
14918 return _this;
14919 }
14920 /** @returns {?string} */
14921 Reference.prototype.getKey = function () {
14922 util.validateArgCount('Reference.key', 0, 0, arguments.length);
14923 return this._delegate.key;
14924 };
14925 Reference.prototype.child = function (pathString) {
14926 util.validateArgCount('Reference.child', 1, 1, arguments.length);
14927 if (typeof pathString === 'number') {
14928 pathString = String(pathString);
14929 }
14930 return new Reference(this.database, child(this._delegate, pathString));
14931 };
14932 /** @returns {?Reference} */
14933 Reference.prototype.getParent = function () {
14934 util.validateArgCount('Reference.parent', 0, 0, arguments.length);
14935 var parent = this._delegate.parent;
14936 return parent ? new Reference(this.database, parent) : null;
14937 };
14938 /** @returns {!Reference} */
14939 Reference.prototype.getRoot = function () {
14940 util.validateArgCount('Reference.root', 0, 0, arguments.length);
14941 return new Reference(this.database, this._delegate.root);
14942 };
14943 Reference.prototype.set = function (newVal, onComplete) {
14944 util.validateArgCount('Reference.set', 1, 2, arguments.length);
14945 util.validateCallback('Reference.set', 'onComplete', onComplete, true);
14946 var result = set(this._delegate, newVal);
14947 if (onComplete) {
14948 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14949 }
14950 return result;
14951 };
14952 Reference.prototype.update = function (values, onComplete) {
14953 util.validateArgCount('Reference.update', 1, 2, arguments.length);
14954 if (Array.isArray(values)) {
14955 var newObjectToMerge = {};
14956 for (var i = 0; i < values.length; ++i) {
14957 newObjectToMerge['' + i] = values[i];
14958 }
14959 values = newObjectToMerge;
14960 warn('Passing an Array to Firebase.update() is deprecated. ' +
14961 'Use set() if you want to overwrite the existing data, or ' +
14962 'an Object with integer keys if you really do want to ' +
14963 'only update some of the children.');
14964 }
14965 validateWritablePath('Reference.update', this._delegate._path);
14966 util.validateCallback('Reference.update', 'onComplete', onComplete, true);
14967 var result = update(this._delegate, values);
14968 if (onComplete) {
14969 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14970 }
14971 return result;
14972 };
14973 Reference.prototype.setWithPriority = function (newVal, newPriority, onComplete) {
14974 util.validateArgCount('Reference.setWithPriority', 2, 3, arguments.length);
14975 util.validateCallback('Reference.setWithPriority', 'onComplete', onComplete, true);
14976 var result = setWithPriority(this._delegate, newVal, newPriority);
14977 if (onComplete) {
14978 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14979 }
14980 return result;
14981 };
14982 Reference.prototype.remove = function (onComplete) {
14983 util.validateArgCount('Reference.remove', 0, 1, arguments.length);
14984 util.validateCallback('Reference.remove', 'onComplete', onComplete, true);
14985 var result = remove(this._delegate);
14986 if (onComplete) {
14987 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
14988 }
14989 return result;
14990 };
14991 Reference.prototype.transaction = function (transactionUpdate, onComplete, applyLocally) {
14992 var _this = this;
14993 util.validateArgCount('Reference.transaction', 1, 3, arguments.length);
14994 util.validateCallback('Reference.transaction', 'transactionUpdate', transactionUpdate, false);
14995 util.validateCallback('Reference.transaction', 'onComplete', onComplete, true);
14996 validateBoolean('Reference.transaction', 'applyLocally', applyLocally, true);
14997 var result = runTransaction(this._delegate, transactionUpdate, {
14998 applyLocally: applyLocally
14999 }).then(function (transactionResult) {
15000 return new TransactionResult$1(transactionResult.committed, new DataSnapshot$1(_this.database, transactionResult.snapshot));
15001 });
15002 if (onComplete) {
15003 result.then(function (transactionResult) {
15004 return onComplete(null, transactionResult.committed, transactionResult.snapshot);
15005 }, function (error) { return onComplete(error, false, null); });
15006 }
15007 return result;
15008 };
15009 Reference.prototype.setPriority = function (priority, onComplete) {
15010 util.validateArgCount('Reference.setPriority', 1, 2, arguments.length);
15011 util.validateCallback('Reference.setPriority', 'onComplete', onComplete, true);
15012 var result = setPriority(this._delegate, priority);
15013 if (onComplete) {
15014 result.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
15015 }
15016 return result;
15017 };
15018 Reference.prototype.push = function (value, onComplete) {
15019 var _this = this;
15020 util.validateArgCount('Reference.push', 0, 2, arguments.length);
15021 util.validateCallback('Reference.push', 'onComplete', onComplete, true);
15022 var expPromise = push(this._delegate, value);
15023 var promise = expPromise.then(function (expRef) { return new Reference(_this.database, expRef); });
15024 if (onComplete) {
15025 promise.then(function () { return onComplete(null); }, function (error) { return onComplete(error); });
15026 }
15027 var result = new Reference(this.database, expPromise);
15028 result.then = promise.then.bind(promise);
15029 result.catch = promise.catch.bind(promise, undefined);
15030 return result;
15031 };
15032 Reference.prototype.onDisconnect = function () {
15033 validateWritablePath('Reference.onDisconnect', this._delegate._path);
15034 return new OnDisconnect$1(new OnDisconnect(this._delegate._repo, this._delegate._path));
15035 };
15036 Object.defineProperty(Reference.prototype, "key", {
15037 get: function () {
15038 return this.getKey();
15039 },
15040 enumerable: false,
15041 configurable: true
15042 });
15043 Object.defineProperty(Reference.prototype, "parent", {
15044 get: function () {
15045 return this.getParent();
15046 },
15047 enumerable: false,
15048 configurable: true
15049 });
15050 Object.defineProperty(Reference.prototype, "root", {
15051 get: function () {
15052 return this.getRoot();
15053 },
15054 enumerable: false,
15055 configurable: true
15056 });
15057 return Reference;
15058}(Query));
15059
15060/**
15061 * @license
15062 * Copyright 2017 Google LLC
15063 *
15064 * Licensed under the Apache License, Version 2.0 (the "License");
15065 * you may not use this file except in compliance with the License.
15066 * You may obtain a copy of the License at
15067 *
15068 * http://www.apache.org/licenses/LICENSE-2.0
15069 *
15070 * Unless required by applicable law or agreed to in writing, software
15071 * distributed under the License is distributed on an "AS IS" BASIS,
15072 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15073 * See the License for the specific language governing permissions and
15074 * limitations under the License.
15075 */
15076/**
15077 * Class representing a firebase database.
15078 */
15079var Database = /** @class */ (function () {
15080 /**
15081 * The constructor should not be called by users of our public API.
15082 */
15083 function Database(_delegate, app) {
15084 var _this = this;
15085 this._delegate = _delegate;
15086 this.app = app;
15087 this.INTERNAL = {
15088 delete: function () { return _this._delegate._delete(); }
15089 };
15090 }
15091 /**
15092 * Modify this instance to communicate with the Realtime Database emulator.
15093 *
15094 * <p>Note: This method must be called before performing any other operation.
15095 *
15096 * @param host - the emulator host (ex: localhost)
15097 * @param port - the emulator port (ex: 8080)
15098 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
15099 */
15100 Database.prototype.useEmulator = function (host, port, options) {
15101 if (options === void 0) { options = {}; }
15102 useDatabaseEmulator(this._delegate, host, port, options);
15103 };
15104 Database.prototype.ref = function (path) {
15105 util.validateArgCount('database.ref', 0, 1, arguments.length);
15106 if (path instanceof Reference) {
15107 var childRef = refFromURL(this._delegate, path.toString());
15108 return new Reference(this, childRef);
15109 }
15110 else {
15111 var childRef = ref(this._delegate, path);
15112 return new Reference(this, childRef);
15113 }
15114 };
15115 /**
15116 * Returns a reference to the root or the path specified in url.
15117 * We throw a exception if the url is not in the same domain as the
15118 * current repo.
15119 * @returns Firebase reference.
15120 */
15121 Database.prototype.refFromURL = function (url) {
15122 var apiName = 'database.refFromURL';
15123 util.validateArgCount(apiName, 1, 1, arguments.length);
15124 var childRef = refFromURL(this._delegate, url);
15125 return new Reference(this, childRef);
15126 };
15127 // Make individual repo go offline.
15128 Database.prototype.goOffline = function () {
15129 util.validateArgCount('database.goOffline', 0, 0, arguments.length);
15130 return goOffline(this._delegate);
15131 };
15132 Database.prototype.goOnline = function () {
15133 util.validateArgCount('database.goOnline', 0, 0, arguments.length);
15134 return goOnline(this._delegate);
15135 };
15136 Database.ServerValue = {
15137 TIMESTAMP: serverTimestamp(),
15138 increment: function (delta) { return increment(delta); }
15139 };
15140 return Database;
15141}());
15142
15143/**
15144 * @license
15145 * Copyright 2017 Google LLC
15146 *
15147 * Licensed under the Apache License, Version 2.0 (the "License");
15148 * you may not use this file except in compliance with the License.
15149 * You may obtain a copy of the License at
15150 *
15151 * http://www.apache.org/licenses/LICENSE-2.0
15152 *
15153 * Unless required by applicable law or agreed to in writing, software
15154 * distributed under the License is distributed on an "AS IS" BASIS,
15155 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15156 * See the License for the specific language governing permissions and
15157 * limitations under the License.
15158 */
15159/**
15160 * INTERNAL methods for internal-use only (tests, etc.).
15161 *
15162 * Customers shouldn't use these or else should be aware that they could break at any time.
15163 */
15164var forceLongPolling = function () {
15165 WebSocketConnection.forceDisallow();
15166 BrowserPollConnection.forceAllow();
15167};
15168var forceWebSockets = function () {
15169 BrowserPollConnection.forceDisallow();
15170};
15171/* Used by App Manager */
15172var isWebSocketsAvailable = function () {
15173 return WebSocketConnection['isAvailable']();
15174};
15175var setSecurityDebugCallback = function (ref, callback) {
15176 var connection = ref._delegate._repo.persistentConnection_;
15177 // eslint-disable-next-line @typescript-eslint/no-explicit-any
15178 connection.securityDebugCallback_ = callback;
15179};
15180var stats = function (ref, showDelta) {
15181 repoStats(ref._delegate._repo, showDelta);
15182};
15183var statsIncrementCounter = function (ref, metric) {
15184 repoStatsIncrementCounter(ref._delegate._repo, metric);
15185};
15186var dataUpdateCount = function (ref) {
15187 return ref._delegate._repo.dataUpdateCount;
15188};
15189var interceptServerData = function (ref, callback) {
15190 return repoInterceptServerData(ref._delegate._repo, callback);
15191};
15192/**
15193 * Used by console to create a database based on the app,
15194 * passed database URL and a custom auth implementation.
15195 *
15196 * @param app - A valid FirebaseApp-like object
15197 * @param url - A valid Firebase databaseURL
15198 * @param version - custom version e.g. firebase-admin version
15199 * @param customAuthImpl - custom auth implementation
15200 */
15201function initStandalone(_a) {
15202 var app = _a.app, url = _a.url, version = _a.version, customAuthImpl = _a.customAuthImpl, namespace = _a.namespace, _b = _a.nodeAdmin, nodeAdmin = _b === void 0 ? false : _b;
15203 setSDKVersion(version);
15204 /**
15205 * ComponentContainer('database-standalone') is just a placeholder that doesn't perform
15206 * any actual function.
15207 */
15208 var authProvider = new component.Provider('auth-internal', new component.ComponentContainer('database-standalone'));
15209 authProvider.setComponent(new component.Component('auth-internal', function () { return customAuthImpl; }, "PRIVATE" /* PRIVATE */));
15210 return {
15211 instance: new Database(repoManagerDatabaseFromApp(app, authProvider,
15212 /* appCheckProvider= */ undefined, url, nodeAdmin), app),
15213 namespace: namespace
15214 };
15215}
15216
15217var INTERNAL = /*#__PURE__*/Object.freeze({
15218 __proto__: null,
15219 forceLongPolling: forceLongPolling,
15220 forceWebSockets: forceWebSockets,
15221 isWebSocketsAvailable: isWebSocketsAvailable,
15222 setSecurityDebugCallback: setSecurityDebugCallback,
15223 stats: stats,
15224 statsIncrementCounter: statsIncrementCounter,
15225 dataUpdateCount: dataUpdateCount,
15226 interceptServerData: interceptServerData,
15227 initStandalone: initStandalone
15228});
15229
15230/**
15231 * @license
15232 * Copyright 2017 Google LLC
15233 *
15234 * Licensed under the Apache License, Version 2.0 (the "License");
15235 * you may not use this file except in compliance with the License.
15236 * You may obtain a copy of the License at
15237 *
15238 * http://www.apache.org/licenses/LICENSE-2.0
15239 *
15240 * Unless required by applicable law or agreed to in writing, software
15241 * distributed under the License is distributed on an "AS IS" BASIS,
15242 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15243 * See the License for the specific language governing permissions and
15244 * limitations under the License.
15245 */
15246var DataConnection = PersistentConnection;
15247// eslint-disable-next-line @typescript-eslint/no-explicit-any
15248PersistentConnection.prototype.simpleListen = function (pathString, onComplete) {
15249 this.sendRequest('q', { p: pathString }, onComplete);
15250};
15251// eslint-disable-next-line @typescript-eslint/no-explicit-any
15252PersistentConnection.prototype.echo = function (data, onEcho) {
15253 this.sendRequest('echo', { d: data }, onEcho);
15254};
15255// RealTimeConnection properties that we use in tests.
15256var RealTimeConnection = Connection;
15257var hijackHash = function (newHash) {
15258 var oldPut = PersistentConnection.prototype.put;
15259 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
15260 if (hash !== undefined) {
15261 hash = newHash();
15262 }
15263 oldPut.call(this, pathString, data, onComplete, hash);
15264 };
15265 return function () {
15266 PersistentConnection.prototype.put = oldPut;
15267 };
15268};
15269var ConnectionTarget = RepoInfo;
15270var queryIdentifier = function (query) {
15271 return query._delegate._queryIdentifier;
15272};
15273/**
15274 * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection.
15275 */
15276var forceRestClient = function (forceRestClient) {
15277 repoManagerForceRestClient(forceRestClient);
15278};
15279
15280var TEST_ACCESS = /*#__PURE__*/Object.freeze({
15281 __proto__: null,
15282 DataConnection: DataConnection,
15283 RealTimeConnection: RealTimeConnection,
15284 hijackHash: hijackHash,
15285 ConnectionTarget: ConnectionTarget,
15286 queryIdentifier: queryIdentifier,
15287 forceRestClient: forceRestClient
15288});
15289
15290/**
15291 * @license
15292 * Copyright 2017 Google LLC
15293 *
15294 * Licensed under the Apache License, Version 2.0 (the "License");
15295 * you may not use this file except in compliance with the License.
15296 * You may obtain a copy of the License at
15297 *
15298 * http://www.apache.org/licenses/LICENSE-2.0
15299 *
15300 * Unless required by applicable law or agreed to in writing, software
15301 * distributed under the License is distributed on an "AS IS" BASIS,
15302 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15303 * See the License for the specific language governing permissions and
15304 * limitations under the License.
15305 */
15306setWebSocketImpl(fayeWebsocket.Client);
15307var ServerValue = Database.ServerValue;
15308/**
15309 * A one off register function which returns a database based on the app and
15310 * passed database URL. (Used by the Admin SDK)
15311 *
15312 * @param app - A valid FirebaseApp-like object
15313 * @param url - A valid Firebase databaseURL
15314 * @param version - custom version e.g. firebase-admin version
15315 * @param nodeAdmin - true if the SDK is being initialized from Firebase Admin.
15316 */
15317function initStandalone$1(app, url, version, nodeAdmin) {
15318 if (nodeAdmin === void 0) { nodeAdmin = true; }
15319 util.CONSTANTS.NODE_ADMIN = nodeAdmin;
15320 return initStandalone({
15321 app: app,
15322 url: url,
15323 version: version,
15324 // firebase-admin-node's app.INTERNAL implements FirebaseAuthInternal interface
15325 // eslint-disable-next-line @typescript-eslint/no-explicit-any
15326 customAuthImpl: app.INTERNAL,
15327 namespace: {
15328 Reference: Reference,
15329 Query: Query,
15330 Database: Database,
15331 DataSnapshot: DataSnapshot$1,
15332 enableLogging: enableLogging$1,
15333 INTERNAL: INTERNAL,
15334 ServerValue: ServerValue,
15335 TEST_ACCESS: TEST_ACCESS
15336 },
15337 nodeAdmin: nodeAdmin
15338 });
15339}
15340function registerDatabase(instance) {
15341 // set SDK_VERSION
15342 setSDKVersion(instance.SDK_VERSION);
15343 // Register the Database Service with the 'firebase' namespace.
15344 var namespace = instance.INTERNAL.registerComponent(new component.Component('database', function (container, _a) {
15345 var url = _a.instanceIdentifier;
15346 /* Dependencies */
15347 // getImmediate for FirebaseApp will always succeed
15348 var app = container.getProvider('app').getImmediate();
15349 var authProvider = container.getProvider('auth-internal');
15350 var appCheckProvider = container.getProvider('app-check-internal');
15351 return new Database(repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url), app);
15352 }, "PUBLIC" /* PUBLIC */)
15353 .setServiceProps(
15354 // firebase.database namespace properties
15355 {
15356 Reference: Reference,
15357 Query: Query,
15358 Database: Database,
15359 DataSnapshot: DataSnapshot$1,
15360 enableLogging: enableLogging$1,
15361 INTERNAL: INTERNAL,
15362 ServerValue: ServerValue,
15363 TEST_ACCESS: TEST_ACCESS
15364 })
15365 .setMultipleInstances(true));
15366 instance.registerVersion(name, version, 'node');
15367 if (util.isNodeSdk()) {
15368 module.exports = Object.assign({}, namespace, { initStandalone: initStandalone$1 });
15369 }
15370}
15371try {
15372 // If @firebase/app is not present, skip registering database.
15373 // It could happen when this package is used in firebase-admin which doesn't depend on @firebase/app.
15374 // Previously firebase-admin depends on @firebase/app, which causes version conflict on
15375 // @firebase/app when used together with the js sdk. More detail:
15376 // https://github.com/firebase/firebase-js-sdk/issues/1696#issuecomment-501546596
15377 // eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-require-imports
15378 var firebase = require('@firebase/app').default; // Only present for v8, undefined for v9 (should skip).
15379 if (firebase) {
15380 registerDatabase(firebase);
15381 }
15382}
15383catch (err) {
15384 // catch and ignore 'MODULE_NOT_FOUND' error in firebase-admin context
15385 // we can safely ignore this error because RTDB in firebase-admin works without @firebase/app
15386 if (err.code !== 'MODULE_NOT_FOUND') {
15387 throw err;
15388 }
15389}
15390
15391exports.DataSnapshot = DataSnapshot$1;
15392exports.Database = Database;
15393exports.OnDisconnect = OnDisconnect$1;
15394exports.Query = Query;
15395exports.Reference = Reference;
15396exports.ServerValue = ServerValue;
15397exports.enableLogging = enableLogging$1;
15398exports.initStandalone = initStandalone$1;
15399exports.registerDatabase = registerDatabase;
15400//# sourceMappingURL=index.node.cjs.js.map