UNPKG

604 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var Websocket = require('faye-websocket');
6var util = require('@firebase/util');
7var tslib = require('tslib');
8var logger$1 = require('@firebase/logger');
9var app = require('@firebase/app');
10var component = require('@firebase/component');
11
12function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
14var Websocket__default = /*#__PURE__*/_interopDefaultLegacy(Websocket);
15
16/**
17 * @license
18 * Copyright 2017 Google LLC
19 *
20 * Licensed under the Apache License, Version 2.0 (the "License");
21 * you may not use this file except in compliance with the License.
22 * You may obtain a copy of the License at
23 *
24 * http://www.apache.org/licenses/LICENSE-2.0
25 *
26 * Unless required by applicable law or agreed to in writing, software
27 * distributed under the License is distributed on an "AS IS" BASIS,
28 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29 * See the License for the specific language governing permissions and
30 * limitations under the License.
31 */
32var PROTOCOL_VERSION = '5';
33var VERSION_PARAM = 'v';
34var TRANSPORT_SESSION_PARAM = 's';
35var REFERER_PARAM = 'r';
36var FORGE_REF = 'f';
37// Matches console.firebase.google.com, firebase-console-*.corp.google.com and
38// firebase.corp.google.com
39var FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/;
40var LAST_SESSION_PARAM = 'ls';
41var APPLICATION_ID_PARAM = 'p';
42var APP_CHECK_TOKEN_PARAM = 'ac';
43var WEBSOCKET = 'websocket';
44var LONG_POLLING = 'long_polling';
45
46/**
47 * @license
48 * Copyright 2017 Google LLC
49 *
50 * Licensed under the Apache License, Version 2.0 (the "License");
51 * you may not use this file except in compliance with the License.
52 * You may obtain a copy of the License at
53 *
54 * http://www.apache.org/licenses/LICENSE-2.0
55 *
56 * Unless required by applicable law or agreed to in writing, software
57 * distributed under the License is distributed on an "AS IS" BASIS,
58 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
59 * See the License for the specific language governing permissions and
60 * limitations under the License.
61 */
62/**
63 * Wraps a DOM Storage object and:
64 * - automatically encode objects as JSON strings before storing them to allow us to store arbitrary types.
65 * - prefixes names with "firebase:" to avoid collisions with app data.
66 *
67 * We automatically (see storage.js) create two such wrappers, one for sessionStorage,
68 * and one for localStorage.
69 *
70 */
71var DOMStorageWrapper = /** @class */ (function () {
72 /**
73 * @param domStorage_ - The underlying storage object (e.g. localStorage or sessionStorage)
74 */
75 function DOMStorageWrapper(domStorage_) {
76 this.domStorage_ = domStorage_;
77 // Use a prefix to avoid collisions with other stuff saved by the app.
78 this.prefix_ = 'firebase:';
79 }
80 /**
81 * @param key - The key to save the value under
82 * @param value - The value being stored, or null to remove the key.
83 */
84 DOMStorageWrapper.prototype.set = function (key, value) {
85 if (value == null) {
86 this.domStorage_.removeItem(this.prefixedName_(key));
87 }
88 else {
89 this.domStorage_.setItem(this.prefixedName_(key), util.stringify(value));
90 }
91 };
92 /**
93 * @returns The value that was stored under this key, or null
94 */
95 DOMStorageWrapper.prototype.get = function (key) {
96 var storedVal = this.domStorage_.getItem(this.prefixedName_(key));
97 if (storedVal == null) {
98 return null;
99 }
100 else {
101 return util.jsonEval(storedVal);
102 }
103 };
104 DOMStorageWrapper.prototype.remove = function (key) {
105 this.domStorage_.removeItem(this.prefixedName_(key));
106 };
107 DOMStorageWrapper.prototype.prefixedName_ = function (name) {
108 return this.prefix_ + name;
109 };
110 DOMStorageWrapper.prototype.toString = function () {
111 return this.domStorage_.toString();
112 };
113 return DOMStorageWrapper;
114}());
115
116/**
117 * @license
118 * Copyright 2017 Google LLC
119 *
120 * Licensed under the Apache License, Version 2.0 (the "License");
121 * you may not use this file except in compliance with the License.
122 * You may obtain a copy of the License at
123 *
124 * http://www.apache.org/licenses/LICENSE-2.0
125 *
126 * Unless required by applicable law or agreed to in writing, software
127 * distributed under the License is distributed on an "AS IS" BASIS,
128 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
129 * See the License for the specific language governing permissions and
130 * limitations under the License.
131 */
132/**
133 * An in-memory storage implementation that matches the API of DOMStorageWrapper
134 * (TODO: create interface for both to implement).
135 */
136var MemoryStorage = /** @class */ (function () {
137 function MemoryStorage() {
138 this.cache_ = {};
139 this.isInMemoryStorage = true;
140 }
141 MemoryStorage.prototype.set = function (key, value) {
142 if (value == null) {
143 delete this.cache_[key];
144 }
145 else {
146 this.cache_[key] = value;
147 }
148 };
149 MemoryStorage.prototype.get = function (key) {
150 if (util.contains(this.cache_, key)) {
151 return this.cache_[key];
152 }
153 return null;
154 };
155 MemoryStorage.prototype.remove = function (key) {
156 delete this.cache_[key];
157 };
158 return MemoryStorage;
159}());
160
161/**
162 * @license
163 * Copyright 2017 Google LLC
164 *
165 * Licensed under the Apache License, Version 2.0 (the "License");
166 * you may not use this file except in compliance with the License.
167 * You may obtain a copy of the License at
168 *
169 * http://www.apache.org/licenses/LICENSE-2.0
170 *
171 * Unless required by applicable law or agreed to in writing, software
172 * distributed under the License is distributed on an "AS IS" BASIS,
173 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
174 * See the License for the specific language governing permissions and
175 * limitations under the License.
176 */
177/**
178 * Helper to create a DOMStorageWrapper or else fall back to MemoryStorage.
179 * TODO: Once MemoryStorage and DOMStorageWrapper have a shared interface this method annotation should change
180 * to reflect this type
181 *
182 * @param domStorageName - Name of the underlying storage object
183 * (e.g. 'localStorage' or 'sessionStorage').
184 * @returns Turning off type information until a common interface is defined.
185 */
186var createStoragefor = function (domStorageName) {
187 try {
188 // NOTE: just accessing "localStorage" or "window['localStorage']" may throw a security exception,
189 // so it must be inside the try/catch.
190 if (typeof window !== 'undefined' &&
191 typeof window[domStorageName] !== 'undefined') {
192 // Need to test cache. Just because it's here doesn't mean it works
193 var domStorage = window[domStorageName];
194 domStorage.setItem('firebase:sentinel', 'cache');
195 domStorage.removeItem('firebase:sentinel');
196 return new DOMStorageWrapper(domStorage);
197 }
198 }
199 catch (e) { }
200 // Failed to create wrapper. Just return in-memory storage.
201 // TODO: log?
202 return new MemoryStorage();
203};
204/** A storage object that lasts across sessions */
205var PersistentStorage = createStoragefor('localStorage');
206/** A storage object that only lasts one session */
207var SessionStorage = createStoragefor('sessionStorage');
208
209/**
210 * @license
211 * Copyright 2017 Google LLC
212 *
213 * Licensed under the Apache License, Version 2.0 (the "License");
214 * you may not use this file except in compliance with the License.
215 * You may obtain a copy of the License at
216 *
217 * http://www.apache.org/licenses/LICENSE-2.0
218 *
219 * Unless required by applicable law or agreed to in writing, software
220 * distributed under the License is distributed on an "AS IS" BASIS,
221 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
222 * See the License for the specific language governing permissions and
223 * limitations under the License.
224 */
225var logClient = new logger$1.Logger('@firebase/database');
226/**
227 * Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
228 */
229var LUIDGenerator = (function () {
230 var id = 1;
231 return function () {
232 return id++;
233 };
234})();
235/**
236 * Sha1 hash of the input string
237 * @param str - The string to hash
238 * @returns {!string} The resulting hash
239 */
240var sha1 = function (str) {
241 var utf8Bytes = util.stringToByteArray(str);
242 var sha1 = new util.Sha1();
243 sha1.update(utf8Bytes);
244 var sha1Bytes = sha1.digest();
245 return util.base64.encodeByteArray(sha1Bytes);
246};
247var buildLogMessage_ = function () {
248 var varArgs = [];
249 for (var _i = 0; _i < arguments.length; _i++) {
250 varArgs[_i] = arguments[_i];
251 }
252 var message = '';
253 for (var i = 0; i < varArgs.length; i++) {
254 var arg = varArgs[i];
255 if (Array.isArray(arg) ||
256 (arg &&
257 typeof arg === 'object' &&
258 // eslint-disable-next-line @typescript-eslint/no-explicit-any
259 typeof arg.length === 'number')) {
260 message += buildLogMessage_.apply(null, arg);
261 }
262 else if (typeof arg === 'object') {
263 message += util.stringify(arg);
264 }
265 else {
266 message += arg;
267 }
268 message += ' ';
269 }
270 return message;
271};
272/**
273 * Use this for all debug messages in Firebase.
274 */
275var logger = null;
276/**
277 * Flag to check for log availability on first log message
278 */
279var firstLog_ = true;
280/**
281 * The implementation of Firebase.enableLogging (defined here to break dependencies)
282 * @param logger_ - A flag to turn on logging, or a custom logger
283 * @param persistent - Whether or not to persist logging settings across refreshes
284 */
285var enableLogging$1 = function (logger_, persistent) {
286 util.assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
287 if (logger_ === true) {
288 logClient.logLevel = logger$1.LogLevel.VERBOSE;
289 logger = logClient.log.bind(logClient);
290 if (persistent) {
291 SessionStorage.set('logging_enabled', true);
292 }
293 }
294 else if (typeof logger_ === 'function') {
295 logger = logger_;
296 }
297 else {
298 logger = null;
299 SessionStorage.remove('logging_enabled');
300 }
301};
302var log = function () {
303 var varArgs = [];
304 for (var _i = 0; _i < arguments.length; _i++) {
305 varArgs[_i] = arguments[_i];
306 }
307 if (firstLog_ === true) {
308 firstLog_ = false;
309 if (logger === null && SessionStorage.get('logging_enabled') === true) {
310 enableLogging$1(true);
311 }
312 }
313 if (logger) {
314 var message = buildLogMessage_.apply(null, varArgs);
315 logger(message);
316 }
317};
318var logWrapper = function (prefix) {
319 return function () {
320 var varArgs = [];
321 for (var _i = 0; _i < arguments.length; _i++) {
322 varArgs[_i] = arguments[_i];
323 }
324 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
325 };
326};
327var error = function () {
328 var varArgs = [];
329 for (var _i = 0; _i < arguments.length; _i++) {
330 varArgs[_i] = arguments[_i];
331 }
332 var message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
333 logClient.error(message);
334};
335var fatal = function () {
336 var varArgs = [];
337 for (var _i = 0; _i < arguments.length; _i++) {
338 varArgs[_i] = arguments[_i];
339 }
340 var message = "FIREBASE FATAL ERROR: " + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
341 logClient.error(message);
342 throw new Error(message);
343};
344var warn = function () {
345 var varArgs = [];
346 for (var _i = 0; _i < arguments.length; _i++) {
347 varArgs[_i] = arguments[_i];
348 }
349 var message = 'FIREBASE WARNING: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs)));
350 logClient.warn(message);
351};
352/**
353 * Logs a warning if the containing page uses https. Called when a call to new Firebase
354 * does not use https.
355 */
356var warnIfPageIsSecure = function () {
357 // Be very careful accessing browser globals. Who knows what may or may not exist.
358 if (typeof window !== 'undefined' &&
359 window.location &&
360 window.location.protocol &&
361 window.location.protocol.indexOf('https:') !== -1) {
362 warn('Insecure Firebase access from a secure page. ' +
363 'Please use https in calls to new Firebase().');
364 }
365};
366/**
367 * Returns true if data is NaN, or +/- Infinity.
368 */
369var isInvalidJSONNumber = function (data) {
370 return (typeof data === 'number' &&
371 (data !== data || // NaN
372 data === Number.POSITIVE_INFINITY ||
373 data === Number.NEGATIVE_INFINITY));
374};
375var executeWhenDOMReady = function (fn) {
376 if (util.isNodeSdk() || document.readyState === 'complete') {
377 fn();
378 }
379 else {
380 // Modeled after jQuery. Try DOMContentLoaded and onreadystatechange (which
381 // fire before onload), but fall back to onload.
382 var called_1 = false;
383 var wrappedFn_1 = function () {
384 if (!document.body) {
385 setTimeout(wrappedFn_1, Math.floor(10));
386 return;
387 }
388 if (!called_1) {
389 called_1 = true;
390 fn();
391 }
392 };
393 if (document.addEventListener) {
394 document.addEventListener('DOMContentLoaded', wrappedFn_1, false);
395 // fallback to onload.
396 window.addEventListener('load', wrappedFn_1, false);
397 // eslint-disable-next-line @typescript-eslint/no-explicit-any
398 }
399 else if (document.attachEvent) {
400 // IE.
401 // eslint-disable-next-line @typescript-eslint/no-explicit-any
402 document.attachEvent('onreadystatechange', function () {
403 if (document.readyState === 'complete') {
404 wrappedFn_1();
405 }
406 });
407 // fallback to onload.
408 // eslint-disable-next-line @typescript-eslint/no-explicit-any
409 window.attachEvent('onload', wrappedFn_1);
410 // jQuery has an extra hack for IE that we could employ (based on
411 // http://javascript.nwbox.com/IEContentLoaded/) But it looks really old.
412 // I'm hoping we don't need it.
413 }
414 }
415};
416/**
417 * Minimum key name. Invalid for actual data, used as a marker to sort before any valid names
418 */
419var MIN_NAME = '[MIN_NAME]';
420/**
421 * Maximum key name. Invalid for actual data, used as a marker to sort above any valid names
422 */
423var MAX_NAME = '[MAX_NAME]';
424/**
425 * Compares valid Firebase key names, plus min and max name
426 */
427var nameCompare = function (a, b) {
428 if (a === b) {
429 return 0;
430 }
431 else if (a === MIN_NAME || b === MAX_NAME) {
432 return -1;
433 }
434 else if (b === MIN_NAME || a === MAX_NAME) {
435 return 1;
436 }
437 else {
438 var aAsInt = tryParseInt(a), bAsInt = tryParseInt(b);
439 if (aAsInt !== null) {
440 if (bAsInt !== null) {
441 return aAsInt - bAsInt === 0 ? a.length - b.length : aAsInt - bAsInt;
442 }
443 else {
444 return -1;
445 }
446 }
447 else if (bAsInt !== null) {
448 return 1;
449 }
450 else {
451 return a < b ? -1 : 1;
452 }
453 }
454};
455/**
456 * @returns {!number} comparison result.
457 */
458var stringCompare = function (a, b) {
459 if (a === b) {
460 return 0;
461 }
462 else if (a < b) {
463 return -1;
464 }
465 else {
466 return 1;
467 }
468};
469var requireKey = function (key, obj) {
470 if (obj && key in obj) {
471 return obj[key];
472 }
473 else {
474 throw new Error('Missing required key (' + key + ') in object: ' + util.stringify(obj));
475 }
476};
477var ObjectToUniqueKey = function (obj) {
478 if (typeof obj !== 'object' || obj === null) {
479 return util.stringify(obj);
480 }
481 var keys = [];
482 // eslint-disable-next-line guard-for-in
483 for (var k in obj) {
484 keys.push(k);
485 }
486 // Export as json, but with the keys sorted.
487 keys.sort();
488 var key = '{';
489 for (var i = 0; i < keys.length; i++) {
490 if (i !== 0) {
491 key += ',';
492 }
493 key += util.stringify(keys[i]);
494 key += ':';
495 key += ObjectToUniqueKey(obj[keys[i]]);
496 }
497 key += '}';
498 return key;
499};
500/**
501 * Splits a string into a number of smaller segments of maximum size
502 * @param str - The string
503 * @param segsize - The maximum number of chars in the string.
504 * @returns The string, split into appropriately-sized chunks
505 */
506var splitStringBySize = function (str, segsize) {
507 var len = str.length;
508 if (len <= segsize) {
509 return [str];
510 }
511 var dataSegs = [];
512 for (var c = 0; c < len; c += segsize) {
513 if (c + segsize > len) {
514 dataSegs.push(str.substring(c, len));
515 }
516 else {
517 dataSegs.push(str.substring(c, c + segsize));
518 }
519 }
520 return dataSegs;
521};
522/**
523 * Apply a function to each (key, value) pair in an object or
524 * apply a function to each (index, value) pair in an array
525 * @param obj - The object or array to iterate over
526 * @param fn - The function to apply
527 */
528function each(obj, fn) {
529 for (var key in obj) {
530 if (obj.hasOwnProperty(key)) {
531 fn(key, obj[key]);
532 }
533 }
534}
535/**
536 * Borrowed from http://hg.secondlife.com/llsd/src/tip/js/typedarray.js (MIT License)
537 * I made one modification at the end and removed the NaN / Infinity
538 * handling (since it seemed broken [caused an overflow] and we don't need it). See MJL comments.
539 * @param v - A double
540 *
541 */
542var doubleToIEEE754String = function (v) {
543 util.assert(!isInvalidJSONNumber(v), 'Invalid JSON number'); // MJL
544 var ebits = 11, fbits = 52;
545 var bias = (1 << (ebits - 1)) - 1;
546 var s, e, f, ln, i;
547 // Compute sign, exponent, fraction
548 // Skip NaN / Infinity handling --MJL.
549 if (v === 0) {
550 e = 0;
551 f = 0;
552 s = 1 / v === -Infinity ? 1 : 0;
553 }
554 else {
555 s = v < 0;
556 v = Math.abs(v);
557 if (v >= Math.pow(2, 1 - bias)) {
558 // Normalized
559 ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
560 e = ln + bias;
561 f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
562 }
563 else {
564 // Denormalized
565 e = 0;
566 f = Math.round(v / Math.pow(2, 1 - bias - fbits));
567 }
568 }
569 // Pack sign, exponent, fraction
570 var bits = [];
571 for (i = fbits; i; i -= 1) {
572 bits.push(f % 2 ? 1 : 0);
573 f = Math.floor(f / 2);
574 }
575 for (i = ebits; i; i -= 1) {
576 bits.push(e % 2 ? 1 : 0);
577 e = Math.floor(e / 2);
578 }
579 bits.push(s ? 1 : 0);
580 bits.reverse();
581 var str = bits.join('');
582 // Return the data as a hex string. --MJL
583 var hexByteString = '';
584 for (i = 0; i < 64; i += 8) {
585 var hexByte = parseInt(str.substr(i, 8), 2).toString(16);
586 if (hexByte.length === 1) {
587 hexByte = '0' + hexByte;
588 }
589 hexByteString = hexByteString + hexByte;
590 }
591 return hexByteString.toLowerCase();
592};
593/**
594 * Used to detect if we're in a Chrome content script (which executes in an
595 * isolated environment where long-polling doesn't work).
596 */
597var isChromeExtensionContentScript = function () {
598 return !!(typeof window === 'object' &&
599 window['chrome'] &&
600 window['chrome']['extension'] &&
601 !/^chrome/.test(window.location.href));
602};
603/**
604 * Used to detect if we're in a Windows 8 Store app.
605 */
606var isWindowsStoreApp = function () {
607 // Check for the presence of a couple WinRT globals
608 return typeof Windows === 'object' && typeof Windows.UI === 'object';
609};
610/**
611 * Converts a server error code to a Javascript Error
612 */
613function errorForServerCode(code, query) {
614 var reason = 'Unknown Error';
615 if (code === 'too_big') {
616 reason =
617 'The data requested exceeds the maximum size ' +
618 'that can be accessed with a single request.';
619 }
620 else if (code === 'permission_denied') {
621 reason = "Client doesn't have permission to access the desired data.";
622 }
623 else if (code === 'unavailable') {
624 reason = 'The service is unavailable';
625 }
626 var error = new Error(code + ' at ' + query._path.toString() + ': ' + reason);
627 // eslint-disable-next-line @typescript-eslint/no-explicit-any
628 error.code = code.toUpperCase();
629 return error;
630}
631/**
632 * Used to test for integer-looking strings
633 */
634var INTEGER_REGEXP_ = new RegExp('^-?(0*)\\d{1,10}$');
635/**
636 * For use in keys, the minimum possible 32-bit integer.
637 */
638var INTEGER_32_MIN = -2147483648;
639/**
640 * For use in kyes, the maximum possible 32-bit integer.
641 */
642var INTEGER_32_MAX = 2147483647;
643/**
644 * If the string contains a 32-bit integer, return it. Else return null.
645 */
646var tryParseInt = function (str) {
647 if (INTEGER_REGEXP_.test(str)) {
648 var intVal = Number(str);
649 if (intVal >= INTEGER_32_MIN && intVal <= INTEGER_32_MAX) {
650 return intVal;
651 }
652 }
653 return null;
654};
655/**
656 * Helper to run some code but catch any exceptions and re-throw them later.
657 * Useful for preventing user callbacks from breaking internal code.
658 *
659 * Re-throwing the exception from a setTimeout is a little evil, but it's very
660 * convenient (we don't have to try to figure out when is a safe point to
661 * re-throw it), and the behavior seems reasonable:
662 *
663 * * If you aren't pausing on exceptions, you get an error in the console with
664 * the correct stack trace.
665 * * If you're pausing on all exceptions, the debugger will pause on your
666 * exception and then again when we rethrow it.
667 * * If you're only pausing on uncaught exceptions, the debugger will only pause
668 * on us re-throwing it.
669 *
670 * @param fn - The code to guard.
671 */
672var exceptionGuard = function (fn) {
673 try {
674 fn();
675 }
676 catch (e) {
677 // Re-throw exception when it's safe.
678 setTimeout(function () {
679 // It used to be that "throw e" would result in a good console error with
680 // relevant context, but as of Chrome 39, you just get the firebase.js
681 // file/line number where we re-throw it, which is useless. So we log
682 // e.stack explicitly.
683 var stack = e.stack || '';
684 warn('Exception was thrown by user callback.', stack);
685 throw e;
686 }, Math.floor(0));
687 }
688};
689/**
690 * @returns {boolean} true if we think we're currently being crawled.
691 */
692var beingCrawled = function () {
693 var userAgent = (typeof window === 'object' &&
694 window['navigator'] &&
695 window['navigator']['userAgent']) ||
696 '';
697 // For now we whitelist the most popular crawlers. We should refine this to be the set of crawlers we
698 // believe to support JavaScript/AJAX rendering.
699 // NOTE: Google Webmaster Tools doesn't really belong, but their "This is how a visitor to your website
700 // would have seen the page" is flaky if we don't treat it as a crawler.
701 return (userAgent.search(/googlebot|google webmaster tools|bingbot|yahoo! slurp|baiduspider|yandexbot|duckduckbot/i) >= 0);
702};
703/**
704 * Same as setTimeout() except on Node.JS it will /not/ prevent the process from exiting.
705 *
706 * It is removed with clearTimeout() as normal.
707 *
708 * @param fn - Function to run.
709 * @param time - Milliseconds to wait before running.
710 * @returns The setTimeout() return value.
711 */
712var setTimeoutNonBlocking = function (fn, time) {
713 var timeout = setTimeout(fn, time);
714 // eslint-disable-next-line @typescript-eslint/no-explicit-any
715 if (typeof timeout === 'object' && timeout['unref']) {
716 // eslint-disable-next-line @typescript-eslint/no-explicit-any
717 timeout['unref']();
718 }
719 return timeout;
720};
721
722/**
723 * @license
724 * Copyright 2017 Google LLC
725 *
726 * Licensed under the Apache License, Version 2.0 (the "License");
727 * you may not use this file except in compliance with the License.
728 * You may obtain a copy of the License at
729 *
730 * http://www.apache.org/licenses/LICENSE-2.0
731 *
732 * Unless required by applicable law or agreed to in writing, software
733 * distributed under the License is distributed on an "AS IS" BASIS,
734 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
735 * See the License for the specific language governing permissions and
736 * limitations under the License.
737 */
738/**
739 * A class that holds metadata about a Repo object
740 */
741var RepoInfo = /** @class */ (function () {
742 /**
743 * @param host - Hostname portion of the url for the repo
744 * @param secure - Whether or not this repo is accessed over ssl
745 * @param namespace - The namespace represented by the repo
746 * @param webSocketOnly - Whether to prefer websockets over all other transports (used by Nest).
747 * @param nodeAdmin - Whether this instance uses Admin SDK credentials
748 * @param persistenceKey - Override the default session persistence storage key
749 */
750 function RepoInfo(host, secure, namespace, webSocketOnly, nodeAdmin, persistenceKey, includeNamespaceInQueryParams) {
751 if (nodeAdmin === void 0) { nodeAdmin = false; }
752 if (persistenceKey === void 0) { persistenceKey = ''; }
753 if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
754 this.secure = secure;
755 this.namespace = namespace;
756 this.webSocketOnly = webSocketOnly;
757 this.nodeAdmin = nodeAdmin;
758 this.persistenceKey = persistenceKey;
759 this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
760 this._host = host.toLowerCase();
761 this._domain = this._host.substr(this._host.indexOf('.') + 1);
762 this.internalHost =
763 PersistentStorage.get('host:' + host) || this._host;
764 }
765 RepoInfo.prototype.isCacheableHost = function () {
766 return this.internalHost.substr(0, 2) === 's-';
767 };
768 RepoInfo.prototype.isCustomHost = function () {
769 return (this._domain !== 'firebaseio.com' &&
770 this._domain !== 'firebaseio-demo.com');
771 };
772 Object.defineProperty(RepoInfo.prototype, "host", {
773 get: function () {
774 return this._host;
775 },
776 set: function (newHost) {
777 if (newHost !== this.internalHost) {
778 this.internalHost = newHost;
779 if (this.isCacheableHost()) {
780 PersistentStorage.set('host:' + this._host, this.internalHost);
781 }
782 }
783 },
784 enumerable: false,
785 configurable: true
786 });
787 RepoInfo.prototype.toString = function () {
788 var str = this.toURLString();
789 if (this.persistenceKey) {
790 str += '<' + this.persistenceKey + '>';
791 }
792 return str;
793 };
794 RepoInfo.prototype.toURLString = function () {
795 var protocol = this.secure ? 'https://' : 'http://';
796 var query = this.includeNamespaceInQueryParams
797 ? "?ns=" + this.namespace
798 : '';
799 return "" + protocol + this.host + "/" + query;
800 };
801 return RepoInfo;
802}());
803function repoInfoNeedsQueryParam(repoInfo) {
804 return (repoInfo.host !== repoInfo.internalHost ||
805 repoInfo.isCustomHost() ||
806 repoInfo.includeNamespaceInQueryParams);
807}
808/**
809 * Returns the websocket URL for this repo
810 * @param repoInfo - RepoInfo object
811 * @param type - of connection
812 * @param params - list
813 * @returns The URL for this repo
814 */
815function repoInfoConnectionURL(repoInfo, type, params) {
816 util.assert(typeof type === 'string', 'typeof type must == string');
817 util.assert(typeof params === 'object', 'typeof params must == object');
818 var connURL;
819 if (type === WEBSOCKET) {
820 connURL =
821 (repoInfo.secure ? 'wss://' : 'ws://') + repoInfo.internalHost + '/.ws?';
822 }
823 else if (type === LONG_POLLING) {
824 connURL =
825 (repoInfo.secure ? 'https://' : 'http://') +
826 repoInfo.internalHost +
827 '/.lp?';
828 }
829 else {
830 throw new Error('Unknown connection type: ' + type);
831 }
832 if (repoInfoNeedsQueryParam(repoInfo)) {
833 params['ns'] = repoInfo.namespace;
834 }
835 var pairs = [];
836 each(params, function (key, value) {
837 pairs.push(key + '=' + value);
838 });
839 return connURL + pairs.join('&');
840}
841
842/**
843 * @license
844 * Copyright 2017 Google LLC
845 *
846 * Licensed under the Apache License, Version 2.0 (the "License");
847 * you may not use this file except in compliance with the License.
848 * You may obtain a copy of the License at
849 *
850 * http://www.apache.org/licenses/LICENSE-2.0
851 *
852 * Unless required by applicable law or agreed to in writing, software
853 * distributed under the License is distributed on an "AS IS" BASIS,
854 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
855 * See the License for the specific language governing permissions and
856 * limitations under the License.
857 */
858/**
859 * Tracks a collection of stats.
860 */
861var StatsCollection = /** @class */ (function () {
862 function StatsCollection() {
863 this.counters_ = {};
864 }
865 StatsCollection.prototype.incrementCounter = function (name, amount) {
866 if (amount === void 0) { amount = 1; }
867 if (!util.contains(this.counters_, name)) {
868 this.counters_[name] = 0;
869 }
870 this.counters_[name] += amount;
871 };
872 StatsCollection.prototype.get = function () {
873 return util.deepCopy(this.counters_);
874 };
875 return StatsCollection;
876}());
877
878/**
879 * @license
880 * Copyright 2017 Google LLC
881 *
882 * Licensed under the Apache License, Version 2.0 (the "License");
883 * you may not use this file except in compliance with the License.
884 * You may obtain a copy of the License at
885 *
886 * http://www.apache.org/licenses/LICENSE-2.0
887 *
888 * Unless required by applicable law or agreed to in writing, software
889 * distributed under the License is distributed on an "AS IS" BASIS,
890 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
891 * See the License for the specific language governing permissions and
892 * limitations under the License.
893 */
894var collections = {};
895var reporters = {};
896function statsManagerGetCollection(repoInfo) {
897 var hashString = repoInfo.toString();
898 if (!collections[hashString]) {
899 collections[hashString] = new StatsCollection();
900 }
901 return collections[hashString];
902}
903function statsManagerGetOrCreateReporter(repoInfo, creatorFunction) {
904 var hashString = repoInfo.toString();
905 if (!reporters[hashString]) {
906 reporters[hashString] = creatorFunction();
907 }
908 return reporters[hashString];
909}
910
911/**
912 * @license
913 * Copyright 2019 Google LLC
914 *
915 * Licensed under the Apache License, Version 2.0 (the "License");
916 * you may not use this file except in compliance with the License.
917 * You may obtain a copy of the License at
918 *
919 * http://www.apache.org/licenses/LICENSE-2.0
920 *
921 * Unless required by applicable law or agreed to in writing, software
922 * distributed under the License is distributed on an "AS IS" BASIS,
923 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
924 * See the License for the specific language governing permissions and
925 * limitations under the License.
926 */
927/** The semver (www.semver.org) version of the SDK. */
928var SDK_VERSION = '';
929/**
930 * SDK_VERSION should be set before any database instance is created
931 * @internal
932 */
933function setSDKVersion(version) {
934 SDK_VERSION = version;
935}
936
937/**
938 * @license
939 * Copyright 2017 Google LLC
940 *
941 * Licensed under the Apache License, Version 2.0 (the "License");
942 * you may not use this file except in compliance with the License.
943 * You may obtain a copy of the License at
944 *
945 * http://www.apache.org/licenses/LICENSE-2.0
946 *
947 * Unless required by applicable law or agreed to in writing, software
948 * distributed under the License is distributed on an "AS IS" BASIS,
949 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
950 * See the License for the specific language governing permissions and
951 * limitations under the License.
952 */
953var WEBSOCKET_MAX_FRAME_SIZE = 16384;
954var WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
955var WebSocketImpl = null;
956if (typeof MozWebSocket !== 'undefined') {
957 WebSocketImpl = MozWebSocket;
958}
959else if (typeof WebSocket !== 'undefined') {
960 WebSocketImpl = WebSocket;
961}
962function setWebSocketImpl(impl) {
963 WebSocketImpl = impl;
964}
965/**
966 * Create a new websocket connection with the given callbacks.
967 */
968var WebSocketConnection = /** @class */ (function () {
969 /**
970 * @param connId identifier for this transport
971 * @param repoInfo The info for the websocket endpoint.
972 * @param applicationId The Firebase App ID for this project.
973 * @param appCheckToken The App Check Token for this client.
974 * @param authToken The Auth Token for this client.
975 * @param transportSessionId Optional transportSessionId if this is connecting
976 * to an existing transport session
977 * @param lastSessionId Optional lastSessionId if there was a previous
978 * connection
979 */
980 function WebSocketConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
981 this.connId = connId;
982 this.applicationId = applicationId;
983 this.appCheckToken = appCheckToken;
984 this.authToken = authToken;
985 this.keepaliveTimer = null;
986 this.frames = null;
987 this.totalFrames = 0;
988 this.bytesSent = 0;
989 this.bytesReceived = 0;
990 this.log_ = logWrapper(this.connId);
991 this.stats_ = statsManagerGetCollection(repoInfo);
992 this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId);
993 this.nodeAdmin = repoInfo.nodeAdmin;
994 }
995 /**
996 * @param repoInfo - The info for the websocket endpoint.
997 * @param transportSessionId - Optional transportSessionId if this is connecting to an existing transport
998 * session
999 * @param lastSessionId - Optional lastSessionId if there was a previous connection
1000 * @returns connection url
1001 */
1002 WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId) {
1003 var urlParams = {};
1004 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1005 if (!util.isNodeSdk() &&
1006 typeof location !== 'undefined' &&
1007 location.hostname &&
1008 FORGE_DOMAIN_RE.test(location.hostname)) {
1009 urlParams[REFERER_PARAM] = FORGE_REF;
1010 }
1011 if (transportSessionId) {
1012 urlParams[TRANSPORT_SESSION_PARAM] = transportSessionId;
1013 }
1014 if (lastSessionId) {
1015 urlParams[LAST_SESSION_PARAM] = lastSessionId;
1016 }
1017 if (appCheckToken) {
1018 urlParams[APP_CHECK_TOKEN_PARAM] = appCheckToken;
1019 }
1020 if (applicationId) {
1021 urlParams[APPLICATION_ID_PARAM] = applicationId;
1022 }
1023 return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
1024 };
1025 /**
1026 * @param onMessage - Callback when messages arrive
1027 * @param onDisconnect - Callback with connection lost.
1028 */
1029 WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
1030 var _this = this;
1031 this.onDisconnect = onDisconnect;
1032 this.onMessage = onMessage;
1033 this.log_('Websocket connecting to ' + this.connURL);
1034 this.everConnected_ = false;
1035 // Assume failure until proven otherwise.
1036 PersistentStorage.set('previous_websocket_failure', true);
1037 try {
1038 var options = void 0;
1039 if (util.isNodeSdk()) {
1040 var device = this.nodeAdmin ? 'AdminNode' : 'Node';
1041 // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
1042 var options_1 = {
1043 headers: {
1044 'User-Agent': "Firebase/" + PROTOCOL_VERSION + "/" + SDK_VERSION + "/" + process.platform + "/" + device,
1045 'X-Firebase-GMPID': this.applicationId || ''
1046 }
1047 };
1048 // If using Node with admin creds, AppCheck-related checks are unnecessary.
1049 // Note that we send the credentials here even if they aren't admin credentials, which is
1050 // not a problem.
1051 // Note that this header is just used to bypass appcheck, and the token should still be sent
1052 // through the websocket connection once it is established.
1053 if (this.authToken) {
1054 options_1.headers['Authorization'] = "Bearer " + this.authToken;
1055 }
1056 if (this.appCheckToken) {
1057 options_1.headers['X-Firebase-AppCheck'] = this.appCheckToken;
1058 }
1059 // Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
1060 var env = process['env'];
1061 var proxy = this.connURL.indexOf('wss://') === 0
1062 ? env['HTTPS_PROXY'] || env['https_proxy']
1063 : env['HTTP_PROXY'] || env['http_proxy'];
1064 if (proxy) {
1065 options_1['proxy'] = { origin: proxy };
1066 }
1067 }
1068 this.mySock = new WebSocketImpl(this.connURL, [], options);
1069 }
1070 catch (e) {
1071 this.log_('Error instantiating WebSocket.');
1072 var error = e.message || e.data;
1073 if (error) {
1074 this.log_(error);
1075 }
1076 this.onClosed_();
1077 return;
1078 }
1079 this.mySock.onopen = function () {
1080 _this.log_('Websocket connected.');
1081 _this.everConnected_ = true;
1082 };
1083 this.mySock.onclose = function () {
1084 _this.log_('Websocket connection was disconnected.');
1085 _this.mySock = null;
1086 _this.onClosed_();
1087 };
1088 this.mySock.onmessage = function (m) {
1089 _this.handleIncomingFrame(m);
1090 };
1091 this.mySock.onerror = function (e) {
1092 _this.log_('WebSocket error. Closing connection.');
1093 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1094 var error = e.message || e.data;
1095 if (error) {
1096 _this.log_(error);
1097 }
1098 _this.onClosed_();
1099 };
1100 };
1101 /**
1102 * No-op for websockets, we don't need to do anything once the connection is confirmed as open
1103 */
1104 WebSocketConnection.prototype.start = function () { };
1105 WebSocketConnection.forceDisallow = function () {
1106 WebSocketConnection.forceDisallow_ = true;
1107 };
1108 WebSocketConnection.isAvailable = function () {
1109 var isOldAndroid = false;
1110 if (typeof navigator !== 'undefined' && navigator.userAgent) {
1111 var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
1112 var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
1113 if (oldAndroidMatch && oldAndroidMatch.length > 1) {
1114 if (parseFloat(oldAndroidMatch[1]) < 4.4) {
1115 isOldAndroid = true;
1116 }
1117 }
1118 }
1119 return (!isOldAndroid &&
1120 WebSocketImpl !== null &&
1121 !WebSocketConnection.forceDisallow_);
1122 };
1123 /**
1124 * Returns true if we previously failed to connect with this transport.
1125 */
1126 WebSocketConnection.previouslyFailed = function () {
1127 // If our persistent storage is actually only in-memory storage,
1128 // we default to assuming that it previously failed to be safe.
1129 return (PersistentStorage.isInMemoryStorage ||
1130 PersistentStorage.get('previous_websocket_failure') === true);
1131 };
1132 WebSocketConnection.prototype.markConnectionHealthy = function () {
1133 PersistentStorage.remove('previous_websocket_failure');
1134 };
1135 WebSocketConnection.prototype.appendFrame_ = function (data) {
1136 this.frames.push(data);
1137 if (this.frames.length === this.totalFrames) {
1138 var fullMess = this.frames.join('');
1139 this.frames = null;
1140 var jsonMess = util.jsonEval(fullMess);
1141 //handle the message
1142 this.onMessage(jsonMess);
1143 }
1144 };
1145 /**
1146 * @param frameCount - The number of frames we are expecting from the server
1147 */
1148 WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
1149 this.totalFrames = frameCount;
1150 this.frames = [];
1151 };
1152 /**
1153 * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
1154 * @returns Any remaining data to be process, or null if there is none
1155 */
1156 WebSocketConnection.prototype.extractFrameCount_ = function (data) {
1157 util.assert(this.frames === null, 'We already have a frame buffer');
1158 // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
1159 // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
1160 if (data.length <= 6) {
1161 var frameCount = Number(data);
1162 if (!isNaN(frameCount)) {
1163 this.handleNewFrameCount_(frameCount);
1164 return null;
1165 }
1166 }
1167 this.handleNewFrameCount_(1);
1168 return data;
1169 };
1170 /**
1171 * Process a websocket frame that has arrived from the server.
1172 * @param mess - The frame data
1173 */
1174 WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
1175 if (this.mySock === null) {
1176 return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
1177 }
1178 var data = mess['data'];
1179 this.bytesReceived += data.length;
1180 this.stats_.incrementCounter('bytes_received', data.length);
1181 this.resetKeepAlive();
1182 if (this.frames !== null) {
1183 // we're buffering
1184 this.appendFrame_(data);
1185 }
1186 else {
1187 // try to parse out a frame count, otherwise, assume 1 and process it
1188 var remainingData = this.extractFrameCount_(data);
1189 if (remainingData !== null) {
1190 this.appendFrame_(remainingData);
1191 }
1192 }
1193 };
1194 /**
1195 * Send a message to the server
1196 * @param data - The JSON object to transmit
1197 */
1198 WebSocketConnection.prototype.send = function (data) {
1199 this.resetKeepAlive();
1200 var dataStr = util.stringify(data);
1201 this.bytesSent += dataStr.length;
1202 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1203 //We can only fit a certain amount in each websocket frame, so we need to split this request
1204 //up into multiple pieces if it doesn't fit in one request.
1205 var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
1206 //Send the length header
1207 if (dataSegs.length > 1) {
1208 this.sendString_(String(dataSegs.length));
1209 }
1210 //Send the actual data in segments.
1211 for (var i = 0; i < dataSegs.length; i++) {
1212 this.sendString_(dataSegs[i]);
1213 }
1214 };
1215 WebSocketConnection.prototype.shutdown_ = function () {
1216 this.isClosed_ = true;
1217 if (this.keepaliveTimer) {
1218 clearInterval(this.keepaliveTimer);
1219 this.keepaliveTimer = null;
1220 }
1221 if (this.mySock) {
1222 this.mySock.close();
1223 this.mySock = null;
1224 }
1225 };
1226 WebSocketConnection.prototype.onClosed_ = function () {
1227 if (!this.isClosed_) {
1228 this.log_('WebSocket is closing itself');
1229 this.shutdown_();
1230 // since this is an internal close, trigger the close listener
1231 if (this.onDisconnect) {
1232 this.onDisconnect(this.everConnected_);
1233 this.onDisconnect = null;
1234 }
1235 }
1236 };
1237 /**
1238 * External-facing close handler.
1239 * Close the websocket and kill the connection.
1240 */
1241 WebSocketConnection.prototype.close = function () {
1242 if (!this.isClosed_) {
1243 this.log_('WebSocket is being closed');
1244 this.shutdown_();
1245 }
1246 };
1247 /**
1248 * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
1249 * the last activity.
1250 */
1251 WebSocketConnection.prototype.resetKeepAlive = function () {
1252 var _this = this;
1253 clearInterval(this.keepaliveTimer);
1254 this.keepaliveTimer = setInterval(function () {
1255 //If there has been no websocket activity for a while, send a no-op
1256 if (_this.mySock) {
1257 _this.sendString_('0');
1258 }
1259 _this.resetKeepAlive();
1260 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1261 }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
1262 };
1263 /**
1264 * Send a string over the websocket.
1265 *
1266 * @param str - String to send.
1267 */
1268 WebSocketConnection.prototype.sendString_ = function (str) {
1269 // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
1270 // calls for some unknown reason. We treat these as an error and disconnect.
1271 // See https://app.asana.com/0/58926111402292/68021340250410
1272 try {
1273 this.mySock.send(str);
1274 }
1275 catch (e) {
1276 this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
1277 setTimeout(this.onClosed_.bind(this), 0);
1278 }
1279 };
1280 /**
1281 * Number of response before we consider the connection "healthy."
1282 */
1283 WebSocketConnection.responsesRequiredToBeHealthy = 2;
1284 /**
1285 * Time to wait for the connection te become healthy before giving up.
1286 */
1287 WebSocketConnection.healthyTimeout = 30000;
1288 return WebSocketConnection;
1289}());
1290
1291var name = "@firebase/database";
1292var version = "0.13.2";
1293
1294/**
1295 * @license
1296 * Copyright 2021 Google LLC
1297 *
1298 * Licensed under the Apache License, Version 2.0 (the "License");
1299 * you may not use this file except in compliance with the License.
1300 * You may obtain a copy of the License at
1301 *
1302 * http://www.apache.org/licenses/LICENSE-2.0
1303 *
1304 * Unless required by applicable law or agreed to in writing, software
1305 * distributed under the License is distributed on an "AS IS" BASIS,
1306 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1307 * See the License for the specific language governing permissions and
1308 * limitations under the License.
1309 */
1310/**
1311 * Abstraction around AppCheck's token fetching capabilities.
1312 */
1313var AppCheckTokenProvider = /** @class */ (function () {
1314 function AppCheckTokenProvider(appName_, appCheckProvider) {
1315 var _this = this;
1316 this.appName_ = appName_;
1317 this.appCheckProvider = appCheckProvider;
1318 this.appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
1319 if (!this.appCheck) {
1320 appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.get().then(function (appCheck) { return (_this.appCheck = appCheck); });
1321 }
1322 }
1323 AppCheckTokenProvider.prototype.getToken = function (forceRefresh) {
1324 var _this = this;
1325 if (!this.appCheck) {
1326 return new Promise(function (resolve, reject) {
1327 // Support delayed initialization of FirebaseAppCheck. This allows our
1328 // customers to initialize the RTDB SDK before initializing Firebase
1329 // AppCheck and ensures that all requests are authenticated if a token
1330 // becomes available before the timoeout below expires.
1331 setTimeout(function () {
1332 if (_this.appCheck) {
1333 _this.getToken(forceRefresh).then(resolve, reject);
1334 }
1335 else {
1336 resolve(null);
1337 }
1338 }, 0);
1339 });
1340 }
1341 return this.appCheck.getToken(forceRefresh);
1342 };
1343 AppCheckTokenProvider.prototype.addTokenChangeListener = function (listener) {
1344 var _a;
1345 (_a = this.appCheckProvider) === null || _a === void 0 ? void 0 : _a.get().then(function (appCheck) { return appCheck.addTokenListener(listener); });
1346 };
1347 AppCheckTokenProvider.prototype.notifyForInvalidToken = function () {
1348 warn("Provided AppCheck credentials for the app named \"" + this.appName_ + "\" " +
1349 'are invalid. This usually indicates your app was not initialized correctly.');
1350 };
1351 return AppCheckTokenProvider;
1352}());
1353
1354/**
1355 * @license
1356 * Copyright 2017 Google LLC
1357 *
1358 * Licensed under the Apache License, Version 2.0 (the "License");
1359 * you may not use this file except in compliance with the License.
1360 * You may obtain a copy of the License at
1361 *
1362 * http://www.apache.org/licenses/LICENSE-2.0
1363 *
1364 * Unless required by applicable law or agreed to in writing, software
1365 * distributed under the License is distributed on an "AS IS" BASIS,
1366 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1367 * See the License for the specific language governing permissions and
1368 * limitations under the License.
1369 */
1370/**
1371 * Abstraction around FirebaseApp's token fetching capabilities.
1372 */
1373var FirebaseAuthTokenProvider = /** @class */ (function () {
1374 function FirebaseAuthTokenProvider(appName_, firebaseOptions_, authProvider_) {
1375 var _this = this;
1376 this.appName_ = appName_;
1377 this.firebaseOptions_ = firebaseOptions_;
1378 this.authProvider_ = authProvider_;
1379 this.auth_ = null;
1380 this.auth_ = authProvider_.getImmediate({ optional: true });
1381 if (!this.auth_) {
1382 authProvider_.onInit(function (auth) { return (_this.auth_ = auth); });
1383 }
1384 }
1385 FirebaseAuthTokenProvider.prototype.getToken = function (forceRefresh) {
1386 var _this = this;
1387 if (!this.auth_) {
1388 return new Promise(function (resolve, reject) {
1389 // Support delayed initialization of FirebaseAuth. This allows our
1390 // customers to initialize the RTDB SDK before initializing Firebase
1391 // Auth and ensures that all requests are authenticated if a token
1392 // becomes available before the timoeout below expires.
1393 setTimeout(function () {
1394 if (_this.auth_) {
1395 _this.getToken(forceRefresh).then(resolve, reject);
1396 }
1397 else {
1398 resolve(null);
1399 }
1400 }, 0);
1401 });
1402 }
1403 return this.auth_.getToken(forceRefresh).catch(function (error) {
1404 // TODO: Need to figure out all the cases this is raised and whether
1405 // this makes sense.
1406 if (error && error.code === 'auth/token-not-initialized') {
1407 log('Got auth/token-not-initialized error. Treating as null token.');
1408 return null;
1409 }
1410 else {
1411 return Promise.reject(error);
1412 }
1413 });
1414 };
1415 FirebaseAuthTokenProvider.prototype.addTokenChangeListener = function (listener) {
1416 // TODO: We might want to wrap the listener and call it with no args to
1417 // avoid a leaky abstraction, but that makes removing the listener harder.
1418 if (this.auth_) {
1419 this.auth_.addAuthTokenListener(listener);
1420 }
1421 else {
1422 this.authProvider_
1423 .get()
1424 .then(function (auth) { return auth.addAuthTokenListener(listener); });
1425 }
1426 };
1427 FirebaseAuthTokenProvider.prototype.removeTokenChangeListener = function (listener) {
1428 this.authProvider_
1429 .get()
1430 .then(function (auth) { return auth.removeAuthTokenListener(listener); });
1431 };
1432 FirebaseAuthTokenProvider.prototype.notifyForInvalidToken = function () {
1433 var errorMessage = 'Provided authentication credentials for the app named "' +
1434 this.appName_ +
1435 '" are invalid. This usually indicates your app was not ' +
1436 'initialized correctly. ';
1437 if ('credential' in this.firebaseOptions_) {
1438 errorMessage +=
1439 'Make sure the "credential" property provided to initializeApp() ' +
1440 'is authorized to access the specified "databaseURL" and is from the correct ' +
1441 'project.';
1442 }
1443 else if ('serviceAccount' in this.firebaseOptions_) {
1444 errorMessage +=
1445 'Make sure the "serviceAccount" property provided to initializeApp() ' +
1446 'is authorized to access the specified "databaseURL" and is from the correct ' +
1447 'project.';
1448 }
1449 else {
1450 errorMessage +=
1451 'Make sure the "apiKey" and "databaseURL" properties provided to ' +
1452 'initializeApp() match the values provided for your app at ' +
1453 'https://console.firebase.google.com/.';
1454 }
1455 warn(errorMessage);
1456 };
1457 return FirebaseAuthTokenProvider;
1458}());
1459/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */
1460var EmulatorTokenProvider = /** @class */ (function () {
1461 function EmulatorTokenProvider(accessToken) {
1462 this.accessToken = accessToken;
1463 }
1464 EmulatorTokenProvider.prototype.getToken = function (forceRefresh) {
1465 return Promise.resolve({
1466 accessToken: this.accessToken
1467 });
1468 };
1469 EmulatorTokenProvider.prototype.addTokenChangeListener = function (listener) {
1470 // Invoke the listener immediately to match the behavior in Firebase Auth
1471 // (see packages/auth/src/auth.js#L1807)
1472 listener(this.accessToken);
1473 };
1474 EmulatorTokenProvider.prototype.removeTokenChangeListener = function (listener) { };
1475 EmulatorTokenProvider.prototype.notifyForInvalidToken = function () { };
1476 /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */
1477 EmulatorTokenProvider.OWNER = 'owner';
1478 return EmulatorTokenProvider;
1479}());
1480
1481/**
1482 * @license
1483 * Copyright 2017 Google LLC
1484 *
1485 * Licensed under the Apache License, Version 2.0 (the "License");
1486 * you may not use this file except in compliance with the License.
1487 * You may obtain a copy of the License at
1488 *
1489 * http://www.apache.org/licenses/LICENSE-2.0
1490 *
1491 * Unless required by applicable law or agreed to in writing, software
1492 * distributed under the License is distributed on an "AS IS" BASIS,
1493 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1494 * See the License for the specific language governing permissions and
1495 * limitations under the License.
1496 */
1497/**
1498 * This class ensures the packets from the server arrive in order
1499 * This class takes data from the server and ensures it gets passed into the callbacks in order.
1500 */
1501var PacketReceiver = /** @class */ (function () {
1502 /**
1503 * @param onMessage_
1504 */
1505 function PacketReceiver(onMessage_) {
1506 this.onMessage_ = onMessage_;
1507 this.pendingResponses = [];
1508 this.currentResponseNum = 0;
1509 this.closeAfterResponse = -1;
1510 this.onClose = null;
1511 }
1512 PacketReceiver.prototype.closeAfter = function (responseNum, callback) {
1513 this.closeAfterResponse = responseNum;
1514 this.onClose = callback;
1515 if (this.closeAfterResponse < this.currentResponseNum) {
1516 this.onClose();
1517 this.onClose = null;
1518 }
1519 };
1520 /**
1521 * Each message from the server comes with a response number, and an array of data. The responseNumber
1522 * allows us to ensure that we process them in the right order, since we can't be guaranteed that all
1523 * browsers will respond in the same order as the requests we sent
1524 */
1525 PacketReceiver.prototype.handleResponse = function (requestNum, data) {
1526 var _this = this;
1527 this.pendingResponses[requestNum] = data;
1528 var _loop_1 = function () {
1529 var toProcess = this_1.pendingResponses[this_1.currentResponseNum];
1530 delete this_1.pendingResponses[this_1.currentResponseNum];
1531 var _loop_2 = function (i) {
1532 if (toProcess[i]) {
1533 exceptionGuard(function () {
1534 _this.onMessage_(toProcess[i]);
1535 });
1536 }
1537 };
1538 for (var i = 0; i < toProcess.length; ++i) {
1539 _loop_2(i);
1540 }
1541 if (this_1.currentResponseNum === this_1.closeAfterResponse) {
1542 if (this_1.onClose) {
1543 this_1.onClose();
1544 this_1.onClose = null;
1545 }
1546 return "break";
1547 }
1548 this_1.currentResponseNum++;
1549 };
1550 var this_1 = this;
1551 while (this.pendingResponses[this.currentResponseNum]) {
1552 var state_1 = _loop_1();
1553 if (state_1 === "break")
1554 break;
1555 }
1556 };
1557 return PacketReceiver;
1558}());
1559
1560/**
1561 * @license
1562 * Copyright 2017 Google LLC
1563 *
1564 * Licensed under the Apache License, Version 2.0 (the "License");
1565 * you may not use this file except in compliance with the License.
1566 * You may obtain a copy of the License at
1567 *
1568 * http://www.apache.org/licenses/LICENSE-2.0
1569 *
1570 * Unless required by applicable law or agreed to in writing, software
1571 * distributed under the License is distributed on an "AS IS" BASIS,
1572 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1573 * See the License for the specific language governing permissions and
1574 * limitations under the License.
1575 */
1576// URL query parameters associated with longpolling
1577var FIREBASE_LONGPOLL_START_PARAM = 'start';
1578var FIREBASE_LONGPOLL_CLOSE_COMMAND = 'close';
1579var FIREBASE_LONGPOLL_COMMAND_CB_NAME = 'pLPCommand';
1580var FIREBASE_LONGPOLL_DATA_CB_NAME = 'pRTLPCB';
1581var FIREBASE_LONGPOLL_ID_PARAM = 'id';
1582var FIREBASE_LONGPOLL_PW_PARAM = 'pw';
1583var FIREBASE_LONGPOLL_SERIAL_PARAM = 'ser';
1584var FIREBASE_LONGPOLL_CALLBACK_ID_PARAM = 'cb';
1585var FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM = 'seg';
1586var FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET = 'ts';
1587var FIREBASE_LONGPOLL_DATA_PARAM = 'd';
1588var FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM = 'dframe';
1589//Data size constants.
1590//TODO: Perf: the maximum length actually differs from browser to browser.
1591// We should check what browser we're on and set accordingly.
1592var MAX_URL_DATA_SIZE = 1870;
1593var SEG_HEADER_SIZE = 30; //ie: &seg=8299234&ts=982389123&d=
1594var MAX_PAYLOAD_SIZE = MAX_URL_DATA_SIZE - SEG_HEADER_SIZE;
1595/**
1596 * Keepalive period
1597 * send a fresh request at minimum every 25 seconds. Opera has a maximum request
1598 * length of 30 seconds that we can't exceed.
1599 */
1600var KEEPALIVE_REQUEST_INTERVAL = 25000;
1601/**
1602 * How long to wait before aborting a long-polling connection attempt.
1603 */
1604var LP_CONNECT_TIMEOUT = 30000;
1605/**
1606 * This class manages a single long-polling connection.
1607 */
1608var BrowserPollConnection = /** @class */ (function () {
1609 /**
1610 * @param connId An identifier for this connection, used for logging
1611 * @param repoInfo The info for the endpoint to send data to.
1612 * @param applicationId The Firebase App ID for this project.
1613 * @param appCheckToken The AppCheck token for this client.
1614 * @param authToken The AuthToken to use for this connection.
1615 * @param transportSessionId Optional transportSessionid if we are
1616 * reconnecting for an existing transport session
1617 * @param lastSessionId Optional lastSessionId if the PersistentConnection has
1618 * already created a connection previously
1619 */
1620 function BrowserPollConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1621 var _this = this;
1622 this.connId = connId;
1623 this.repoInfo = repoInfo;
1624 this.applicationId = applicationId;
1625 this.appCheckToken = appCheckToken;
1626 this.authToken = authToken;
1627 this.transportSessionId = transportSessionId;
1628 this.lastSessionId = lastSessionId;
1629 this.bytesSent = 0;
1630 this.bytesReceived = 0;
1631 this.everConnected_ = false;
1632 this.log_ = logWrapper(connId);
1633 this.stats_ = statsManagerGetCollection(repoInfo);
1634 this.urlFn = function (params) {
1635 // Always add the token if we have one.
1636 if (_this.appCheckToken) {
1637 params[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1638 }
1639 return repoInfoConnectionURL(repoInfo, LONG_POLLING, params);
1640 };
1641 }
1642 /**
1643 * @param onMessage - Callback when messages arrive
1644 * @param onDisconnect - Callback with connection lost.
1645 */
1646 BrowserPollConnection.prototype.open = function (onMessage, onDisconnect) {
1647 var _this = this;
1648 this.curSegmentNum = 0;
1649 this.onDisconnect_ = onDisconnect;
1650 this.myPacketOrderer = new PacketReceiver(onMessage);
1651 this.isClosed_ = false;
1652 this.connectTimeoutTimer_ = setTimeout(function () {
1653 _this.log_('Timed out trying to connect.');
1654 // Make sure we clear the host cache
1655 _this.onClosed_();
1656 _this.connectTimeoutTimer_ = null;
1657 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1658 }, Math.floor(LP_CONNECT_TIMEOUT));
1659 // Ensure we delay the creation of the iframe until the DOM is loaded.
1660 executeWhenDOMReady(function () {
1661 if (_this.isClosed_) {
1662 return;
1663 }
1664 //Set up a callback that gets triggered once a connection is set up.
1665 _this.scriptTagHolder = new FirebaseIFrameScriptHolder(function () {
1666 var args = [];
1667 for (var _i = 0; _i < arguments.length; _i++) {
1668 args[_i] = arguments[_i];
1669 }
1670 var _a = tslib.__read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2]; _a[3]; _a[4];
1671 _this.incrementIncomingBytes_(args);
1672 if (!_this.scriptTagHolder) {
1673 return; // we closed the connection.
1674 }
1675 if (_this.connectTimeoutTimer_) {
1676 clearTimeout(_this.connectTimeoutTimer_);
1677 _this.connectTimeoutTimer_ = null;
1678 }
1679 _this.everConnected_ = true;
1680 if (command === FIREBASE_LONGPOLL_START_PARAM) {
1681 _this.id = arg1;
1682 _this.password = arg2;
1683 }
1684 else if (command === FIREBASE_LONGPOLL_CLOSE_COMMAND) {
1685 // Don't clear the host cache. We got a response from the server, so we know it's reachable
1686 if (arg1) {
1687 // We aren't expecting any more data (other than what the server's already in the process of sending us
1688 // through our already open polls), so don't send any more.
1689 _this.scriptTagHolder.sendNewPolls = false;
1690 // arg1 in this case is the last response number sent by the server. We should try to receive
1691 // all of the responses up to this one before closing
1692 _this.myPacketOrderer.closeAfter(arg1, function () {
1693 _this.onClosed_();
1694 });
1695 }
1696 else {
1697 _this.onClosed_();
1698 }
1699 }
1700 else {
1701 throw new Error('Unrecognized command received: ' + command);
1702 }
1703 }, function () {
1704 var args = [];
1705 for (var _i = 0; _i < arguments.length; _i++) {
1706 args[_i] = arguments[_i];
1707 }
1708 var _a = tslib.__read(args, 2), pN = _a[0], data = _a[1];
1709 _this.incrementIncomingBytes_(args);
1710 _this.myPacketOrderer.handleResponse(pN, data);
1711 }, function () {
1712 _this.onClosed_();
1713 }, _this.urlFn);
1714 //Send the initial request to connect. The serial number is simply to keep the browser from pulling previous results
1715 //from cache.
1716 var urlParams = {};
1717 urlParams[FIREBASE_LONGPOLL_START_PARAM] = 't';
1718 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = Math.floor(Math.random() * 100000000);
1719 if (_this.scriptTagHolder.uniqueCallbackIdentifier) {
1720 urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] =
1721 _this.scriptTagHolder.uniqueCallbackIdentifier;
1722 }
1723 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1724 if (_this.transportSessionId) {
1725 urlParams[TRANSPORT_SESSION_PARAM] = _this.transportSessionId;
1726 }
1727 if (_this.lastSessionId) {
1728 urlParams[LAST_SESSION_PARAM] = _this.lastSessionId;
1729 }
1730 if (_this.applicationId) {
1731 urlParams[APPLICATION_ID_PARAM] = _this.applicationId;
1732 }
1733 if (_this.appCheckToken) {
1734 urlParams[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1735 }
1736 if (typeof location !== 'undefined' &&
1737 location.hostname &&
1738 FORGE_DOMAIN_RE.test(location.hostname)) {
1739 urlParams[REFERER_PARAM] = FORGE_REF;
1740 }
1741 var connectURL = _this.urlFn(urlParams);
1742 _this.log_('Connecting via long-poll to ' + connectURL);
1743 _this.scriptTagHolder.addTag(connectURL, function () {
1744 /* do nothing */
1745 });
1746 });
1747 };
1748 /**
1749 * Call this when a handshake has completed successfully and we want to consider the connection established
1750 */
1751 BrowserPollConnection.prototype.start = function () {
1752 this.scriptTagHolder.startLongPoll(this.id, this.password);
1753 this.addDisconnectPingFrame(this.id, this.password);
1754 };
1755 /**
1756 * Forces long polling to be considered as a potential transport
1757 */
1758 BrowserPollConnection.forceAllow = function () {
1759 BrowserPollConnection.forceAllow_ = true;
1760 };
1761 /**
1762 * Forces longpolling to not be considered as a potential transport
1763 */
1764 BrowserPollConnection.forceDisallow = function () {
1765 BrowserPollConnection.forceDisallow_ = true;
1766 };
1767 // Static method, use string literal so it can be accessed in a generic way
1768 BrowserPollConnection.isAvailable = function () {
1769 if (util.isNodeSdk()) {
1770 return false;
1771 }
1772 else if (BrowserPollConnection.forceAllow_) {
1773 return true;
1774 }
1775 else {
1776 // NOTE: In React-Native there's normally no 'document', but if you debug a React-Native app in
1777 // the Chrome debugger, 'document' is defined, but document.createElement is null (2015/06/08).
1778 return (!BrowserPollConnection.forceDisallow_ &&
1779 typeof document !== 'undefined' &&
1780 document.createElement != null &&
1781 !isChromeExtensionContentScript() &&
1782 !isWindowsStoreApp());
1783 }
1784 };
1785 /**
1786 * No-op for polling
1787 */
1788 BrowserPollConnection.prototype.markConnectionHealthy = function () { };
1789 /**
1790 * Stops polling and cleans up the iframe
1791 */
1792 BrowserPollConnection.prototype.shutdown_ = function () {
1793 this.isClosed_ = true;
1794 if (this.scriptTagHolder) {
1795 this.scriptTagHolder.close();
1796 this.scriptTagHolder = null;
1797 }
1798 //remove the disconnect frame, which will trigger an XHR call to the server to tell it we're leaving.
1799 if (this.myDisconnFrame) {
1800 document.body.removeChild(this.myDisconnFrame);
1801 this.myDisconnFrame = null;
1802 }
1803 if (this.connectTimeoutTimer_) {
1804 clearTimeout(this.connectTimeoutTimer_);
1805 this.connectTimeoutTimer_ = null;
1806 }
1807 };
1808 /**
1809 * Triggered when this transport is closed
1810 */
1811 BrowserPollConnection.prototype.onClosed_ = function () {
1812 if (!this.isClosed_) {
1813 this.log_('Longpoll is closing itself');
1814 this.shutdown_();
1815 if (this.onDisconnect_) {
1816 this.onDisconnect_(this.everConnected_);
1817 this.onDisconnect_ = null;
1818 }
1819 }
1820 };
1821 /**
1822 * External-facing close handler. RealTime has requested we shut down. Kill our connection and tell the server
1823 * that we've left.
1824 */
1825 BrowserPollConnection.prototype.close = function () {
1826 if (!this.isClosed_) {
1827 this.log_('Longpoll is being closed.');
1828 this.shutdown_();
1829 }
1830 };
1831 /**
1832 * Send the JSON object down to the server. It will need to be stringified, base64 encoded, and then
1833 * broken into chunks (since URLs have a small maximum length).
1834 * @param data - The JSON data to transmit.
1835 */
1836 BrowserPollConnection.prototype.send = function (data) {
1837 var dataStr = util.stringify(data);
1838 this.bytesSent += dataStr.length;
1839 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1840 //first, lets get the base64-encoded data
1841 var base64data = util.base64Encode(dataStr);
1842 //We can only fit a certain amount in each URL, so we need to split this request
1843 //up into multiple pieces if it doesn't fit in one request.
1844 var dataSegs = splitStringBySize(base64data, MAX_PAYLOAD_SIZE);
1845 //Enqueue each segment for transmission. We assign each chunk a sequential ID and a total number
1846 //of segments so that we can reassemble the packet on the server.
1847 for (var i = 0; i < dataSegs.length; i++) {
1848 this.scriptTagHolder.enqueueSegment(this.curSegmentNum, dataSegs.length, dataSegs[i]);
1849 this.curSegmentNum++;
1850 }
1851 };
1852 /**
1853 * This is how we notify the server that we're leaving.
1854 * We aren't able to send requests with DHTML on a window close event, but we can
1855 * trigger XHR requests in some browsers (everything but Opera basically).
1856 */
1857 BrowserPollConnection.prototype.addDisconnectPingFrame = function (id, pw) {
1858 if (util.isNodeSdk()) {
1859 return;
1860 }
1861 this.myDisconnFrame = document.createElement('iframe');
1862 var urlParams = {};
1863 urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
1864 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = id;
1865 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = pw;
1866 this.myDisconnFrame.src = this.urlFn(urlParams);
1867 this.myDisconnFrame.style.display = 'none';
1868 document.body.appendChild(this.myDisconnFrame);
1869 };
1870 /**
1871 * Used to track the bytes received by this client
1872 */
1873 BrowserPollConnection.prototype.incrementIncomingBytes_ = function (args) {
1874 // TODO: This is an annoying perf hit just to track the number of incoming bytes. Maybe it should be opt-in.
1875 var bytesReceived = util.stringify(args).length;
1876 this.bytesReceived += bytesReceived;
1877 this.stats_.incrementCounter('bytes_received', bytesReceived);
1878 };
1879 return BrowserPollConnection;
1880}());
1881/*********************************************************************************************
1882 * A wrapper around an iframe that is used as a long-polling script holder.
1883 *********************************************************************************************/
1884var FirebaseIFrameScriptHolder = /** @class */ (function () {
1885 /**
1886 * @param commandCB - The callback to be called when control commands are recevied from the server.
1887 * @param onMessageCB - The callback to be triggered when responses arrive from the server.
1888 * @param onDisconnect - The callback to be triggered when this tag holder is closed
1889 * @param urlFn - A function that provides the URL of the endpoint to send data to.
1890 */
1891 function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnect, urlFn) {
1892 this.onDisconnect = onDisconnect;
1893 this.urlFn = urlFn;
1894 //We maintain a count of all of the outstanding requests, because if we have too many active at once it can cause
1895 //problems in some browsers.
1896 this.outstandingRequests = new Set();
1897 //A queue of the pending segments waiting for transmission to the server.
1898 this.pendingSegs = [];
1899 //A serial number. We use this for two things:
1900 // 1) A way to ensure the browser doesn't cache responses to polls
1901 // 2) A way to make the server aware when long-polls arrive in a different order than we started them. The
1902 // server needs to release both polls in this case or it will cause problems in Opera since Opera can only execute
1903 // JSONP code in the order it was added to the iframe.
1904 this.currentSerial = Math.floor(Math.random() * 100000000);
1905 // This gets set to false when we're "closing down" the connection (e.g. we're switching transports but there's still
1906 // incoming data from the server that we're waiting for).
1907 this.sendNewPolls = true;
1908 if (!util.isNodeSdk()) {
1909 //Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
1910 //iframes where we put the long-polling script tags. We have two callbacks:
1911 // 1) Command Callback - Triggered for control issues, like starting a connection.
1912 // 2) Message Callback - Triggered when new data arrives.
1913 this.uniqueCallbackIdentifier = LUIDGenerator();
1914 window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
1915 window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] =
1916 onMessageCB;
1917 //Create an iframe for us to add script tags to.
1918 this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_();
1919 // Set the iframe's contents.
1920 var script = '';
1921 // if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
1922 // for ie9, but ie8 needs to do it again in the document itself.
1923 if (this.myIFrame.src &&
1924 this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
1925 var currentDomain = document.domain;
1926 script = '<script>document.domain="' + currentDomain + '";</script>';
1927 }
1928 var iframeContents = '<html><body>' + script + '</body></html>';
1929 try {
1930 this.myIFrame.doc.open();
1931 this.myIFrame.doc.write(iframeContents);
1932 this.myIFrame.doc.close();
1933 }
1934 catch (e) {
1935 log('frame writing exception');
1936 if (e.stack) {
1937 log(e.stack);
1938 }
1939 log(e);
1940 }
1941 }
1942 else {
1943 this.commandCB = commandCB;
1944 this.onMessageCB = onMessageCB;
1945 }
1946 }
1947 /**
1948 * Each browser has its own funny way to handle iframes. Here we mush them all together into one object that I can
1949 * actually use.
1950 */
1951 FirebaseIFrameScriptHolder.createIFrame_ = function () {
1952 var iframe = document.createElement('iframe');
1953 iframe.style.display = 'none';
1954 // This is necessary in order to initialize the document inside the iframe
1955 if (document.body) {
1956 document.body.appendChild(iframe);
1957 try {
1958 // If document.domain has been modified in IE, this will throw an error, and we need to set the
1959 // domain of the iframe's document manually. We can do this via a javascript: url as the src attribute
1960 // Also note that we must do this *after* the iframe has been appended to the page. Otherwise it doesn't work.
1961 var a = iframe.contentWindow.document;
1962 if (!a) {
1963 // Apologies for the log-spam, I need to do something to keep closure from optimizing out the assignment above.
1964 log('No IE domain setting required');
1965 }
1966 }
1967 catch (e) {
1968 var domain = document.domain;
1969 iframe.src =
1970 "javascript:void((function(){document.open();document.domain='" +
1971 domain +
1972 "';document.close();})())";
1973 }
1974 }
1975 else {
1976 // LongPollConnection attempts to delay initialization until the document is ready, so hopefully this
1977 // never gets hit.
1978 throw 'Document body has not initialized. Wait to initialize Firebase until after the document is ready.';
1979 }
1980 // Get the document of the iframe in a browser-specific way.
1981 if (iframe.contentDocument) {
1982 iframe.doc = iframe.contentDocument; // Firefox, Opera, Safari
1983 }
1984 else if (iframe.contentWindow) {
1985 iframe.doc = iframe.contentWindow.document; // Internet Explorer
1986 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1987 }
1988 else if (iframe.document) {
1989 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1990 iframe.doc = iframe.document; //others?
1991 }
1992 return iframe;
1993 };
1994 /**
1995 * Cancel all outstanding queries and remove the frame.
1996 */
1997 FirebaseIFrameScriptHolder.prototype.close = function () {
1998 var _this = this;
1999 //Mark this iframe as dead, so no new requests are sent.
2000 this.alive = false;
2001 if (this.myIFrame) {
2002 //We have to actually remove all of the html inside this iframe before removing it from the
2003 //window, or IE will continue loading and executing the script tags we've already added, which
2004 //can lead to some errors being thrown. Setting innerHTML seems to be the easiest way to do this.
2005 this.myIFrame.doc.body.innerHTML = '';
2006 setTimeout(function () {
2007 if (_this.myIFrame !== null) {
2008 document.body.removeChild(_this.myIFrame);
2009 _this.myIFrame = null;
2010 }
2011 }, Math.floor(0));
2012 }
2013 // Protect from being called recursively.
2014 var onDisconnect = this.onDisconnect;
2015 if (onDisconnect) {
2016 this.onDisconnect = null;
2017 onDisconnect();
2018 }
2019 };
2020 /**
2021 * Actually start the long-polling session by adding the first script tag(s) to the iframe.
2022 * @param id - The ID of this connection
2023 * @param pw - The password for this connection
2024 */
2025 FirebaseIFrameScriptHolder.prototype.startLongPoll = function (id, pw) {
2026 this.myID = id;
2027 this.myPW = pw;
2028 this.alive = true;
2029 //send the initial request. If there are requests queued, make sure that we transmit as many as we are currently able to.
2030 while (this.newRequest_()) { }
2031 };
2032 /**
2033 * This is called any time someone might want a script tag to be added. It adds a script tag when there aren't
2034 * too many outstanding requests and we are still alive.
2035 *
2036 * If there are outstanding packet segments to send, it sends one. If there aren't, it sends a long-poll anyways if
2037 * needed.
2038 */
2039 FirebaseIFrameScriptHolder.prototype.newRequest_ = function () {
2040 // We keep one outstanding request open all the time to receive data, but if we need to send data
2041 // (pendingSegs.length > 0) then we create a new request to send the data. The server will automatically
2042 // close the old request.
2043 if (this.alive &&
2044 this.sendNewPolls &&
2045 this.outstandingRequests.size < (this.pendingSegs.length > 0 ? 2 : 1)) {
2046 //construct our url
2047 this.currentSerial++;
2048 var urlParams = {};
2049 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
2050 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
2051 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = this.currentSerial;
2052 var theURL = this.urlFn(urlParams);
2053 //Now add as much data as we can.
2054 var curDataString = '';
2055 var i = 0;
2056 while (this.pendingSegs.length > 0) {
2057 //first, lets see if the next segment will fit.
2058 var nextSeg = this.pendingSegs[0];
2059 if (nextSeg.d.length +
2060 SEG_HEADER_SIZE +
2061 curDataString.length <=
2062 MAX_URL_DATA_SIZE) {
2063 //great, the segment will fit. Lets append it.
2064 var theSeg = this.pendingSegs.shift();
2065 curDataString =
2066 curDataString +
2067 '&' +
2068 FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM +
2069 i +
2070 '=' +
2071 theSeg.seg +
2072 '&' +
2073 FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET +
2074 i +
2075 '=' +
2076 theSeg.ts +
2077 '&' +
2078 FIREBASE_LONGPOLL_DATA_PARAM +
2079 i +
2080 '=' +
2081 theSeg.d;
2082 i++;
2083 }
2084 else {
2085 break;
2086 }
2087 }
2088 theURL = theURL + curDataString;
2089 this.addLongPollTag_(theURL, this.currentSerial);
2090 return true;
2091 }
2092 else {
2093 return false;
2094 }
2095 };
2096 /**
2097 * Queue a packet for transmission to the server.
2098 * @param segnum - A sequential id for this packet segment used for reassembly
2099 * @param totalsegs - The total number of segments in this packet
2100 * @param data - The data for this segment.
2101 */
2102 FirebaseIFrameScriptHolder.prototype.enqueueSegment = function (segnum, totalsegs, data) {
2103 //add this to the queue of segments to send.
2104 this.pendingSegs.push({ seg: segnum, ts: totalsegs, d: data });
2105 //send the data immediately if there isn't already data being transmitted, unless
2106 //startLongPoll hasn't been called yet.
2107 if (this.alive) {
2108 this.newRequest_();
2109 }
2110 };
2111 /**
2112 * Add a script tag for a regular long-poll request.
2113 * @param url - The URL of the script tag.
2114 * @param serial - The serial number of the request.
2115 */
2116 FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function (url, serial) {
2117 var _this = this;
2118 //remember that we sent this request.
2119 this.outstandingRequests.add(serial);
2120 var doNewRequest = function () {
2121 _this.outstandingRequests.delete(serial);
2122 _this.newRequest_();
2123 };
2124 // If this request doesn't return on its own accord (by the server sending us some data), we'll
2125 // create a new one after the KEEPALIVE interval to make sure we always keep a fresh request open.
2126 var keepaliveTimeout = setTimeout(doNewRequest, Math.floor(KEEPALIVE_REQUEST_INTERVAL));
2127 var readyStateCB = function () {
2128 // Request completed. Cancel the keepalive.
2129 clearTimeout(keepaliveTimeout);
2130 // Trigger a new request so we can continue receiving data.
2131 doNewRequest();
2132 };
2133 this.addTag(url, readyStateCB);
2134 };
2135 /**
2136 * Add an arbitrary script tag to the iframe.
2137 * @param url - The URL for the script tag source.
2138 * @param loadCB - A callback to be triggered once the script has loaded.
2139 */
2140 FirebaseIFrameScriptHolder.prototype.addTag = function (url, loadCB) {
2141 var _this = this;
2142 if (util.isNodeSdk()) {
2143 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2144 this.doNodeLongPoll(url, loadCB);
2145 }
2146 else {
2147 setTimeout(function () {
2148 try {
2149 // if we're already closed, don't add this poll
2150 if (!_this.sendNewPolls) {
2151 return;
2152 }
2153 var newScript_1 = _this.myIFrame.doc.createElement('script');
2154 newScript_1.type = 'text/javascript';
2155 newScript_1.async = true;
2156 newScript_1.src = url;
2157 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2158 newScript_1.onload = newScript_1.onreadystatechange =
2159 function () {
2160 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2161 var rstate = newScript_1.readyState;
2162 if (!rstate || rstate === 'loaded' || rstate === 'complete') {
2163 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2164 newScript_1.onload = newScript_1.onreadystatechange = null;
2165 if (newScript_1.parentNode) {
2166 newScript_1.parentNode.removeChild(newScript_1);
2167 }
2168 loadCB();
2169 }
2170 };
2171 newScript_1.onerror = function () {
2172 log('Long-poll script failed to load: ' + url);
2173 _this.sendNewPolls = false;
2174 _this.close();
2175 };
2176 _this.myIFrame.doc.body.appendChild(newScript_1);
2177 }
2178 catch (e) {
2179 // TODO: we should make this error visible somehow
2180 }
2181 }, Math.floor(1));
2182 }
2183 };
2184 return FirebaseIFrameScriptHolder;
2185}());
2186
2187/**
2188 * @license
2189 * Copyright 2017 Google LLC
2190 *
2191 * Licensed under the Apache License, Version 2.0 (the "License");
2192 * you may not use this file except in compliance with the License.
2193 * You may obtain a copy of the License at
2194 *
2195 * http://www.apache.org/licenses/LICENSE-2.0
2196 *
2197 * Unless required by applicable law or agreed to in writing, software
2198 * distributed under the License is distributed on an "AS IS" BASIS,
2199 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2200 * See the License for the specific language governing permissions and
2201 * limitations under the License.
2202 */
2203/**
2204 * Currently simplistic, this class manages what transport a Connection should use at various stages of its
2205 * lifecycle.
2206 *
2207 * It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
2208 * they are available.
2209 */
2210var TransportManager = /** @class */ (function () {
2211 /**
2212 * @param repoInfo - Metadata around the namespace we're connecting to
2213 */
2214 function TransportManager(repoInfo) {
2215 this.initTransports_(repoInfo);
2216 }
2217 Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
2218 get: function () {
2219 return [BrowserPollConnection, WebSocketConnection];
2220 },
2221 enumerable: false,
2222 configurable: true
2223 });
2224 Object.defineProperty(TransportManager, "IS_TRANSPORT_INITIALIZED", {
2225 /**
2226 * Returns whether transport has been selected to ensure WebSocketConnection or BrowserPollConnection are not called after
2227 * TransportManager has already set up transports_
2228 */
2229 get: function () {
2230 return this.globalTransportInitialized_;
2231 },
2232 enumerable: false,
2233 configurable: true
2234 });
2235 TransportManager.prototype.initTransports_ = function (repoInfo) {
2236 var e_1, _a;
2237 var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
2238 var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
2239 if (repoInfo.webSocketOnly) {
2240 if (!isWebSocketsAvailable) {
2241 warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
2242 }
2243 isSkipPollConnection = true;
2244 }
2245 if (isSkipPollConnection) {
2246 this.transports_ = [WebSocketConnection];
2247 }
2248 else {
2249 var transports = (this.transports_ = []);
2250 try {
2251 for (var _b = tslib.__values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
2252 var transport = _c.value;
2253 if (transport && transport['isAvailable']()) {
2254 transports.push(transport);
2255 }
2256 }
2257 }
2258 catch (e_1_1) { e_1 = { error: e_1_1 }; }
2259 finally {
2260 try {
2261 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
2262 }
2263 finally { if (e_1) throw e_1.error; }
2264 }
2265 TransportManager.globalTransportInitialized_ = true;
2266 }
2267 };
2268 /**
2269 * @returns The constructor for the initial transport to use
2270 */
2271 TransportManager.prototype.initialTransport = function () {
2272 if (this.transports_.length > 0) {
2273 return this.transports_[0];
2274 }
2275 else {
2276 throw new Error('No transports available');
2277 }
2278 };
2279 /**
2280 * @returns The constructor for the next transport, or null
2281 */
2282 TransportManager.prototype.upgradeTransport = function () {
2283 if (this.transports_.length > 1) {
2284 return this.transports_[1];
2285 }
2286 else {
2287 return null;
2288 }
2289 };
2290 // Keeps track of whether the TransportManager has already chosen a transport to use
2291 TransportManager.globalTransportInitialized_ = false;
2292 return TransportManager;
2293}());
2294
2295/**
2296 * @license
2297 * Copyright 2017 Google LLC
2298 *
2299 * Licensed under the Apache License, Version 2.0 (the "License");
2300 * you may not use this file except in compliance with the License.
2301 * You may obtain a copy of the License at
2302 *
2303 * http://www.apache.org/licenses/LICENSE-2.0
2304 *
2305 * Unless required by applicable law or agreed to in writing, software
2306 * distributed under the License is distributed on an "AS IS" BASIS,
2307 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2308 * See the License for the specific language governing permissions and
2309 * limitations under the License.
2310 */
2311// Abort upgrade attempt if it takes longer than 60s.
2312var UPGRADE_TIMEOUT = 60000;
2313// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
2314// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
2315var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
2316// 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)
2317// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
2318// but we've sent/received enough bytes, we don't cancel the connection.
2319var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
2320var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
2321var MESSAGE_TYPE = 't';
2322var MESSAGE_DATA = 'd';
2323var CONTROL_SHUTDOWN = 's';
2324var CONTROL_RESET = 'r';
2325var CONTROL_ERROR = 'e';
2326var CONTROL_PONG = 'o';
2327var SWITCH_ACK = 'a';
2328var END_TRANSMISSION = 'n';
2329var PING = 'p';
2330var SERVER_HELLO = 'h';
2331/**
2332 * Creates a new real-time connection to the server using whichever method works
2333 * best in the current browser.
2334 */
2335var Connection = /** @class */ (function () {
2336 /**
2337 * @param id - an id for this connection
2338 * @param repoInfo_ - the info for the endpoint to connect to
2339 * @param applicationId_ - the Firebase App ID for this project
2340 * @param appCheckToken_ - The App Check Token for this device.
2341 * @param authToken_ - The auth token for this session.
2342 * @param onMessage_ - the callback to be triggered when a server-push message arrives
2343 * @param onReady_ - the callback to be triggered when this connection is ready to send messages.
2344 * @param onDisconnect_ - the callback to be triggered when a connection was lost
2345 * @param onKill_ - the callback to be triggered when this connection has permanently shut down.
2346 * @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
2347 */
2348 function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
2349 this.id = id;
2350 this.repoInfo_ = repoInfo_;
2351 this.applicationId_ = applicationId_;
2352 this.appCheckToken_ = appCheckToken_;
2353 this.authToken_ = authToken_;
2354 this.onMessage_ = onMessage_;
2355 this.onReady_ = onReady_;
2356 this.onDisconnect_ = onDisconnect_;
2357 this.onKill_ = onKill_;
2358 this.lastSessionId = lastSessionId;
2359 this.connectionCount = 0;
2360 this.pendingDataMessages = [];
2361 this.state_ = 0 /* CONNECTING */;
2362 this.log_ = logWrapper('c:' + this.id + ':');
2363 this.transportManager_ = new TransportManager(repoInfo_);
2364 this.log_('Connection created');
2365 this.start_();
2366 }
2367 /**
2368 * Starts a connection attempt
2369 */
2370 Connection.prototype.start_ = function () {
2371 var _this = this;
2372 var conn = this.transportManager_.initialTransport();
2373 this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, null, this.lastSessionId);
2374 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2375 // can consider the transport healthy.
2376 this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
2377 var onMessageReceived = this.connReceiver_(this.conn_);
2378 var onConnectionLost = this.disconnReceiver_(this.conn_);
2379 this.tx_ = this.conn_;
2380 this.rx_ = this.conn_;
2381 this.secondaryConn_ = null;
2382 this.isHealthy_ = false;
2383 /*
2384 * Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
2385 * This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
2386 * Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
2387 * still have the context of your originating frame.
2388 */
2389 setTimeout(function () {
2390 // this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
2391 _this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
2392 }, Math.floor(0));
2393 var healthyTimeoutMS = conn['healthyTimeout'] || 0;
2394 if (healthyTimeoutMS > 0) {
2395 this.healthyTimeout_ = setTimeoutNonBlocking(function () {
2396 _this.healthyTimeout_ = null;
2397 if (!_this.isHealthy_) {
2398 if (_this.conn_ &&
2399 _this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
2400 _this.log_('Connection exceeded healthy timeout but has received ' +
2401 _this.conn_.bytesReceived +
2402 ' bytes. Marking connection healthy.');
2403 _this.isHealthy_ = true;
2404 _this.conn_.markConnectionHealthy();
2405 }
2406 else if (_this.conn_ &&
2407 _this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
2408 _this.log_('Connection exceeded healthy timeout but has sent ' +
2409 _this.conn_.bytesSent +
2410 ' bytes. Leaving connection alive.');
2411 // NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
2412 // the server.
2413 }
2414 else {
2415 _this.log_('Closing unhealthy connection after timeout.');
2416 _this.close();
2417 }
2418 }
2419 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2420 }, Math.floor(healthyTimeoutMS));
2421 }
2422 };
2423 Connection.prototype.nextTransportId_ = function () {
2424 return 'c:' + this.id + ':' + this.connectionCount++;
2425 };
2426 Connection.prototype.disconnReceiver_ = function (conn) {
2427 var _this = this;
2428 return function (everConnected) {
2429 if (conn === _this.conn_) {
2430 _this.onConnectionLost_(everConnected);
2431 }
2432 else if (conn === _this.secondaryConn_) {
2433 _this.log_('Secondary connection lost.');
2434 _this.onSecondaryConnectionLost_();
2435 }
2436 else {
2437 _this.log_('closing an old connection');
2438 }
2439 };
2440 };
2441 Connection.prototype.connReceiver_ = function (conn) {
2442 var _this = this;
2443 return function (message) {
2444 if (_this.state_ !== 2 /* DISCONNECTED */) {
2445 if (conn === _this.rx_) {
2446 _this.onPrimaryMessageReceived_(message);
2447 }
2448 else if (conn === _this.secondaryConn_) {
2449 _this.onSecondaryMessageReceived_(message);
2450 }
2451 else {
2452 _this.log_('message on old connection');
2453 }
2454 }
2455 };
2456 };
2457 /**
2458 * @param dataMsg - An arbitrary data message to be sent to the server
2459 */
2460 Connection.prototype.sendRequest = function (dataMsg) {
2461 // wrap in a data message envelope and send it on
2462 var msg = { t: 'd', d: dataMsg };
2463 this.sendData_(msg);
2464 };
2465 Connection.prototype.tryCleanupConnection = function () {
2466 if (this.tx_ === this.secondaryConn_ && this.rx_ === this.secondaryConn_) {
2467 this.log_('cleaning up and promoting a connection: ' + this.secondaryConn_.connId);
2468 this.conn_ = this.secondaryConn_;
2469 this.secondaryConn_ = null;
2470 // the server will shutdown the old connection
2471 }
2472 };
2473 Connection.prototype.onSecondaryControl_ = function (controlData) {
2474 if (MESSAGE_TYPE in controlData) {
2475 var cmd = controlData[MESSAGE_TYPE];
2476 if (cmd === SWITCH_ACK) {
2477 this.upgradeIfSecondaryHealthy_();
2478 }
2479 else if (cmd === CONTROL_RESET) {
2480 // Most likely the session wasn't valid. Abandon the switch attempt
2481 this.log_('Got a reset on secondary, closing it');
2482 this.secondaryConn_.close();
2483 // If we were already using this connection for something, than we need to fully close
2484 if (this.tx_ === this.secondaryConn_ ||
2485 this.rx_ === this.secondaryConn_) {
2486 this.close();
2487 }
2488 }
2489 else if (cmd === CONTROL_PONG) {
2490 this.log_('got pong on secondary.');
2491 this.secondaryResponsesRequired_--;
2492 this.upgradeIfSecondaryHealthy_();
2493 }
2494 }
2495 };
2496 Connection.prototype.onSecondaryMessageReceived_ = function (parsedData) {
2497 var layer = requireKey('t', parsedData);
2498 var data = requireKey('d', parsedData);
2499 if (layer === 'c') {
2500 this.onSecondaryControl_(data);
2501 }
2502 else if (layer === 'd') {
2503 // got a data message, but we're still second connection. Need to buffer it up
2504 this.pendingDataMessages.push(data);
2505 }
2506 else {
2507 throw new Error('Unknown protocol layer: ' + layer);
2508 }
2509 };
2510 Connection.prototype.upgradeIfSecondaryHealthy_ = function () {
2511 if (this.secondaryResponsesRequired_ <= 0) {
2512 this.log_('Secondary connection is healthy.');
2513 this.isHealthy_ = true;
2514 this.secondaryConn_.markConnectionHealthy();
2515 this.proceedWithUpgrade_();
2516 }
2517 else {
2518 // Send a ping to make sure the connection is healthy.
2519 this.log_('sending ping on secondary.');
2520 this.secondaryConn_.send({ t: 'c', d: { t: PING, d: {} } });
2521 }
2522 };
2523 Connection.prototype.proceedWithUpgrade_ = function () {
2524 // tell this connection to consider itself open
2525 this.secondaryConn_.start();
2526 // send ack
2527 this.log_('sending client ack on secondary');
2528 this.secondaryConn_.send({ t: 'c', d: { t: SWITCH_ACK, d: {} } });
2529 // send end packet on primary transport, switch to sending on this one
2530 // can receive on this one, buffer responses until end received on primary transport
2531 this.log_('Ending transmission on primary');
2532 this.conn_.send({ t: 'c', d: { t: END_TRANSMISSION, d: {} } });
2533 this.tx_ = this.secondaryConn_;
2534 this.tryCleanupConnection();
2535 };
2536 Connection.prototype.onPrimaryMessageReceived_ = function (parsedData) {
2537 // Must refer to parsedData properties in quotes, so closure doesn't touch them.
2538 var layer = requireKey('t', parsedData);
2539 var data = requireKey('d', parsedData);
2540 if (layer === 'c') {
2541 this.onControl_(data);
2542 }
2543 else if (layer === 'd') {
2544 this.onDataMessage_(data);
2545 }
2546 };
2547 Connection.prototype.onDataMessage_ = function (message) {
2548 this.onPrimaryResponse_();
2549 // We don't do anything with data messages, just kick them up a level
2550 this.onMessage_(message);
2551 };
2552 Connection.prototype.onPrimaryResponse_ = function () {
2553 if (!this.isHealthy_) {
2554 this.primaryResponsesRequired_--;
2555 if (this.primaryResponsesRequired_ <= 0) {
2556 this.log_('Primary connection is healthy.');
2557 this.isHealthy_ = true;
2558 this.conn_.markConnectionHealthy();
2559 }
2560 }
2561 };
2562 Connection.prototype.onControl_ = function (controlData) {
2563 var cmd = requireKey(MESSAGE_TYPE, controlData);
2564 if (MESSAGE_DATA in controlData) {
2565 var payload = controlData[MESSAGE_DATA];
2566 if (cmd === SERVER_HELLO) {
2567 this.onHandshake_(payload);
2568 }
2569 else if (cmd === END_TRANSMISSION) {
2570 this.log_('recvd end transmission on primary');
2571 this.rx_ = this.secondaryConn_;
2572 for (var i = 0; i < this.pendingDataMessages.length; ++i) {
2573 this.onDataMessage_(this.pendingDataMessages[i]);
2574 }
2575 this.pendingDataMessages = [];
2576 this.tryCleanupConnection();
2577 }
2578 else if (cmd === CONTROL_SHUTDOWN) {
2579 // This was previously the 'onKill' callback passed to the lower-level connection
2580 // payload in this case is the reason for the shutdown. Generally a human-readable error
2581 this.onConnectionShutdown_(payload);
2582 }
2583 else if (cmd === CONTROL_RESET) {
2584 // payload in this case is the host we should contact
2585 this.onReset_(payload);
2586 }
2587 else if (cmd === CONTROL_ERROR) {
2588 error('Server Error: ' + payload);
2589 }
2590 else if (cmd === CONTROL_PONG) {
2591 this.log_('got pong on primary.');
2592 this.onPrimaryResponse_();
2593 this.sendPingOnPrimaryIfNecessary_();
2594 }
2595 else {
2596 error('Unknown control packet command: ' + cmd);
2597 }
2598 }
2599 };
2600 /**
2601 * @param handshake - The handshake data returned from the server
2602 */
2603 Connection.prototype.onHandshake_ = function (handshake) {
2604 var timestamp = handshake.ts;
2605 var version = handshake.v;
2606 var host = handshake.h;
2607 this.sessionId = handshake.s;
2608 this.repoInfo_.host = host;
2609 // if we've already closed the connection, then don't bother trying to progress further
2610 if (this.state_ === 0 /* CONNECTING */) {
2611 this.conn_.start();
2612 this.onConnectionEstablished_(this.conn_, timestamp);
2613 if (PROTOCOL_VERSION !== version) {
2614 warn('Protocol version mismatch detected');
2615 }
2616 // TODO: do we want to upgrade? when? maybe a delay?
2617 this.tryStartUpgrade_();
2618 }
2619 };
2620 Connection.prototype.tryStartUpgrade_ = function () {
2621 var conn = this.transportManager_.upgradeTransport();
2622 if (conn) {
2623 this.startUpgrade_(conn);
2624 }
2625 };
2626 Connection.prototype.startUpgrade_ = function (conn) {
2627 var _this = this;
2628 this.secondaryConn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, this.sessionId);
2629 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2630 // can consider the transport healthy.
2631 this.secondaryResponsesRequired_ =
2632 conn['responsesRequiredToBeHealthy'] || 0;
2633 var onMessage = this.connReceiver_(this.secondaryConn_);
2634 var onDisconnect = this.disconnReceiver_(this.secondaryConn_);
2635 this.secondaryConn_.open(onMessage, onDisconnect);
2636 // If we haven't successfully upgraded after UPGRADE_TIMEOUT, give up and kill the secondary.
2637 setTimeoutNonBlocking(function () {
2638 if (_this.secondaryConn_) {
2639 _this.log_('Timed out trying to upgrade.');
2640 _this.secondaryConn_.close();
2641 }
2642 }, Math.floor(UPGRADE_TIMEOUT));
2643 };
2644 Connection.prototype.onReset_ = function (host) {
2645 this.log_('Reset packet received. New host: ' + host);
2646 this.repoInfo_.host = host;
2647 // TODO: if we're already "connected", we need to trigger a disconnect at the next layer up.
2648 // We don't currently support resets after the connection has already been established
2649 if (this.state_ === 1 /* CONNECTED */) {
2650 this.close();
2651 }
2652 else {
2653 // Close whatever connections we have open and start again.
2654 this.closeConnections_();
2655 this.start_();
2656 }
2657 };
2658 Connection.prototype.onConnectionEstablished_ = function (conn, timestamp) {
2659 var _this = this;
2660 this.log_('Realtime connection established.');
2661 this.conn_ = conn;
2662 this.state_ = 1 /* CONNECTED */;
2663 if (this.onReady_) {
2664 this.onReady_(timestamp, this.sessionId);
2665 this.onReady_ = null;
2666 }
2667 // If after 5 seconds we haven't sent enough requests to the server to get the connection healthy,
2668 // send some pings.
2669 if (this.primaryResponsesRequired_ === 0) {
2670 this.log_('Primary connection is healthy.');
2671 this.isHealthy_ = true;
2672 }
2673 else {
2674 setTimeoutNonBlocking(function () {
2675 _this.sendPingOnPrimaryIfNecessary_();
2676 }, Math.floor(DELAY_BEFORE_SENDING_EXTRA_REQUESTS));
2677 }
2678 };
2679 Connection.prototype.sendPingOnPrimaryIfNecessary_ = function () {
2680 // If the connection isn't considered healthy yet, we'll send a noop ping packet request.
2681 if (!this.isHealthy_ && this.state_ === 1 /* CONNECTED */) {
2682 this.log_('sending ping on primary.');
2683 this.sendData_({ t: 'c', d: { t: PING, d: {} } });
2684 }
2685 };
2686 Connection.prototype.onSecondaryConnectionLost_ = function () {
2687 var conn = this.secondaryConn_;
2688 this.secondaryConn_ = null;
2689 if (this.tx_ === conn || this.rx_ === conn) {
2690 // we are relying on this connection already in some capacity. Therefore, a failure is real
2691 this.close();
2692 }
2693 };
2694 /**
2695 * @param everConnected - Whether or not the connection ever reached a server. Used to determine if
2696 * we should flush the host cache
2697 */
2698 Connection.prototype.onConnectionLost_ = function (everConnected) {
2699 this.conn_ = null;
2700 // NOTE: IF you're seeing a Firefox error for this line, I think it might be because it's getting
2701 // called on window close and RealtimeState.CONNECTING is no longer defined. Just a guess.
2702 if (!everConnected && this.state_ === 0 /* CONNECTING */) {
2703 this.log_('Realtime connection failed.');
2704 // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
2705 if (this.repoInfo_.isCacheableHost()) {
2706 PersistentStorage.remove('host:' + this.repoInfo_.host);
2707 // reset the internal host to what we would show the user, i.e. <ns>.firebaseio.com
2708 this.repoInfo_.internalHost = this.repoInfo_.host;
2709 }
2710 }
2711 else if (this.state_ === 1 /* CONNECTED */) {
2712 this.log_('Realtime connection lost.');
2713 }
2714 this.close();
2715 };
2716 Connection.prototype.onConnectionShutdown_ = function (reason) {
2717 this.log_('Connection shutdown command received. Shutting down...');
2718 if (this.onKill_) {
2719 this.onKill_(reason);
2720 this.onKill_ = null;
2721 }
2722 // We intentionally don't want to fire onDisconnect (kill is a different case),
2723 // so clear the callback.
2724 this.onDisconnect_ = null;
2725 this.close();
2726 };
2727 Connection.prototype.sendData_ = function (data) {
2728 if (this.state_ !== 1 /* CONNECTED */) {
2729 throw 'Connection is not connected';
2730 }
2731 else {
2732 this.tx_.send(data);
2733 }
2734 };
2735 /**
2736 * Cleans up this connection, calling the appropriate callbacks
2737 */
2738 Connection.prototype.close = function () {
2739 if (this.state_ !== 2 /* DISCONNECTED */) {
2740 this.log_('Closing realtime connection.');
2741 this.state_ = 2 /* DISCONNECTED */;
2742 this.closeConnections_();
2743 if (this.onDisconnect_) {
2744 this.onDisconnect_();
2745 this.onDisconnect_ = null;
2746 }
2747 }
2748 };
2749 Connection.prototype.closeConnections_ = function () {
2750 this.log_('Shutting down all connections');
2751 if (this.conn_) {
2752 this.conn_.close();
2753 this.conn_ = null;
2754 }
2755 if (this.secondaryConn_) {
2756 this.secondaryConn_.close();
2757 this.secondaryConn_ = null;
2758 }
2759 if (this.healthyTimeout_) {
2760 clearTimeout(this.healthyTimeout_);
2761 this.healthyTimeout_ = null;
2762 }
2763 };
2764 return Connection;
2765}());
2766
2767/**
2768 * @license
2769 * Copyright 2017 Google LLC
2770 *
2771 * Licensed under the Apache License, Version 2.0 (the "License");
2772 * you may not use this file except in compliance with the License.
2773 * You may obtain a copy of the License at
2774 *
2775 * http://www.apache.org/licenses/LICENSE-2.0
2776 *
2777 * Unless required by applicable law or agreed to in writing, software
2778 * distributed under the License is distributed on an "AS IS" BASIS,
2779 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2780 * See the License for the specific language governing permissions and
2781 * limitations under the License.
2782 */
2783/**
2784 * Interface defining the set of actions that can be performed against the Firebase server
2785 * (basically corresponds to our wire protocol).
2786 *
2787 * @interface
2788 */
2789var ServerActions = /** @class */ (function () {
2790 function ServerActions() {
2791 }
2792 ServerActions.prototype.put = function (pathString, data, onComplete, hash) { };
2793 ServerActions.prototype.merge = function (pathString, data, onComplete, hash) { };
2794 /**
2795 * Refreshes the auth token for the current connection.
2796 * @param token - The authentication token
2797 */
2798 ServerActions.prototype.refreshAuthToken = function (token) { };
2799 /**
2800 * Refreshes the app check token for the current connection.
2801 * @param token The app check token
2802 */
2803 ServerActions.prototype.refreshAppCheckToken = function (token) { };
2804 ServerActions.prototype.onDisconnectPut = function (pathString, data, onComplete) { };
2805 ServerActions.prototype.onDisconnectMerge = function (pathString, data, onComplete) { };
2806 ServerActions.prototype.onDisconnectCancel = function (pathString, onComplete) { };
2807 ServerActions.prototype.reportStats = function (stats) { };
2808 return ServerActions;
2809}());
2810
2811/**
2812 * @license
2813 * Copyright 2017 Google LLC
2814 *
2815 * Licensed under the Apache License, Version 2.0 (the "License");
2816 * you may not use this file except in compliance with the License.
2817 * You may obtain a copy of the License at
2818 *
2819 * http://www.apache.org/licenses/LICENSE-2.0
2820 *
2821 * Unless required by applicable law or agreed to in writing, software
2822 * distributed under the License is distributed on an "AS IS" BASIS,
2823 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2824 * See the License for the specific language governing permissions and
2825 * limitations under the License.
2826 */
2827/**
2828 * Base class to be used if you want to emit events. Call the constructor with
2829 * the set of allowed event names.
2830 */
2831var EventEmitter = /** @class */ (function () {
2832 function EventEmitter(allowedEvents_) {
2833 this.allowedEvents_ = allowedEvents_;
2834 this.listeners_ = {};
2835 util.assert(Array.isArray(allowedEvents_) && allowedEvents_.length > 0, 'Requires a non-empty array');
2836 }
2837 /**
2838 * To be called by derived classes to trigger events.
2839 */
2840 EventEmitter.prototype.trigger = function (eventType) {
2841 var varArgs = [];
2842 for (var _i = 1; _i < arguments.length; _i++) {
2843 varArgs[_i - 1] = arguments[_i];
2844 }
2845 if (Array.isArray(this.listeners_[eventType])) {
2846 // Clone the list, since callbacks could add/remove listeners.
2847 var listeners = tslib.__spreadArray([], tslib.__read(this.listeners_[eventType]));
2848 for (var i = 0; i < listeners.length; i++) {
2849 listeners[i].callback.apply(listeners[i].context, varArgs);
2850 }
2851 }
2852 };
2853 EventEmitter.prototype.on = function (eventType, callback, context) {
2854 this.validateEventType_(eventType);
2855 this.listeners_[eventType] = this.listeners_[eventType] || [];
2856 this.listeners_[eventType].push({ callback: callback, context: context });
2857 var eventData = this.getInitialEvent(eventType);
2858 if (eventData) {
2859 callback.apply(context, eventData);
2860 }
2861 };
2862 EventEmitter.prototype.off = function (eventType, callback, context) {
2863 this.validateEventType_(eventType);
2864 var listeners = this.listeners_[eventType] || [];
2865 for (var i = 0; i < listeners.length; i++) {
2866 if (listeners[i].callback === callback &&
2867 (!context || context === listeners[i].context)) {
2868 listeners.splice(i, 1);
2869 return;
2870 }
2871 }
2872 };
2873 EventEmitter.prototype.validateEventType_ = function (eventType) {
2874 util.assert(this.allowedEvents_.find(function (et) {
2875 return et === eventType;
2876 }), 'Unknown event: ' + eventType);
2877 };
2878 return EventEmitter;
2879}());
2880
2881/**
2882 * @license
2883 * Copyright 2017 Google LLC
2884 *
2885 * Licensed under the Apache License, Version 2.0 (the "License");
2886 * you may not use this file except in compliance with the License.
2887 * You may obtain a copy of the License at
2888 *
2889 * http://www.apache.org/licenses/LICENSE-2.0
2890 *
2891 * Unless required by applicable law or agreed to in writing, software
2892 * distributed under the License is distributed on an "AS IS" BASIS,
2893 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2894 * See the License for the specific language governing permissions and
2895 * limitations under the License.
2896 */
2897/**
2898 * Monitors online state (as reported by window.online/offline events).
2899 *
2900 * The expectation is that this could have many false positives (thinks we are online
2901 * when we're not), but no false negatives. So we can safely use it to determine when
2902 * we definitely cannot reach the internet.
2903 */
2904var OnlineMonitor = /** @class */ (function (_super) {
2905 tslib.__extends(OnlineMonitor, _super);
2906 function OnlineMonitor() {
2907 var _this = _super.call(this, ['online']) || this;
2908 _this.online_ = true;
2909 // We've had repeated complaints that Cordova apps can get stuck "offline", e.g.
2910 // https://forum.ionicframework.com/t/firebase-connection-is-lost-and-never-come-back/43810
2911 // It would seem that the 'online' event does not always fire consistently. So we disable it
2912 // for Cordova.
2913 if (typeof window !== 'undefined' &&
2914 typeof window.addEventListener !== 'undefined' &&
2915 !util.isMobileCordova()) {
2916 window.addEventListener('online', function () {
2917 if (!_this.online_) {
2918 _this.online_ = true;
2919 _this.trigger('online', true);
2920 }
2921 }, false);
2922 window.addEventListener('offline', function () {
2923 if (_this.online_) {
2924 _this.online_ = false;
2925 _this.trigger('online', false);
2926 }
2927 }, false);
2928 }
2929 return _this;
2930 }
2931 OnlineMonitor.getInstance = function () {
2932 return new OnlineMonitor();
2933 };
2934 OnlineMonitor.prototype.getInitialEvent = function (eventType) {
2935 util.assert(eventType === 'online', 'Unknown event type: ' + eventType);
2936 return [this.online_];
2937 };
2938 OnlineMonitor.prototype.currentlyOnline = function () {
2939 return this.online_;
2940 };
2941 return OnlineMonitor;
2942}(EventEmitter));
2943
2944/**
2945 * @license
2946 * Copyright 2017 Google LLC
2947 *
2948 * Licensed under the Apache License, Version 2.0 (the "License");
2949 * you may not use this file except in compliance with the License.
2950 * You may obtain a copy of the License at
2951 *
2952 * http://www.apache.org/licenses/LICENSE-2.0
2953 *
2954 * Unless required by applicable law or agreed to in writing, software
2955 * distributed under the License is distributed on an "AS IS" BASIS,
2956 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2957 * See the License for the specific language governing permissions and
2958 * limitations under the License.
2959 */
2960/** Maximum key depth. */
2961var MAX_PATH_DEPTH = 32;
2962/** Maximum number of (UTF8) bytes in a Firebase path. */
2963var MAX_PATH_LENGTH_BYTES = 768;
2964/**
2965 * An immutable object representing a parsed path. It's immutable so that you
2966 * can pass them around to other functions without worrying about them changing
2967 * it.
2968 */
2969var Path = /** @class */ (function () {
2970 /**
2971 * @param pathOrString - Path string to parse, or another path, or the raw
2972 * tokens array
2973 */
2974 function Path(pathOrString, pieceNum) {
2975 if (pieceNum === void 0) {
2976 this.pieces_ = pathOrString.split('/');
2977 // Remove empty pieces.
2978 var copyTo = 0;
2979 for (var i = 0; i < this.pieces_.length; i++) {
2980 if (this.pieces_[i].length > 0) {
2981 this.pieces_[copyTo] = this.pieces_[i];
2982 copyTo++;
2983 }
2984 }
2985 this.pieces_.length = copyTo;
2986 this.pieceNum_ = 0;
2987 }
2988 else {
2989 this.pieces_ = pathOrString;
2990 this.pieceNum_ = pieceNum;
2991 }
2992 }
2993 Path.prototype.toString = function () {
2994 var pathString = '';
2995 for (var i = this.pieceNum_; i < this.pieces_.length; i++) {
2996 if (this.pieces_[i] !== '') {
2997 pathString += '/' + this.pieces_[i];
2998 }
2999 }
3000 return pathString || '/';
3001 };
3002 return Path;
3003}());
3004function newEmptyPath() {
3005 return new Path('');
3006}
3007function pathGetFront(path) {
3008 if (path.pieceNum_ >= path.pieces_.length) {
3009 return null;
3010 }
3011 return path.pieces_[path.pieceNum_];
3012}
3013/**
3014 * @returns The number of segments in this path
3015 */
3016function pathGetLength(path) {
3017 return path.pieces_.length - path.pieceNum_;
3018}
3019function pathPopFront(path) {
3020 var pieceNum = path.pieceNum_;
3021 if (pieceNum < path.pieces_.length) {
3022 pieceNum++;
3023 }
3024 return new Path(path.pieces_, pieceNum);
3025}
3026function pathGetBack(path) {
3027 if (path.pieceNum_ < path.pieces_.length) {
3028 return path.pieces_[path.pieces_.length - 1];
3029 }
3030 return null;
3031}
3032function pathToUrlEncodedString(path) {
3033 var pathString = '';
3034 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3035 if (path.pieces_[i] !== '') {
3036 pathString += '/' + encodeURIComponent(String(path.pieces_[i]));
3037 }
3038 }
3039 return pathString || '/';
3040}
3041/**
3042 * Shallow copy of the parts of the path.
3043 *
3044 */
3045function pathSlice(path, begin) {
3046 if (begin === void 0) { begin = 0; }
3047 return path.pieces_.slice(path.pieceNum_ + begin);
3048}
3049function pathParent(path) {
3050 if (path.pieceNum_ >= path.pieces_.length) {
3051 return null;
3052 }
3053 var pieces = [];
3054 for (var i = path.pieceNum_; i < path.pieces_.length - 1; i++) {
3055 pieces.push(path.pieces_[i]);
3056 }
3057 return new Path(pieces, 0);
3058}
3059function pathChild(path, childPathObj) {
3060 var pieces = [];
3061 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3062 pieces.push(path.pieces_[i]);
3063 }
3064 if (childPathObj instanceof Path) {
3065 for (var i = childPathObj.pieceNum_; i < childPathObj.pieces_.length; i++) {
3066 pieces.push(childPathObj.pieces_[i]);
3067 }
3068 }
3069 else {
3070 var childPieces = childPathObj.split('/');
3071 for (var i = 0; i < childPieces.length; i++) {
3072 if (childPieces[i].length > 0) {
3073 pieces.push(childPieces[i]);
3074 }
3075 }
3076 }
3077 return new Path(pieces, 0);
3078}
3079/**
3080 * @returns True if there are no segments in this path
3081 */
3082function pathIsEmpty(path) {
3083 return path.pieceNum_ >= path.pieces_.length;
3084}
3085/**
3086 * @returns The path from outerPath to innerPath
3087 */
3088function newRelativePath(outerPath, innerPath) {
3089 var outer = pathGetFront(outerPath), inner = pathGetFront(innerPath);
3090 if (outer === null) {
3091 return innerPath;
3092 }
3093 else if (outer === inner) {
3094 return newRelativePath(pathPopFront(outerPath), pathPopFront(innerPath));
3095 }
3096 else {
3097 throw new Error('INTERNAL ERROR: innerPath (' +
3098 innerPath +
3099 ') is not within ' +
3100 'outerPath (' +
3101 outerPath +
3102 ')');
3103 }
3104}
3105/**
3106 * @returns -1, 0, 1 if left is less, equal, or greater than the right.
3107 */
3108function pathCompare(left, right) {
3109 var leftKeys = pathSlice(left, 0);
3110 var rightKeys = pathSlice(right, 0);
3111 for (var i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
3112 var cmp = nameCompare(leftKeys[i], rightKeys[i]);
3113 if (cmp !== 0) {
3114 return cmp;
3115 }
3116 }
3117 if (leftKeys.length === rightKeys.length) {
3118 return 0;
3119 }
3120 return leftKeys.length < rightKeys.length ? -1 : 1;
3121}
3122/**
3123 * @returns true if paths are the same.
3124 */
3125function pathEquals(path, other) {
3126 if (pathGetLength(path) !== pathGetLength(other)) {
3127 return false;
3128 }
3129 for (var i = path.pieceNum_, j = other.pieceNum_; i <= path.pieces_.length; i++, j++) {
3130 if (path.pieces_[i] !== other.pieces_[j]) {
3131 return false;
3132 }
3133 }
3134 return true;
3135}
3136/**
3137 * @returns True if this path is a parent of (or the same as) other
3138 */
3139function pathContains(path, other) {
3140 var i = path.pieceNum_;
3141 var j = other.pieceNum_;
3142 if (pathGetLength(path) > pathGetLength(other)) {
3143 return false;
3144 }
3145 while (i < path.pieces_.length) {
3146 if (path.pieces_[i] !== other.pieces_[j]) {
3147 return false;
3148 }
3149 ++i;
3150 ++j;
3151 }
3152 return true;
3153}
3154/**
3155 * Dynamic (mutable) path used to count path lengths.
3156 *
3157 * This class is used to efficiently check paths for valid
3158 * length (in UTF8 bytes) and depth (used in path validation).
3159 *
3160 * Throws Error exception if path is ever invalid.
3161 *
3162 * The definition of a path always begins with '/'.
3163 */
3164var ValidationPath = /** @class */ (function () {
3165 /**
3166 * @param path - Initial Path.
3167 * @param errorPrefix_ - Prefix for any error messages.
3168 */
3169 function ValidationPath(path, errorPrefix_) {
3170 this.errorPrefix_ = errorPrefix_;
3171 this.parts_ = pathSlice(path, 0);
3172 /** Initialize to number of '/' chars needed in path. */
3173 this.byteLength_ = Math.max(1, this.parts_.length);
3174 for (var i = 0; i < this.parts_.length; i++) {
3175 this.byteLength_ += util.stringLength(this.parts_[i]);
3176 }
3177 validationPathCheckValid(this);
3178 }
3179 return ValidationPath;
3180}());
3181function validationPathPush(validationPath, child) {
3182 // Count the needed '/'
3183 if (validationPath.parts_.length > 0) {
3184 validationPath.byteLength_ += 1;
3185 }
3186 validationPath.parts_.push(child);
3187 validationPath.byteLength_ += util.stringLength(child);
3188 validationPathCheckValid(validationPath);
3189}
3190function validationPathPop(validationPath) {
3191 var last = validationPath.parts_.pop();
3192 validationPath.byteLength_ -= util.stringLength(last);
3193 // Un-count the previous '/'
3194 if (validationPath.parts_.length > 0) {
3195 validationPath.byteLength_ -= 1;
3196 }
3197}
3198function validationPathCheckValid(validationPath) {
3199 if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
3200 throw new Error(validationPath.errorPrefix_ +
3201 'has a key path longer than ' +
3202 MAX_PATH_LENGTH_BYTES +
3203 ' bytes (' +
3204 validationPath.byteLength_ +
3205 ').');
3206 }
3207 if (validationPath.parts_.length > MAX_PATH_DEPTH) {
3208 throw new Error(validationPath.errorPrefix_ +
3209 'path specified exceeds the maximum depth that can be written (' +
3210 MAX_PATH_DEPTH +
3211 ') or object contains a cycle ' +
3212 validationPathToErrorString(validationPath));
3213 }
3214}
3215/**
3216 * String for use in error messages - uses '.' notation for path.
3217 */
3218function validationPathToErrorString(validationPath) {
3219 if (validationPath.parts_.length === 0) {
3220 return '';
3221 }
3222 return "in property '" + validationPath.parts_.join('.') + "'";
3223}
3224
3225/**
3226 * @license
3227 * Copyright 2017 Google LLC
3228 *
3229 * Licensed under the Apache License, Version 2.0 (the "License");
3230 * you may not use this file except in compliance with the License.
3231 * You may obtain a copy of the License at
3232 *
3233 * http://www.apache.org/licenses/LICENSE-2.0
3234 *
3235 * Unless required by applicable law or agreed to in writing, software
3236 * distributed under the License is distributed on an "AS IS" BASIS,
3237 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3238 * See the License for the specific language governing permissions and
3239 * limitations under the License.
3240 */
3241var VisibilityMonitor = /** @class */ (function (_super) {
3242 tslib.__extends(VisibilityMonitor, _super);
3243 function VisibilityMonitor() {
3244 var _this = _super.call(this, ['visible']) || this;
3245 var hidden;
3246 var visibilityChange;
3247 if (typeof document !== 'undefined' &&
3248 typeof document.addEventListener !== 'undefined') {
3249 if (typeof document['hidden'] !== 'undefined') {
3250 // Opera 12.10 and Firefox 18 and later support
3251 visibilityChange = 'visibilitychange';
3252 hidden = 'hidden';
3253 }
3254 else if (typeof document['mozHidden'] !== 'undefined') {
3255 visibilityChange = 'mozvisibilitychange';
3256 hidden = 'mozHidden';
3257 }
3258 else if (typeof document['msHidden'] !== 'undefined') {
3259 visibilityChange = 'msvisibilitychange';
3260 hidden = 'msHidden';
3261 }
3262 else if (typeof document['webkitHidden'] !== 'undefined') {
3263 visibilityChange = 'webkitvisibilitychange';
3264 hidden = 'webkitHidden';
3265 }
3266 }
3267 // Initially, we always assume we are visible. This ensures that in browsers
3268 // without page visibility support or in cases where we are never visible
3269 // (e.g. chrome extension), we act as if we are visible, i.e. don't delay
3270 // reconnects
3271 _this.visible_ = true;
3272 if (visibilityChange) {
3273 document.addEventListener(visibilityChange, function () {
3274 var visible = !document[hidden];
3275 if (visible !== _this.visible_) {
3276 _this.visible_ = visible;
3277 _this.trigger('visible', visible);
3278 }
3279 }, false);
3280 }
3281 return _this;
3282 }
3283 VisibilityMonitor.getInstance = function () {
3284 return new VisibilityMonitor();
3285 };
3286 VisibilityMonitor.prototype.getInitialEvent = function (eventType) {
3287 util.assert(eventType === 'visible', 'Unknown event type: ' + eventType);
3288 return [this.visible_];
3289 };
3290 return VisibilityMonitor;
3291}(EventEmitter));
3292
3293/**
3294 * @license
3295 * Copyright 2017 Google LLC
3296 *
3297 * Licensed under the Apache License, Version 2.0 (the "License");
3298 * you may not use this file except in compliance with the License.
3299 * You may obtain a copy of the License at
3300 *
3301 * http://www.apache.org/licenses/LICENSE-2.0
3302 *
3303 * Unless required by applicable law or agreed to in writing, software
3304 * distributed under the License is distributed on an "AS IS" BASIS,
3305 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3306 * See the License for the specific language governing permissions and
3307 * limitations under the License.
3308 */
3309var RECONNECT_MIN_DELAY = 1000;
3310var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
3311var GET_CONNECT_TIMEOUT = 3 * 1000;
3312var RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
3313var RECONNECT_DELAY_MULTIPLIER = 1.3;
3314var RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
3315var SERVER_KILL_INTERRUPT_REASON = 'server_kill';
3316// If auth fails repeatedly, we'll assume something is wrong and log a warning / back off.
3317var INVALID_TOKEN_THRESHOLD = 3;
3318/**
3319 * Firebase connection. Abstracts wire protocol and handles reconnecting.
3320 *
3321 * NOTE: All JSON objects sent to the realtime connection must have property names enclosed
3322 * in quotes to make sure the closure compiler does not minify them.
3323 */
3324var PersistentConnection = /** @class */ (function (_super) {
3325 tslib.__extends(PersistentConnection, _super);
3326 /**
3327 * @param repoInfo_ - Data about the namespace we are connecting to
3328 * @param applicationId_ - The Firebase App ID for this project
3329 * @param onDataUpdate_ - A callback for new data from the server
3330 */
3331 function PersistentConnection(repoInfo_, applicationId_, onDataUpdate_, onConnectStatus_, onServerInfoUpdate_, authTokenProvider_, appCheckTokenProvider_, authOverride_) {
3332 var _this = _super.call(this) || this;
3333 _this.repoInfo_ = repoInfo_;
3334 _this.applicationId_ = applicationId_;
3335 _this.onDataUpdate_ = onDataUpdate_;
3336 _this.onConnectStatus_ = onConnectStatus_;
3337 _this.onServerInfoUpdate_ = onServerInfoUpdate_;
3338 _this.authTokenProvider_ = authTokenProvider_;
3339 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
3340 _this.authOverride_ = authOverride_;
3341 // Used for diagnostic logging.
3342 _this.id = PersistentConnection.nextPersistentConnectionId_++;
3343 _this.log_ = logWrapper('p:' + _this.id + ':');
3344 _this.interruptReasons_ = {};
3345 _this.listens = new Map();
3346 _this.outstandingPuts_ = [];
3347 _this.outstandingGets_ = [];
3348 _this.outstandingPutCount_ = 0;
3349 _this.outstandingGetCount_ = 0;
3350 _this.onDisconnectRequestQueue_ = [];
3351 _this.connected_ = false;
3352 _this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3353 _this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_DEFAULT;
3354 _this.securityDebugCallback_ = null;
3355 _this.lastSessionId = null;
3356 _this.establishConnectionTimer_ = null;
3357 _this.visible_ = false;
3358 // Before we get connected, we keep a queue of pending messages to send.
3359 _this.requestCBHash_ = {};
3360 _this.requestNumber_ = 0;
3361 _this.realtime_ = null;
3362 _this.authToken_ = null;
3363 _this.appCheckToken_ = null;
3364 _this.forceTokenRefresh_ = false;
3365 _this.invalidAuthTokenCount_ = 0;
3366 _this.invalidAppCheckTokenCount_ = 0;
3367 _this.firstConnection_ = true;
3368 _this.lastConnectionAttemptTime_ = null;
3369 _this.lastConnectionEstablishedTime_ = null;
3370 if (authOverride_ && !util.isNodeSdk()) {
3371 throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
3372 }
3373 VisibilityMonitor.getInstance().on('visible', _this.onVisible_, _this);
3374 if (repoInfo_.host.indexOf('fblocal') === -1) {
3375 OnlineMonitor.getInstance().on('online', _this.onOnline_, _this);
3376 }
3377 return _this;
3378 }
3379 PersistentConnection.prototype.sendRequest = function (action, body, onResponse) {
3380 var curReqNum = ++this.requestNumber_;
3381 var msg = { r: curReqNum, a: action, b: body };
3382 this.log_(util.stringify(msg));
3383 util.assert(this.connected_, "sendRequest call when we're not connected not allowed.");
3384 this.realtime_.sendRequest(msg);
3385 if (onResponse) {
3386 this.requestCBHash_[curReqNum] = onResponse;
3387 }
3388 };
3389 PersistentConnection.prototype.get = function (query) {
3390 var _this = this;
3391 this.initConnection_();
3392 var deferred = new util.Deferred();
3393 var request = {
3394 p: query._path.toString(),
3395 q: query._queryObject
3396 };
3397 var outstandingGet = {
3398 action: 'g',
3399 request: request,
3400 onComplete: function (message) {
3401 var payload = message['d'];
3402 if (message['s'] === 'ok') {
3403 deferred.resolve(payload);
3404 }
3405 else {
3406 deferred.reject(payload);
3407 }
3408 }
3409 };
3410 this.outstandingGets_.push(outstandingGet);
3411 this.outstandingGetCount_++;
3412 var index = this.outstandingGets_.length - 1;
3413 if (!this.connected_) {
3414 setTimeout(function () {
3415 var get = _this.outstandingGets_[index];
3416 if (get === undefined || outstandingGet !== get) {
3417 return;
3418 }
3419 delete _this.outstandingGets_[index];
3420 _this.outstandingGetCount_--;
3421 if (_this.outstandingGetCount_ === 0) {
3422 _this.outstandingGets_ = [];
3423 }
3424 _this.log_('get ' + index + ' timed out on connection');
3425 deferred.reject(new Error('Client is offline.'));
3426 }, GET_CONNECT_TIMEOUT);
3427 }
3428 if (this.connected_) {
3429 this.sendGet_(index);
3430 }
3431 return deferred.promise;
3432 };
3433 PersistentConnection.prototype.listen = function (query, currentHashFn, tag, onComplete) {
3434 this.initConnection_();
3435 var queryId = query._queryIdentifier;
3436 var pathString = query._path.toString();
3437 this.log_('Listen called for ' + pathString + ' ' + queryId);
3438 if (!this.listens.has(pathString)) {
3439 this.listens.set(pathString, new Map());
3440 }
3441 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'listen() called for non-default but complete query');
3442 util.assert(!this.listens.get(pathString).has(queryId), "listen() called twice for same path/queryId.");
3443 var listenSpec = {
3444 onComplete: onComplete,
3445 hashFn: currentHashFn,
3446 query: query,
3447 tag: tag
3448 };
3449 this.listens.get(pathString).set(queryId, listenSpec);
3450 if (this.connected_) {
3451 this.sendListen_(listenSpec);
3452 }
3453 };
3454 PersistentConnection.prototype.sendGet_ = function (index) {
3455 var _this = this;
3456 var get = this.outstandingGets_[index];
3457 this.sendRequest('g', get.request, function (message) {
3458 delete _this.outstandingGets_[index];
3459 _this.outstandingGetCount_--;
3460 if (_this.outstandingGetCount_ === 0) {
3461 _this.outstandingGets_ = [];
3462 }
3463 if (get.onComplete) {
3464 get.onComplete(message);
3465 }
3466 });
3467 };
3468 PersistentConnection.prototype.sendListen_ = function (listenSpec) {
3469 var _this = this;
3470 var query = listenSpec.query;
3471 var pathString = query._path.toString();
3472 var queryId = query._queryIdentifier;
3473 this.log_('Listen on ' + pathString + ' for ' + queryId);
3474 var req = { /*path*/ p: pathString };
3475 var action = 'q';
3476 // Only bother to send query if it's non-default.
3477 if (listenSpec.tag) {
3478 req['q'] = query._queryObject;
3479 req['t'] = listenSpec.tag;
3480 }
3481 req[ /*hash*/'h'] = listenSpec.hashFn();
3482 this.sendRequest(action, req, function (message) {
3483 var payload = message[ /*data*/'d'];
3484 var status = message[ /*status*/'s'];
3485 // print warnings in any case...
3486 PersistentConnection.warnOnListenWarnings_(payload, query);
3487 var currentListenSpec = _this.listens.get(pathString) &&
3488 _this.listens.get(pathString).get(queryId);
3489 // only trigger actions if the listen hasn't been removed and readded
3490 if (currentListenSpec === listenSpec) {
3491 _this.log_('listen response', message);
3492 if (status !== 'ok') {
3493 _this.removeListen_(pathString, queryId);
3494 }
3495 if (listenSpec.onComplete) {
3496 listenSpec.onComplete(status, payload);
3497 }
3498 }
3499 });
3500 };
3501 PersistentConnection.warnOnListenWarnings_ = function (payload, query) {
3502 if (payload && typeof payload === 'object' && util.contains(payload, 'w')) {
3503 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3504 var warnings = util.safeGet(payload, 'w');
3505 if (Array.isArray(warnings) && ~warnings.indexOf('no_index')) {
3506 var indexSpec = '".indexOn": "' + query._queryParams.getIndex().toString() + '"';
3507 var indexPath = query._path.toString();
3508 warn("Using an unspecified index. Your data will be downloaded and " +
3509 ("filtered on the client. Consider adding " + indexSpec + " at ") +
3510 (indexPath + " to your security rules for better performance."));
3511 }
3512 }
3513 };
3514 PersistentConnection.prototype.refreshAuthToken = function (token) {
3515 this.authToken_ = token;
3516 this.log_('Auth token refreshed');
3517 if (this.authToken_) {
3518 this.tryAuth();
3519 }
3520 else {
3521 //If we're connected we want to let the server know to unauthenticate us. If we're not connected, simply delete
3522 //the credential so we dont become authenticated next time we connect.
3523 if (this.connected_) {
3524 this.sendRequest('unauth', {}, function () { });
3525 }
3526 }
3527 this.reduceReconnectDelayIfAdminCredential_(token);
3528 };
3529 PersistentConnection.prototype.reduceReconnectDelayIfAdminCredential_ = function (credential) {
3530 // NOTE: This isn't intended to be bulletproof (a malicious developer can always just modify the client).
3531 // Additionally, we don't bother resetting the max delay back to the default if auth fails / expires.
3532 var isFirebaseSecret = credential && credential.length === 40;
3533 if (isFirebaseSecret || util.isAdmin(credential)) {
3534 this.log_('Admin auth credential detected. Reducing max reconnect time.');
3535 this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
3536 }
3537 };
3538 PersistentConnection.prototype.refreshAppCheckToken = function (token) {
3539 this.appCheckToken_ = token;
3540 this.log_('App check token refreshed');
3541 if (this.appCheckToken_) {
3542 this.tryAppCheck();
3543 }
3544 else {
3545 //If we're connected we want to let the server know to unauthenticate us.
3546 //If we're not connected, simply delete the credential so we dont become
3547 // authenticated next time we connect.
3548 if (this.connected_) {
3549 this.sendRequest('unappeck', {}, function () { });
3550 }
3551 }
3552 };
3553 /**
3554 * Attempts to authenticate with the given credentials. If the authentication attempt fails, it's triggered like
3555 * a auth revoked (the connection is closed).
3556 */
3557 PersistentConnection.prototype.tryAuth = function () {
3558 var _this = this;
3559 if (this.connected_ && this.authToken_) {
3560 var token_1 = this.authToken_;
3561 var authMethod = util.isValidFormat(token_1) ? 'auth' : 'gauth';
3562 var requestData = { cred: token_1 };
3563 if (this.authOverride_ === null) {
3564 requestData['noauth'] = true;
3565 }
3566 else if (typeof this.authOverride_ === 'object') {
3567 requestData['authvar'] = this.authOverride_;
3568 }
3569 this.sendRequest(authMethod, requestData, function (res) {
3570 var status = res[ /*status*/'s'];
3571 var data = res[ /*data*/'d'] || 'error';
3572 if (_this.authToken_ === token_1) {
3573 if (status === 'ok') {
3574 _this.invalidAuthTokenCount_ = 0;
3575 }
3576 else {
3577 // Triggers reconnect and force refresh for auth token
3578 _this.onAuthRevoked_(status, data);
3579 }
3580 }
3581 });
3582 }
3583 };
3584 /**
3585 * Attempts to authenticate with the given token. If the authentication
3586 * attempt fails, it's triggered like the token was revoked (the connection is
3587 * closed).
3588 */
3589 PersistentConnection.prototype.tryAppCheck = function () {
3590 var _this = this;
3591 if (this.connected_ && this.appCheckToken_) {
3592 this.sendRequest('appcheck', { 'token': this.appCheckToken_ }, function (res) {
3593 var status = res[ /*status*/'s'];
3594 var data = res[ /*data*/'d'] || 'error';
3595 if (status === 'ok') {
3596 _this.invalidAppCheckTokenCount_ = 0;
3597 }
3598 else {
3599 _this.onAppCheckRevoked_(status, data);
3600 }
3601 });
3602 }
3603 };
3604 /**
3605 * @inheritDoc
3606 */
3607 PersistentConnection.prototype.unlisten = function (query, tag) {
3608 var pathString = query._path.toString();
3609 var queryId = query._queryIdentifier;
3610 this.log_('Unlisten called for ' + pathString + ' ' + queryId);
3611 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'unlisten() called for non-default but complete query');
3612 var listen = this.removeListen_(pathString, queryId);
3613 if (listen && this.connected_) {
3614 this.sendUnlisten_(pathString, queryId, query._queryObject, tag);
3615 }
3616 };
3617 PersistentConnection.prototype.sendUnlisten_ = function (pathString, queryId, queryObj, tag) {
3618 this.log_('Unlisten on ' + pathString + ' for ' + queryId);
3619 var req = { /*path*/ p: pathString };
3620 var action = 'n';
3621 // Only bother sending queryId if it's non-default.
3622 if (tag) {
3623 req['q'] = queryObj;
3624 req['t'] = tag;
3625 }
3626 this.sendRequest(action, req);
3627 };
3628 PersistentConnection.prototype.onDisconnectPut = function (pathString, data, onComplete) {
3629 this.initConnection_();
3630 if (this.connected_) {
3631 this.sendOnDisconnect_('o', pathString, data, onComplete);
3632 }
3633 else {
3634 this.onDisconnectRequestQueue_.push({
3635 pathString: pathString,
3636 action: 'o',
3637 data: data,
3638 onComplete: onComplete
3639 });
3640 }
3641 };
3642 PersistentConnection.prototype.onDisconnectMerge = function (pathString, data, onComplete) {
3643 this.initConnection_();
3644 if (this.connected_) {
3645 this.sendOnDisconnect_('om', pathString, data, onComplete);
3646 }
3647 else {
3648 this.onDisconnectRequestQueue_.push({
3649 pathString: pathString,
3650 action: 'om',
3651 data: data,
3652 onComplete: onComplete
3653 });
3654 }
3655 };
3656 PersistentConnection.prototype.onDisconnectCancel = function (pathString, onComplete) {
3657 this.initConnection_();
3658 if (this.connected_) {
3659 this.sendOnDisconnect_('oc', pathString, null, onComplete);
3660 }
3661 else {
3662 this.onDisconnectRequestQueue_.push({
3663 pathString: pathString,
3664 action: 'oc',
3665 data: null,
3666 onComplete: onComplete
3667 });
3668 }
3669 };
3670 PersistentConnection.prototype.sendOnDisconnect_ = function (action, pathString, data, onComplete) {
3671 var request = { /*path*/ p: pathString, /*data*/ d: data };
3672 this.log_('onDisconnect ' + action, request);
3673 this.sendRequest(action, request, function (response) {
3674 if (onComplete) {
3675 setTimeout(function () {
3676 onComplete(response[ /*status*/'s'], response[ /* data */'d']);
3677 }, Math.floor(0));
3678 }
3679 });
3680 };
3681 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
3682 this.putInternal('p', pathString, data, onComplete, hash);
3683 };
3684 PersistentConnection.prototype.merge = function (pathString, data, onComplete, hash) {
3685 this.putInternal('m', pathString, data, onComplete, hash);
3686 };
3687 PersistentConnection.prototype.putInternal = function (action, pathString, data, onComplete, hash) {
3688 this.initConnection_();
3689 var request = {
3690 /*path*/ p: pathString,
3691 /*data*/ d: data
3692 };
3693 if (hash !== undefined) {
3694 request[ /*hash*/'h'] = hash;
3695 }
3696 // TODO: Only keep track of the most recent put for a given path?
3697 this.outstandingPuts_.push({
3698 action: action,
3699 request: request,
3700 onComplete: onComplete
3701 });
3702 this.outstandingPutCount_++;
3703 var index = this.outstandingPuts_.length - 1;
3704 if (this.connected_) {
3705 this.sendPut_(index);
3706 }
3707 else {
3708 this.log_('Buffering put: ' + pathString);
3709 }
3710 };
3711 PersistentConnection.prototype.sendPut_ = function (index) {
3712 var _this = this;
3713 var action = this.outstandingPuts_[index].action;
3714 var request = this.outstandingPuts_[index].request;
3715 var onComplete = this.outstandingPuts_[index].onComplete;
3716 this.outstandingPuts_[index].queued = this.connected_;
3717 this.sendRequest(action, request, function (message) {
3718 _this.log_(action + ' response', message);
3719 delete _this.outstandingPuts_[index];
3720 _this.outstandingPutCount_--;
3721 // Clean up array occasionally.
3722 if (_this.outstandingPutCount_ === 0) {
3723 _this.outstandingPuts_ = [];
3724 }
3725 if (onComplete) {
3726 onComplete(message[ /*status*/'s'], message[ /* data */'d']);
3727 }
3728 });
3729 };
3730 PersistentConnection.prototype.reportStats = function (stats) {
3731 var _this = this;
3732 // If we're not connected, we just drop the stats.
3733 if (this.connected_) {
3734 var request = { /*counters*/ c: stats };
3735 this.log_('reportStats', request);
3736 this.sendRequest(/*stats*/ 's', request, function (result) {
3737 var status = result[ /*status*/'s'];
3738 if (status !== 'ok') {
3739 var errorReason = result[ /* data */'d'];
3740 _this.log_('reportStats', 'Error sending stats: ' + errorReason);
3741 }
3742 });
3743 }
3744 };
3745 PersistentConnection.prototype.onDataMessage_ = function (message) {
3746 if ('r' in message) {
3747 // this is a response
3748 this.log_('from server: ' + util.stringify(message));
3749 var reqNum = message['r'];
3750 var onResponse = this.requestCBHash_[reqNum];
3751 if (onResponse) {
3752 delete this.requestCBHash_[reqNum];
3753 onResponse(message[ /*body*/'b']);
3754 }
3755 }
3756 else if ('error' in message) {
3757 throw 'A server-side error has occurred: ' + message['error'];
3758 }
3759 else if ('a' in message) {
3760 // a and b are action and body, respectively
3761 this.onDataPush_(message['a'], message['b']);
3762 }
3763 };
3764 PersistentConnection.prototype.onDataPush_ = function (action, body) {
3765 this.log_('handleServerMessage', action, body);
3766 if (action === 'd') {
3767 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3768 /*isMerge*/ false, body['t']);
3769 }
3770 else if (action === 'm') {
3771 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3772 /*isMerge=*/ true, body['t']);
3773 }
3774 else if (action === 'c') {
3775 this.onListenRevoked_(body[ /*path*/'p'], body[ /*query*/'q']);
3776 }
3777 else if (action === 'ac') {
3778 this.onAuthRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3779 }
3780 else if (action === 'apc') {
3781 this.onAppCheckRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3782 }
3783 else if (action === 'sd') {
3784 this.onSecurityDebugPacket_(body);
3785 }
3786 else {
3787 error('Unrecognized action received from server: ' +
3788 util.stringify(action) +
3789 '\nAre you using the latest client?');
3790 }
3791 };
3792 PersistentConnection.prototype.onReady_ = function (timestamp, sessionId) {
3793 this.log_('connection ready');
3794 this.connected_ = true;
3795 this.lastConnectionEstablishedTime_ = new Date().getTime();
3796 this.handleTimestamp_(timestamp);
3797 this.lastSessionId = sessionId;
3798 if (this.firstConnection_) {
3799 this.sendConnectStats_();
3800 }
3801 this.restoreState_();
3802 this.firstConnection_ = false;
3803 this.onConnectStatus_(true);
3804 };
3805 PersistentConnection.prototype.scheduleConnect_ = function (timeout) {
3806 var _this = this;
3807 util.assert(!this.realtime_, "Scheduling a connect when we're already connected/ing?");
3808 if (this.establishConnectionTimer_) {
3809 clearTimeout(this.establishConnectionTimer_);
3810 }
3811 // NOTE: Even when timeout is 0, it's important to do a setTimeout to work around an infuriating "Security Error" in
3812 // Firefox when trying to write to our long-polling iframe in some scenarios (e.g. Forge or our unit tests).
3813 this.establishConnectionTimer_ = setTimeout(function () {
3814 _this.establishConnectionTimer_ = null;
3815 _this.establishConnection_();
3816 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3817 }, Math.floor(timeout));
3818 };
3819 PersistentConnection.prototype.initConnection_ = function () {
3820 if (!this.realtime_ && this.firstConnection_) {
3821 this.scheduleConnect_(0);
3822 }
3823 };
3824 PersistentConnection.prototype.onVisible_ = function (visible) {
3825 // NOTE: Tabbing away and back to a window will defeat our reconnect backoff, but I think that's fine.
3826 if (visible &&
3827 !this.visible_ &&
3828 this.reconnectDelay_ === this.maxReconnectDelay_) {
3829 this.log_('Window became visible. Reducing delay.');
3830 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3831 if (!this.realtime_) {
3832 this.scheduleConnect_(0);
3833 }
3834 }
3835 this.visible_ = visible;
3836 };
3837 PersistentConnection.prototype.onOnline_ = function (online) {
3838 if (online) {
3839 this.log_('Browser went online.');
3840 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3841 if (!this.realtime_) {
3842 this.scheduleConnect_(0);
3843 }
3844 }
3845 else {
3846 this.log_('Browser went offline. Killing connection.');
3847 if (this.realtime_) {
3848 this.realtime_.close();
3849 }
3850 }
3851 };
3852 PersistentConnection.prototype.onRealtimeDisconnect_ = function () {
3853 this.log_('data client disconnected');
3854 this.connected_ = false;
3855 this.realtime_ = null;
3856 // Since we don't know if our sent transactions succeeded or not, we need to cancel them.
3857 this.cancelSentTransactions_();
3858 // Clear out the pending requests.
3859 this.requestCBHash_ = {};
3860 if (this.shouldReconnect_()) {
3861 if (!this.visible_) {
3862 this.log_("Window isn't visible. Delaying reconnect.");
3863 this.reconnectDelay_ = this.maxReconnectDelay_;
3864 this.lastConnectionAttemptTime_ = new Date().getTime();
3865 }
3866 else if (this.lastConnectionEstablishedTime_) {
3867 // If we've been connected long enough, reset reconnect delay to minimum.
3868 var timeSinceLastConnectSucceeded = new Date().getTime() - this.lastConnectionEstablishedTime_;
3869 if (timeSinceLastConnectSucceeded > RECONNECT_DELAY_RESET_TIMEOUT) {
3870 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3871 }
3872 this.lastConnectionEstablishedTime_ = null;
3873 }
3874 var timeSinceLastConnectAttempt = new Date().getTime() - this.lastConnectionAttemptTime_;
3875 var reconnectDelay = Math.max(0, this.reconnectDelay_ - timeSinceLastConnectAttempt);
3876 reconnectDelay = Math.random() * reconnectDelay;
3877 this.log_('Trying to reconnect in ' + reconnectDelay + 'ms');
3878 this.scheduleConnect_(reconnectDelay);
3879 // Adjust reconnect delay for next time.
3880 this.reconnectDelay_ = Math.min(this.maxReconnectDelay_, this.reconnectDelay_ * RECONNECT_DELAY_MULTIPLIER);
3881 }
3882 this.onConnectStatus_(false);
3883 };
3884 PersistentConnection.prototype.establishConnection_ = function () {
3885 return tslib.__awaiter(this, void 0, void 0, function () {
3886 var onDataMessage, onReady, onDisconnect_1, connId, lastSessionId, canceled_1, connection_1, closeFn, sendRequestFn, forceRefresh, _a, authToken, appCheckToken, error_1;
3887 var _this = this;
3888 return tslib.__generator(this, function (_b) {
3889 switch (_b.label) {
3890 case 0:
3891 if (!this.shouldReconnect_()) return [3 /*break*/, 4];
3892 this.log_('Making a connection attempt');
3893 this.lastConnectionAttemptTime_ = new Date().getTime();
3894 this.lastConnectionEstablishedTime_ = null;
3895 onDataMessage = this.onDataMessage_.bind(this);
3896 onReady = this.onReady_.bind(this);
3897 onDisconnect_1 = this.onRealtimeDisconnect_.bind(this);
3898 connId = this.id + ':' + PersistentConnection.nextConnectionId_++;
3899 lastSessionId = this.lastSessionId;
3900 canceled_1 = false;
3901 connection_1 = null;
3902 closeFn = function () {
3903 if (connection_1) {
3904 connection_1.close();
3905 }
3906 else {
3907 canceled_1 = true;
3908 onDisconnect_1();
3909 }
3910 };
3911 sendRequestFn = function (msg) {
3912 util.assert(connection_1, "sendRequest call when we're not connected not allowed.");
3913 connection_1.sendRequest(msg);
3914 };
3915 this.realtime_ = {
3916 close: closeFn,
3917 sendRequest: sendRequestFn
3918 };
3919 forceRefresh = this.forceTokenRefresh_;
3920 this.forceTokenRefresh_ = false;
3921 _b.label = 1;
3922 case 1:
3923 _b.trys.push([1, 3, , 4]);
3924 return [4 /*yield*/, Promise.all([
3925 this.authTokenProvider_.getToken(forceRefresh),
3926 this.appCheckTokenProvider_.getToken(forceRefresh)
3927 ])];
3928 case 2:
3929 _a = tslib.__read.apply(void 0, [_b.sent(), 2]), authToken = _a[0], appCheckToken = _a[1];
3930 if (!canceled_1) {
3931 log('getToken() completed. Creating connection.');
3932 this.authToken_ = authToken && authToken.accessToken;
3933 this.appCheckToken_ = appCheckToken && appCheckToken.token;
3934 connection_1 = new Connection(connId, this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, onDataMessage, onReady, onDisconnect_1,
3935 /* onKill= */ function (reason) {
3936 warn(reason + ' (' + _this.repoInfo_.toString() + ')');
3937 _this.interrupt(SERVER_KILL_INTERRUPT_REASON);
3938 }, lastSessionId);
3939 }
3940 else {
3941 log('getToken() completed but was canceled');
3942 }
3943 return [3 /*break*/, 4];
3944 case 3:
3945 error_1 = _b.sent();
3946 this.log_('Failed to get token: ' + error_1);
3947 if (!canceled_1) {
3948 if (this.repoInfo_.nodeAdmin) {
3949 // This may be a critical error for the Admin Node.js SDK, so log a warning.
3950 // But getToken() may also just have temporarily failed, so we still want to
3951 // continue retrying.
3952 warn(error_1);
3953 }
3954 closeFn();
3955 }
3956 return [3 /*break*/, 4];
3957 case 4: return [2 /*return*/];
3958 }
3959 });
3960 });
3961 };
3962 PersistentConnection.prototype.interrupt = function (reason) {
3963 log('Interrupting connection for reason: ' + reason);
3964 this.interruptReasons_[reason] = true;
3965 if (this.realtime_) {
3966 this.realtime_.close();
3967 }
3968 else {
3969 if (this.establishConnectionTimer_) {
3970 clearTimeout(this.establishConnectionTimer_);
3971 this.establishConnectionTimer_ = null;
3972 }
3973 if (this.connected_) {
3974 this.onRealtimeDisconnect_();
3975 }
3976 }
3977 };
3978 PersistentConnection.prototype.resume = function (reason) {
3979 log('Resuming connection for reason: ' + reason);
3980 delete this.interruptReasons_[reason];
3981 if (util.isEmpty(this.interruptReasons_)) {
3982 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3983 if (!this.realtime_) {
3984 this.scheduleConnect_(0);
3985 }
3986 }
3987 };
3988 PersistentConnection.prototype.handleTimestamp_ = function (timestamp) {
3989 var delta = timestamp - new Date().getTime();
3990 this.onServerInfoUpdate_({ serverTimeOffset: delta });
3991 };
3992 PersistentConnection.prototype.cancelSentTransactions_ = function () {
3993 for (var i = 0; i < this.outstandingPuts_.length; i++) {
3994 var put = this.outstandingPuts_[i];
3995 if (put && /*hash*/ 'h' in put.request && put.queued) {
3996 if (put.onComplete) {
3997 put.onComplete('disconnect');
3998 }
3999 delete this.outstandingPuts_[i];
4000 this.outstandingPutCount_--;
4001 }
4002 }
4003 // Clean up array occasionally.
4004 if (this.outstandingPutCount_ === 0) {
4005 this.outstandingPuts_ = [];
4006 }
4007 };
4008 PersistentConnection.prototype.onListenRevoked_ = function (pathString, query) {
4009 // Remove the listen and manufacture a "permission_denied" error for the failed listen.
4010 var queryId;
4011 if (!query) {
4012 queryId = 'default';
4013 }
4014 else {
4015 queryId = query.map(function (q) { return ObjectToUniqueKey(q); }).join('$');
4016 }
4017 var listen = this.removeListen_(pathString, queryId);
4018 if (listen && listen.onComplete) {
4019 listen.onComplete('permission_denied');
4020 }
4021 };
4022 PersistentConnection.prototype.removeListen_ = function (pathString, queryId) {
4023 var normalizedPathString = new Path(pathString).toString(); // normalize path.
4024 var listen;
4025 if (this.listens.has(normalizedPathString)) {
4026 var map = this.listens.get(normalizedPathString);
4027 listen = map.get(queryId);
4028 map.delete(queryId);
4029 if (map.size === 0) {
4030 this.listens.delete(normalizedPathString);
4031 }
4032 }
4033 else {
4034 // all listens for this path has already been removed
4035 listen = undefined;
4036 }
4037 return listen;
4038 };
4039 PersistentConnection.prototype.onAuthRevoked_ = function (statusCode, explanation) {
4040 log('Auth token revoked: ' + statusCode + '/' + explanation);
4041 this.authToken_ = null;
4042 this.forceTokenRefresh_ = true;
4043 this.realtime_.close();
4044 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4045 // We'll wait a couple times before logging the warning / increasing the
4046 // retry period since oauth tokens will report as "invalid" if they're
4047 // just expired. Plus there may be transient issues that resolve themselves.
4048 this.invalidAuthTokenCount_++;
4049 if (this.invalidAuthTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4050 // Set a long reconnect delay because recovery is unlikely
4051 this.reconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
4052 // Notify the auth token provider that the token is invalid, which will log
4053 // a warning
4054 this.authTokenProvider_.notifyForInvalidToken();
4055 }
4056 }
4057 };
4058 PersistentConnection.prototype.onAppCheckRevoked_ = function (statusCode, explanation) {
4059 log('App check token revoked: ' + statusCode + '/' + explanation);
4060 this.appCheckToken_ = null;
4061 this.forceTokenRefresh_ = true;
4062 // Note: We don't close the connection as the developer may not have
4063 // enforcement enabled. The backend closes connections with enforcements.
4064 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4065 // We'll wait a couple times before logging the warning / increasing the
4066 // retry period since oauth tokens will report as "invalid" if they're
4067 // just expired. Plus there may be transient issues that resolve themselves.
4068 this.invalidAppCheckTokenCount_++;
4069 if (this.invalidAppCheckTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4070 this.appCheckTokenProvider_.notifyForInvalidToken();
4071 }
4072 }
4073 };
4074 PersistentConnection.prototype.onSecurityDebugPacket_ = function (body) {
4075 if (this.securityDebugCallback_) {
4076 this.securityDebugCallback_(body);
4077 }
4078 else {
4079 if ('msg' in body) {
4080 console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
4081 }
4082 }
4083 };
4084 PersistentConnection.prototype.restoreState_ = function () {
4085 var e_1, _a, e_2, _b;
4086 //Re-authenticate ourselves if we have a credential stored.
4087 this.tryAuth();
4088 this.tryAppCheck();
4089 try {
4090 // Puts depend on having received the corresponding data update from the server before they complete, so we must
4091 // make sure to send listens before puts.
4092 for (var _c = tslib.__values(this.listens.values()), _d = _c.next(); !_d.done; _d = _c.next()) {
4093 var queries = _d.value;
4094 try {
4095 for (var _e = (e_2 = void 0, tslib.__values(queries.values())), _f = _e.next(); !_f.done; _f = _e.next()) {
4096 var listenSpec = _f.value;
4097 this.sendListen_(listenSpec);
4098 }
4099 }
4100 catch (e_2_1) { e_2 = { error: e_2_1 }; }
4101 finally {
4102 try {
4103 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
4104 }
4105 finally { if (e_2) throw e_2.error; }
4106 }
4107 }
4108 }
4109 catch (e_1_1) { e_1 = { error: e_1_1 }; }
4110 finally {
4111 try {
4112 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
4113 }
4114 finally { if (e_1) throw e_1.error; }
4115 }
4116 for (var i = 0; i < this.outstandingPuts_.length; i++) {
4117 if (this.outstandingPuts_[i]) {
4118 this.sendPut_(i);
4119 }
4120 }
4121 while (this.onDisconnectRequestQueue_.length) {
4122 var request = this.onDisconnectRequestQueue_.shift();
4123 this.sendOnDisconnect_(request.action, request.pathString, request.data, request.onComplete);
4124 }
4125 for (var i = 0; i < this.outstandingGets_.length; i++) {
4126 if (this.outstandingGets_[i]) {
4127 this.sendGet_(i);
4128 }
4129 }
4130 };
4131 /**
4132 * Sends client stats for first connection
4133 */
4134 PersistentConnection.prototype.sendConnectStats_ = function () {
4135 var stats = {};
4136 var clientName = 'js';
4137 if (util.isNodeSdk()) {
4138 if (this.repoInfo_.nodeAdmin) {
4139 clientName = 'admin_node';
4140 }
4141 else {
4142 clientName = 'node';
4143 }
4144 }
4145 stats['sdk.' + clientName + '.' + SDK_VERSION.replace(/\./g, '-')] = 1;
4146 if (util.isMobileCordova()) {
4147 stats['framework.cordova'] = 1;
4148 }
4149 else if (util.isReactNative()) {
4150 stats['framework.reactnative'] = 1;
4151 }
4152 this.reportStats(stats);
4153 };
4154 PersistentConnection.prototype.shouldReconnect_ = function () {
4155 var online = OnlineMonitor.getInstance().currentlyOnline();
4156 return util.isEmpty(this.interruptReasons_) && online;
4157 };
4158 PersistentConnection.nextPersistentConnectionId_ = 0;
4159 /**
4160 * Counter for number of connections created. Mainly used for tagging in the logs
4161 */
4162 PersistentConnection.nextConnectionId_ = 0;
4163 return PersistentConnection;
4164}(ServerActions));
4165
4166/**
4167 * @license
4168 * Copyright 2017 Google LLC
4169 *
4170 * Licensed under the Apache License, Version 2.0 (the "License");
4171 * you may not use this file except in compliance with the License.
4172 * You may obtain a copy of the License at
4173 *
4174 * http://www.apache.org/licenses/LICENSE-2.0
4175 *
4176 * Unless required by applicable law or agreed to in writing, software
4177 * distributed under the License is distributed on an "AS IS" BASIS,
4178 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4179 * See the License for the specific language governing permissions and
4180 * limitations under the License.
4181 */
4182var NamedNode = /** @class */ (function () {
4183 function NamedNode(name, node) {
4184 this.name = name;
4185 this.node = node;
4186 }
4187 NamedNode.Wrap = function (name, node) {
4188 return new NamedNode(name, node);
4189 };
4190 return NamedNode;
4191}());
4192
4193/**
4194 * @license
4195 * Copyright 2017 Google LLC
4196 *
4197 * Licensed under the Apache License, Version 2.0 (the "License");
4198 * you may not use this file except in compliance with the License.
4199 * You may obtain a copy of the License at
4200 *
4201 * http://www.apache.org/licenses/LICENSE-2.0
4202 *
4203 * Unless required by applicable law or agreed to in writing, software
4204 * distributed under the License is distributed on an "AS IS" BASIS,
4205 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4206 * See the License for the specific language governing permissions and
4207 * limitations under the License.
4208 */
4209var Index = /** @class */ (function () {
4210 function Index() {
4211 }
4212 /**
4213 * @returns A standalone comparison function for
4214 * this index
4215 */
4216 Index.prototype.getCompare = function () {
4217 return this.compare.bind(this);
4218 };
4219 /**
4220 * Given a before and after value for a node, determine if the indexed value has changed. Even if they are different,
4221 * it's possible that the changes are isolated to parts of the snapshot that are not indexed.
4222 *
4223 *
4224 * @returns True if the portion of the snapshot being indexed changed between oldNode and newNode
4225 */
4226 Index.prototype.indexedValueChanged = function (oldNode, newNode) {
4227 var oldWrapped = new NamedNode(MIN_NAME, oldNode);
4228 var newWrapped = new NamedNode(MIN_NAME, newNode);
4229 return this.compare(oldWrapped, newWrapped) !== 0;
4230 };
4231 /**
4232 * @returns a node wrapper that will sort equal to or less than
4233 * any other node wrapper, using this index
4234 */
4235 Index.prototype.minPost = function () {
4236 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4237 return NamedNode.MIN;
4238 };
4239 return Index;
4240}());
4241
4242/**
4243 * @license
4244 * Copyright 2017 Google LLC
4245 *
4246 * Licensed under the Apache License, Version 2.0 (the "License");
4247 * you may not use this file except in compliance with the License.
4248 * You may obtain a copy of the License at
4249 *
4250 * http://www.apache.org/licenses/LICENSE-2.0
4251 *
4252 * Unless required by applicable law or agreed to in writing, software
4253 * distributed under the License is distributed on an "AS IS" BASIS,
4254 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4255 * See the License for the specific language governing permissions and
4256 * limitations under the License.
4257 */
4258var __EMPTY_NODE;
4259var KeyIndex = /** @class */ (function (_super) {
4260 tslib.__extends(KeyIndex, _super);
4261 function KeyIndex() {
4262 return _super !== null && _super.apply(this, arguments) || this;
4263 }
4264 Object.defineProperty(KeyIndex, "__EMPTY_NODE", {
4265 get: function () {
4266 return __EMPTY_NODE;
4267 },
4268 set: function (val) {
4269 __EMPTY_NODE = val;
4270 },
4271 enumerable: false,
4272 configurable: true
4273 });
4274 KeyIndex.prototype.compare = function (a, b) {
4275 return nameCompare(a.name, b.name);
4276 };
4277 KeyIndex.prototype.isDefinedOn = function (node) {
4278 // We could probably return true here (since every node has a key), but it's never called
4279 // so just leaving unimplemented for now.
4280 throw util.assertionError('KeyIndex.isDefinedOn not expected to be called.');
4281 };
4282 KeyIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
4283 return false; // The key for a node never changes.
4284 };
4285 KeyIndex.prototype.minPost = function () {
4286 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4287 return NamedNode.MIN;
4288 };
4289 KeyIndex.prototype.maxPost = function () {
4290 // TODO: This should really be created once and cached in a static property, but
4291 // NamedNode isn't defined yet, so I can't use it in a static. Bleh.
4292 return new NamedNode(MAX_NAME, __EMPTY_NODE);
4293 };
4294 KeyIndex.prototype.makePost = function (indexValue, name) {
4295 util.assert(typeof indexValue === 'string', 'KeyIndex indexValue must always be a string.');
4296 // We just use empty node, but it'll never be compared, since our comparator only looks at name.
4297 return new NamedNode(indexValue, __EMPTY_NODE);
4298 };
4299 /**
4300 * @returns String representation for inclusion in a query spec
4301 */
4302 KeyIndex.prototype.toString = function () {
4303 return '.key';
4304 };
4305 return KeyIndex;
4306}(Index));
4307var KEY_INDEX = new KeyIndex();
4308
4309/**
4310 * @license
4311 * Copyright 2017 Google LLC
4312 *
4313 * Licensed under the Apache License, Version 2.0 (the "License");
4314 * you may not use this file except in compliance with the License.
4315 * You may obtain a copy of the License at
4316 *
4317 * http://www.apache.org/licenses/LICENSE-2.0
4318 *
4319 * Unless required by applicable law or agreed to in writing, software
4320 * distributed under the License is distributed on an "AS IS" BASIS,
4321 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4322 * See the License for the specific language governing permissions and
4323 * limitations under the License.
4324 */
4325/**
4326 * An iterator over an LLRBNode.
4327 */
4328var SortedMapIterator = /** @class */ (function () {
4329 /**
4330 * @param node - Node to iterate.
4331 * @param isReverse_ - Whether or not to iterate in reverse
4332 */
4333 function SortedMapIterator(node, startKey, comparator, isReverse_, resultGenerator_) {
4334 if (resultGenerator_ === void 0) { resultGenerator_ = null; }
4335 this.isReverse_ = isReverse_;
4336 this.resultGenerator_ = resultGenerator_;
4337 this.nodeStack_ = [];
4338 var cmp = 1;
4339 while (!node.isEmpty()) {
4340 node = node;
4341 cmp = startKey ? comparator(node.key, startKey) : 1;
4342 // flip the comparison if we're going in reverse
4343 if (isReverse_) {
4344 cmp *= -1;
4345 }
4346 if (cmp < 0) {
4347 // This node is less than our start key. ignore it
4348 if (this.isReverse_) {
4349 node = node.left;
4350 }
4351 else {
4352 node = node.right;
4353 }
4354 }
4355 else if (cmp === 0) {
4356 // This node is exactly equal to our start key. Push it on the stack, but stop iterating;
4357 this.nodeStack_.push(node);
4358 break;
4359 }
4360 else {
4361 // This node is greater than our start key, add it to the stack and move to the next one
4362 this.nodeStack_.push(node);
4363 if (this.isReverse_) {
4364 node = node.right;
4365 }
4366 else {
4367 node = node.left;
4368 }
4369 }
4370 }
4371 }
4372 SortedMapIterator.prototype.getNext = function () {
4373 if (this.nodeStack_.length === 0) {
4374 return null;
4375 }
4376 var node = this.nodeStack_.pop();
4377 var result;
4378 if (this.resultGenerator_) {
4379 result = this.resultGenerator_(node.key, node.value);
4380 }
4381 else {
4382 result = { key: node.key, value: node.value };
4383 }
4384 if (this.isReverse_) {
4385 node = node.left;
4386 while (!node.isEmpty()) {
4387 this.nodeStack_.push(node);
4388 node = node.right;
4389 }
4390 }
4391 else {
4392 node = node.right;
4393 while (!node.isEmpty()) {
4394 this.nodeStack_.push(node);
4395 node = node.left;
4396 }
4397 }
4398 return result;
4399 };
4400 SortedMapIterator.prototype.hasNext = function () {
4401 return this.nodeStack_.length > 0;
4402 };
4403 SortedMapIterator.prototype.peek = function () {
4404 if (this.nodeStack_.length === 0) {
4405 return null;
4406 }
4407 var node = this.nodeStack_[this.nodeStack_.length - 1];
4408 if (this.resultGenerator_) {
4409 return this.resultGenerator_(node.key, node.value);
4410 }
4411 else {
4412 return { key: node.key, value: node.value };
4413 }
4414 };
4415 return SortedMapIterator;
4416}());
4417/**
4418 * Represents a node in a Left-leaning Red-Black tree.
4419 */
4420var LLRBNode = /** @class */ (function () {
4421 /**
4422 * @param key - Key associated with this node.
4423 * @param value - Value associated with this node.
4424 * @param color - Whether this node is red.
4425 * @param left - Left child.
4426 * @param right - Right child.
4427 */
4428 function LLRBNode(key, value, color, left, right) {
4429 this.key = key;
4430 this.value = value;
4431 this.color = color != null ? color : LLRBNode.RED;
4432 this.left =
4433 left != null ? left : SortedMap.EMPTY_NODE;
4434 this.right =
4435 right != null ? right : SortedMap.EMPTY_NODE;
4436 }
4437 /**
4438 * Returns a copy of the current node, optionally replacing pieces of it.
4439 *
4440 * @param key - New key for the node, or null.
4441 * @param value - New value for the node, or null.
4442 * @param color - New color for the node, or null.
4443 * @param left - New left child for the node, or null.
4444 * @param right - New right child for the node, or null.
4445 * @returns The node copy.
4446 */
4447 LLRBNode.prototype.copy = function (key, value, color, left, right) {
4448 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);
4449 };
4450 /**
4451 * @returns The total number of nodes in the tree.
4452 */
4453 LLRBNode.prototype.count = function () {
4454 return this.left.count() + 1 + this.right.count();
4455 };
4456 /**
4457 * @returns True if the tree is empty.
4458 */
4459 LLRBNode.prototype.isEmpty = function () {
4460 return false;
4461 };
4462 /**
4463 * Traverses the tree in key order and calls the specified action function
4464 * for each node.
4465 *
4466 * @param action - Callback function to be called for each
4467 * node. If it returns true, traversal is aborted.
4468 * @returns The first truthy value returned by action, or the last falsey
4469 * value returned by action
4470 */
4471 LLRBNode.prototype.inorderTraversal = function (action) {
4472 return (this.left.inorderTraversal(action) ||
4473 !!action(this.key, this.value) ||
4474 this.right.inorderTraversal(action));
4475 };
4476 /**
4477 * Traverses the tree in reverse key order and calls the specified action function
4478 * for each node.
4479 *
4480 * @param action - Callback function to be called for each
4481 * node. If it returns true, traversal is aborted.
4482 * @returns True if traversal was aborted.
4483 */
4484 LLRBNode.prototype.reverseTraversal = function (action) {
4485 return (this.right.reverseTraversal(action) ||
4486 action(this.key, this.value) ||
4487 this.left.reverseTraversal(action));
4488 };
4489 /**
4490 * @returns The minimum node in the tree.
4491 */
4492 LLRBNode.prototype.min_ = function () {
4493 if (this.left.isEmpty()) {
4494 return this;
4495 }
4496 else {
4497 return this.left.min_();
4498 }
4499 };
4500 /**
4501 * @returns The maximum key in the tree.
4502 */
4503 LLRBNode.prototype.minKey = function () {
4504 return this.min_().key;
4505 };
4506 /**
4507 * @returns The maximum key in the tree.
4508 */
4509 LLRBNode.prototype.maxKey = function () {
4510 if (this.right.isEmpty()) {
4511 return this.key;
4512 }
4513 else {
4514 return this.right.maxKey();
4515 }
4516 };
4517 /**
4518 * @param key - Key to insert.
4519 * @param value - Value to insert.
4520 * @param comparator - Comparator.
4521 * @returns New tree, with the key/value added.
4522 */
4523 LLRBNode.prototype.insert = function (key, value, comparator) {
4524 var n = this;
4525 var cmp = comparator(key, n.key);
4526 if (cmp < 0) {
4527 n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
4528 }
4529 else if (cmp === 0) {
4530 n = n.copy(null, value, null, null, null);
4531 }
4532 else {
4533 n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
4534 }
4535 return n.fixUp_();
4536 };
4537 /**
4538 * @returns New tree, with the minimum key removed.
4539 */
4540 LLRBNode.prototype.removeMin_ = function () {
4541 if (this.left.isEmpty()) {
4542 return SortedMap.EMPTY_NODE;
4543 }
4544 var n = this;
4545 if (!n.left.isRed_() && !n.left.left.isRed_()) {
4546 n = n.moveRedLeft_();
4547 }
4548 n = n.copy(null, null, null, n.left.removeMin_(), null);
4549 return n.fixUp_();
4550 };
4551 /**
4552 * @param key - The key of the item to remove.
4553 * @param comparator - Comparator.
4554 * @returns New tree, with the specified item removed.
4555 */
4556 LLRBNode.prototype.remove = function (key, comparator) {
4557 var n, smallest;
4558 n = this;
4559 if (comparator(key, n.key) < 0) {
4560 if (!n.left.isEmpty() && !n.left.isRed_() && !n.left.left.isRed_()) {
4561 n = n.moveRedLeft_();
4562 }
4563 n = n.copy(null, null, null, n.left.remove(key, comparator), null);
4564 }
4565 else {
4566 if (n.left.isRed_()) {
4567 n = n.rotateRight_();
4568 }
4569 if (!n.right.isEmpty() && !n.right.isRed_() && !n.right.left.isRed_()) {
4570 n = n.moveRedRight_();
4571 }
4572 if (comparator(key, n.key) === 0) {
4573 if (n.right.isEmpty()) {
4574 return SortedMap.EMPTY_NODE;
4575 }
4576 else {
4577 smallest = n.right.min_();
4578 n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin_());
4579 }
4580 }
4581 n = n.copy(null, null, null, null, n.right.remove(key, comparator));
4582 }
4583 return n.fixUp_();
4584 };
4585 /**
4586 * @returns Whether this is a RED node.
4587 */
4588 LLRBNode.prototype.isRed_ = function () {
4589 return this.color;
4590 };
4591 /**
4592 * @returns New tree after performing any needed rotations.
4593 */
4594 LLRBNode.prototype.fixUp_ = function () {
4595 var n = this;
4596 if (n.right.isRed_() && !n.left.isRed_()) {
4597 n = n.rotateLeft_();
4598 }
4599 if (n.left.isRed_() && n.left.left.isRed_()) {
4600 n = n.rotateRight_();
4601 }
4602 if (n.left.isRed_() && n.right.isRed_()) {
4603 n = n.colorFlip_();
4604 }
4605 return n;
4606 };
4607 /**
4608 * @returns New tree, after moveRedLeft.
4609 */
4610 LLRBNode.prototype.moveRedLeft_ = function () {
4611 var n = this.colorFlip_();
4612 if (n.right.left.isRed_()) {
4613 n = n.copy(null, null, null, null, n.right.rotateRight_());
4614 n = n.rotateLeft_();
4615 n = n.colorFlip_();
4616 }
4617 return n;
4618 };
4619 /**
4620 * @returns New tree, after moveRedRight.
4621 */
4622 LLRBNode.prototype.moveRedRight_ = function () {
4623 var n = this.colorFlip_();
4624 if (n.left.left.isRed_()) {
4625 n = n.rotateRight_();
4626 n = n.colorFlip_();
4627 }
4628 return n;
4629 };
4630 /**
4631 * @returns New tree, after rotateLeft.
4632 */
4633 LLRBNode.prototype.rotateLeft_ = function () {
4634 var nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
4635 return this.right.copy(null, null, this.color, nl, null);
4636 };
4637 /**
4638 * @returns New tree, after rotateRight.
4639 */
4640 LLRBNode.prototype.rotateRight_ = function () {
4641 var nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
4642 return this.left.copy(null, null, this.color, null, nr);
4643 };
4644 /**
4645 * @returns Newt ree, after colorFlip.
4646 */
4647 LLRBNode.prototype.colorFlip_ = function () {
4648 var left = this.left.copy(null, null, !this.left.color, null, null);
4649 var right = this.right.copy(null, null, !this.right.color, null, null);
4650 return this.copy(null, null, !this.color, left, right);
4651 };
4652 /**
4653 * For testing.
4654 *
4655 * @returns True if all is well.
4656 */
4657 LLRBNode.prototype.checkMaxDepth_ = function () {
4658 var blackDepth = this.check_();
4659 return Math.pow(2.0, blackDepth) <= this.count() + 1;
4660 };
4661 LLRBNode.prototype.check_ = function () {
4662 if (this.isRed_() && this.left.isRed_()) {
4663 throw new Error('Red node has red child(' + this.key + ',' + this.value + ')');
4664 }
4665 if (this.right.isRed_()) {
4666 throw new Error('Right child of (' + this.key + ',' + this.value + ') is red');
4667 }
4668 var blackDepth = this.left.check_();
4669 if (blackDepth !== this.right.check_()) {
4670 throw new Error('Black depths differ');
4671 }
4672 else {
4673 return blackDepth + (this.isRed_() ? 0 : 1);
4674 }
4675 };
4676 LLRBNode.RED = true;
4677 LLRBNode.BLACK = false;
4678 return LLRBNode;
4679}());
4680/**
4681 * Represents an empty node (a leaf node in the Red-Black Tree).
4682 */
4683var LLRBEmptyNode = /** @class */ (function () {
4684 function LLRBEmptyNode() {
4685 }
4686 /**
4687 * Returns a copy of the current node.
4688 *
4689 * @returns The node copy.
4690 */
4691 LLRBEmptyNode.prototype.copy = function (key, value, color, left, right) {
4692 return this;
4693 };
4694 /**
4695 * Returns a copy of the tree, with the specified key/value added.
4696 *
4697 * @param key - Key to be added.
4698 * @param value - Value to be added.
4699 * @param comparator - Comparator.
4700 * @returns New tree, with item added.
4701 */
4702 LLRBEmptyNode.prototype.insert = function (key, value, comparator) {
4703 return new LLRBNode(key, value, null);
4704 };
4705 /**
4706 * Returns a copy of the tree, with the specified key removed.
4707 *
4708 * @param key - The key to remove.
4709 * @param comparator - Comparator.
4710 * @returns New tree, with item removed.
4711 */
4712 LLRBEmptyNode.prototype.remove = function (key, comparator) {
4713 return this;
4714 };
4715 /**
4716 * @returns The total number of nodes in the tree.
4717 */
4718 LLRBEmptyNode.prototype.count = function () {
4719 return 0;
4720 };
4721 /**
4722 * @returns True if the tree is empty.
4723 */
4724 LLRBEmptyNode.prototype.isEmpty = function () {
4725 return true;
4726 };
4727 /**
4728 * Traverses the tree in key order and calls the specified action function
4729 * for each node.
4730 *
4731 * @param action - Callback function to be called for each
4732 * node. If it returns true, traversal is aborted.
4733 * @returns True if traversal was aborted.
4734 */
4735 LLRBEmptyNode.prototype.inorderTraversal = function (action) {
4736 return false;
4737 };
4738 /**
4739 * Traverses the tree in reverse key order and calls the specified action function
4740 * for each node.
4741 *
4742 * @param action - Callback function to be called for each
4743 * node. If it returns true, traversal is aborted.
4744 * @returns True if traversal was aborted.
4745 */
4746 LLRBEmptyNode.prototype.reverseTraversal = function (action) {
4747 return false;
4748 };
4749 LLRBEmptyNode.prototype.minKey = function () {
4750 return null;
4751 };
4752 LLRBEmptyNode.prototype.maxKey = function () {
4753 return null;
4754 };
4755 LLRBEmptyNode.prototype.check_ = function () {
4756 return 0;
4757 };
4758 /**
4759 * @returns Whether this node is red.
4760 */
4761 LLRBEmptyNode.prototype.isRed_ = function () {
4762 return false;
4763 };
4764 return LLRBEmptyNode;
4765}());
4766/**
4767 * An immutable sorted map implementation, based on a Left-leaning Red-Black
4768 * tree.
4769 */
4770var SortedMap = /** @class */ (function () {
4771 /**
4772 * @param comparator_ - Key comparator.
4773 * @param root_ - Optional root node for the map.
4774 */
4775 function SortedMap(comparator_, root_) {
4776 if (root_ === void 0) { root_ = SortedMap.EMPTY_NODE; }
4777 this.comparator_ = comparator_;
4778 this.root_ = root_;
4779 }
4780 /**
4781 * Returns a copy of the map, with the specified key/value added or replaced.
4782 * (TODO: We should perhaps rename this method to 'put')
4783 *
4784 * @param key - Key to be added.
4785 * @param value - Value to be added.
4786 * @returns New map, with item added.
4787 */
4788 SortedMap.prototype.insert = function (key, value) {
4789 return new SortedMap(this.comparator_, this.root_
4790 .insert(key, value, this.comparator_)
4791 .copy(null, null, LLRBNode.BLACK, null, null));
4792 };
4793 /**
4794 * Returns a copy of the map, with the specified key removed.
4795 *
4796 * @param key - The key to remove.
4797 * @returns New map, with item removed.
4798 */
4799 SortedMap.prototype.remove = function (key) {
4800 return new SortedMap(this.comparator_, this.root_
4801 .remove(key, this.comparator_)
4802 .copy(null, null, LLRBNode.BLACK, null, null));
4803 };
4804 /**
4805 * Returns the value of the node with the given key, or null.
4806 *
4807 * @param key - The key to look up.
4808 * @returns The value of the node with the given key, or null if the
4809 * key doesn't exist.
4810 */
4811 SortedMap.prototype.get = function (key) {
4812 var cmp;
4813 var node = this.root_;
4814 while (!node.isEmpty()) {
4815 cmp = this.comparator_(key, node.key);
4816 if (cmp === 0) {
4817 return node.value;
4818 }
4819 else if (cmp < 0) {
4820 node = node.left;
4821 }
4822 else if (cmp > 0) {
4823 node = node.right;
4824 }
4825 }
4826 return null;
4827 };
4828 /**
4829 * Returns the key of the item *before* the specified key, or null if key is the first item.
4830 * @param key - The key to find the predecessor of
4831 * @returns The predecessor key.
4832 */
4833 SortedMap.prototype.getPredecessorKey = function (key) {
4834 var cmp, node = this.root_, rightParent = null;
4835 while (!node.isEmpty()) {
4836 cmp = this.comparator_(key, node.key);
4837 if (cmp === 0) {
4838 if (!node.left.isEmpty()) {
4839 node = node.left;
4840 while (!node.right.isEmpty()) {
4841 node = node.right;
4842 }
4843 return node.key;
4844 }
4845 else if (rightParent) {
4846 return rightParent.key;
4847 }
4848 else {
4849 return null; // first item.
4850 }
4851 }
4852 else if (cmp < 0) {
4853 node = node.left;
4854 }
4855 else if (cmp > 0) {
4856 rightParent = node;
4857 node = node.right;
4858 }
4859 }
4860 throw new Error('Attempted to find predecessor key for a nonexistent key. What gives?');
4861 };
4862 /**
4863 * @returns True if the map is empty.
4864 */
4865 SortedMap.prototype.isEmpty = function () {
4866 return this.root_.isEmpty();
4867 };
4868 /**
4869 * @returns The total number of nodes in the map.
4870 */
4871 SortedMap.prototype.count = function () {
4872 return this.root_.count();
4873 };
4874 /**
4875 * @returns The minimum key in the map.
4876 */
4877 SortedMap.prototype.minKey = function () {
4878 return this.root_.minKey();
4879 };
4880 /**
4881 * @returns The maximum key in the map.
4882 */
4883 SortedMap.prototype.maxKey = function () {
4884 return this.root_.maxKey();
4885 };
4886 /**
4887 * Traverses the map in key order and calls the specified action function
4888 * for each key/value pair.
4889 *
4890 * @param action - Callback function to be called
4891 * for each key/value pair. If action returns true, traversal is aborted.
4892 * @returns The first truthy value returned by action, or the last falsey
4893 * value returned by action
4894 */
4895 SortedMap.prototype.inorderTraversal = function (action) {
4896 return this.root_.inorderTraversal(action);
4897 };
4898 /**
4899 * Traverses the map in reverse key order and calls the specified action function
4900 * for each key/value pair.
4901 *
4902 * @param action - Callback function to be called
4903 * for each key/value pair. If action returns true, traversal is aborted.
4904 * @returns True if the traversal was aborted.
4905 */
4906 SortedMap.prototype.reverseTraversal = function (action) {
4907 return this.root_.reverseTraversal(action);
4908 };
4909 /**
4910 * Returns an iterator over the SortedMap.
4911 * @returns The iterator.
4912 */
4913 SortedMap.prototype.getIterator = function (resultGenerator) {
4914 return new SortedMapIterator(this.root_, null, this.comparator_, false, resultGenerator);
4915 };
4916 SortedMap.prototype.getIteratorFrom = function (key, resultGenerator) {
4917 return new SortedMapIterator(this.root_, key, this.comparator_, false, resultGenerator);
4918 };
4919 SortedMap.prototype.getReverseIteratorFrom = function (key, resultGenerator) {
4920 return new SortedMapIterator(this.root_, key, this.comparator_, true, resultGenerator);
4921 };
4922 SortedMap.prototype.getReverseIterator = function (resultGenerator) {
4923 return new SortedMapIterator(this.root_, null, this.comparator_, true, resultGenerator);
4924 };
4925 /**
4926 * Always use the same empty node, to reduce memory.
4927 */
4928 SortedMap.EMPTY_NODE = new LLRBEmptyNode();
4929 return SortedMap;
4930}());
4931
4932/**
4933 * @license
4934 * Copyright 2017 Google LLC
4935 *
4936 * Licensed under the Apache License, Version 2.0 (the "License");
4937 * you may not use this file except in compliance with the License.
4938 * You may obtain a copy of the License at
4939 *
4940 * http://www.apache.org/licenses/LICENSE-2.0
4941 *
4942 * Unless required by applicable law or agreed to in writing, software
4943 * distributed under the License is distributed on an "AS IS" BASIS,
4944 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4945 * See the License for the specific language governing permissions and
4946 * limitations under the License.
4947 */
4948function NAME_ONLY_COMPARATOR(left, right) {
4949 return nameCompare(left.name, right.name);
4950}
4951function NAME_COMPARATOR(left, right) {
4952 return nameCompare(left, right);
4953}
4954
4955/**
4956 * @license
4957 * Copyright 2017 Google LLC
4958 *
4959 * Licensed under the Apache License, Version 2.0 (the "License");
4960 * you may not use this file except in compliance with the License.
4961 * You may obtain a copy of the License at
4962 *
4963 * http://www.apache.org/licenses/LICENSE-2.0
4964 *
4965 * Unless required by applicable law or agreed to in writing, software
4966 * distributed under the License is distributed on an "AS IS" BASIS,
4967 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4968 * See the License for the specific language governing permissions and
4969 * limitations under the License.
4970 */
4971var MAX_NODE$2;
4972function setMaxNode$1(val) {
4973 MAX_NODE$2 = val;
4974}
4975var priorityHashText = function (priority) {
4976 if (typeof priority === 'number') {
4977 return 'number:' + doubleToIEEE754String(priority);
4978 }
4979 else {
4980 return 'string:' + priority;
4981 }
4982};
4983/**
4984 * Validates that a priority snapshot Node is valid.
4985 */
4986var validatePriorityNode = function (priorityNode) {
4987 if (priorityNode.isLeafNode()) {
4988 var val = priorityNode.val();
4989 util.assert(typeof val === 'string' ||
4990 typeof val === 'number' ||
4991 (typeof val === 'object' && util.contains(val, '.sv')), 'Priority must be a string or number.');
4992 }
4993 else {
4994 util.assert(priorityNode === MAX_NODE$2 || priorityNode.isEmpty(), 'priority of unexpected type.');
4995 }
4996 // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
4997 util.assert(priorityNode === MAX_NODE$2 || priorityNode.getPriority().isEmpty(), "Priority nodes can't have a priority of their own.");
4998};
4999
5000/**
5001 * @license
5002 * Copyright 2017 Google LLC
5003 *
5004 * Licensed under the Apache License, Version 2.0 (the "License");
5005 * you may not use this file except in compliance with the License.
5006 * You may obtain a copy of the License at
5007 *
5008 * http://www.apache.org/licenses/LICENSE-2.0
5009 *
5010 * Unless required by applicable law or agreed to in writing, software
5011 * distributed under the License is distributed on an "AS IS" BASIS,
5012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5013 * See the License for the specific language governing permissions and
5014 * limitations under the License.
5015 */
5016var __childrenNodeConstructor;
5017/**
5018 * LeafNode is a class for storing leaf nodes in a DataSnapshot. It
5019 * implements Node and stores the value of the node (a string,
5020 * number, or boolean) accessible via getValue().
5021 */
5022var LeafNode = /** @class */ (function () {
5023 /**
5024 * @param value_ - The value to store in this leaf node. The object type is
5025 * possible in the event of a deferred value
5026 * @param priorityNode_ - The priority of this node.
5027 */
5028 function LeafNode(value_, priorityNode_) {
5029 if (priorityNode_ === void 0) { priorityNode_ = LeafNode.__childrenNodeConstructor.EMPTY_NODE; }
5030 this.value_ = value_;
5031 this.priorityNode_ = priorityNode_;
5032 this.lazyHash_ = null;
5033 util.assert(this.value_ !== undefined && this.value_ !== null, "LeafNode shouldn't be created with null/undefined value.");
5034 validatePriorityNode(this.priorityNode_);
5035 }
5036 Object.defineProperty(LeafNode, "__childrenNodeConstructor", {
5037 get: function () {
5038 return __childrenNodeConstructor;
5039 },
5040 set: function (val) {
5041 __childrenNodeConstructor = val;
5042 },
5043 enumerable: false,
5044 configurable: true
5045 });
5046 /** @inheritDoc */
5047 LeafNode.prototype.isLeafNode = function () {
5048 return true;
5049 };
5050 /** @inheritDoc */
5051 LeafNode.prototype.getPriority = function () {
5052 return this.priorityNode_;
5053 };
5054 /** @inheritDoc */
5055 LeafNode.prototype.updatePriority = function (newPriorityNode) {
5056 return new LeafNode(this.value_, newPriorityNode);
5057 };
5058 /** @inheritDoc */
5059 LeafNode.prototype.getImmediateChild = function (childName) {
5060 // Hack to treat priority as a regular child
5061 if (childName === '.priority') {
5062 return this.priorityNode_;
5063 }
5064 else {
5065 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5066 }
5067 };
5068 /** @inheritDoc */
5069 LeafNode.prototype.getChild = function (path) {
5070 if (pathIsEmpty(path)) {
5071 return this;
5072 }
5073 else if (pathGetFront(path) === '.priority') {
5074 return this.priorityNode_;
5075 }
5076 else {
5077 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5078 }
5079 };
5080 LeafNode.prototype.hasChild = function () {
5081 return false;
5082 };
5083 /** @inheritDoc */
5084 LeafNode.prototype.getPredecessorChildName = function (childName, childNode) {
5085 return null;
5086 };
5087 /** @inheritDoc */
5088 LeafNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5089 if (childName === '.priority') {
5090 return this.updatePriority(newChildNode);
5091 }
5092 else if (newChildNode.isEmpty() && childName !== '.priority') {
5093 return this;
5094 }
5095 else {
5096 return LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateImmediateChild(childName, newChildNode).updatePriority(this.priorityNode_);
5097 }
5098 };
5099 /** @inheritDoc */
5100 LeafNode.prototype.updateChild = function (path, newChildNode) {
5101 var front = pathGetFront(path);
5102 if (front === null) {
5103 return newChildNode;
5104 }
5105 else if (newChildNode.isEmpty() && front !== '.priority') {
5106 return this;
5107 }
5108 else {
5109 util.assert(front !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5110 return this.updateImmediateChild(front, LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateChild(pathPopFront(path), newChildNode));
5111 }
5112 };
5113 /** @inheritDoc */
5114 LeafNode.prototype.isEmpty = function () {
5115 return false;
5116 };
5117 /** @inheritDoc */
5118 LeafNode.prototype.numChildren = function () {
5119 return 0;
5120 };
5121 /** @inheritDoc */
5122 LeafNode.prototype.forEachChild = function (index, action) {
5123 return false;
5124 };
5125 LeafNode.prototype.val = function (exportFormat) {
5126 if (exportFormat && !this.getPriority().isEmpty()) {
5127 return {
5128 '.value': this.getValue(),
5129 '.priority': this.getPriority().val()
5130 };
5131 }
5132 else {
5133 return this.getValue();
5134 }
5135 };
5136 /** @inheritDoc */
5137 LeafNode.prototype.hash = function () {
5138 if (this.lazyHash_ === null) {
5139 var toHash = '';
5140 if (!this.priorityNode_.isEmpty()) {
5141 toHash +=
5142 'priority:' +
5143 priorityHashText(this.priorityNode_.val()) +
5144 ':';
5145 }
5146 var type = typeof this.value_;
5147 toHash += type + ':';
5148 if (type === 'number') {
5149 toHash += doubleToIEEE754String(this.value_);
5150 }
5151 else {
5152 toHash += this.value_;
5153 }
5154 this.lazyHash_ = sha1(toHash);
5155 }
5156 return this.lazyHash_;
5157 };
5158 /**
5159 * Returns the value of the leaf node.
5160 * @returns The value of the node.
5161 */
5162 LeafNode.prototype.getValue = function () {
5163 return this.value_;
5164 };
5165 LeafNode.prototype.compareTo = function (other) {
5166 if (other === LeafNode.__childrenNodeConstructor.EMPTY_NODE) {
5167 return 1;
5168 }
5169 else if (other instanceof LeafNode.__childrenNodeConstructor) {
5170 return -1;
5171 }
5172 else {
5173 util.assert(other.isLeafNode(), 'Unknown node type');
5174 return this.compareToLeafNode_(other);
5175 }
5176 };
5177 /**
5178 * Comparison specifically for two leaf nodes
5179 */
5180 LeafNode.prototype.compareToLeafNode_ = function (otherLeaf) {
5181 var otherLeafType = typeof otherLeaf.value_;
5182 var thisLeafType = typeof this.value_;
5183 var otherIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(otherLeafType);
5184 var thisIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(thisLeafType);
5185 util.assert(otherIndex >= 0, 'Unknown leaf type: ' + otherLeafType);
5186 util.assert(thisIndex >= 0, 'Unknown leaf type: ' + thisLeafType);
5187 if (otherIndex === thisIndex) {
5188 // Same type, compare values
5189 if (thisLeafType === 'object') {
5190 // Deferred value nodes are all equal, but we should also never get to this point...
5191 return 0;
5192 }
5193 else {
5194 // Note that this works because true > false, all others are number or string comparisons
5195 if (this.value_ < otherLeaf.value_) {
5196 return -1;
5197 }
5198 else if (this.value_ === otherLeaf.value_) {
5199 return 0;
5200 }
5201 else {
5202 return 1;
5203 }
5204 }
5205 }
5206 else {
5207 return thisIndex - otherIndex;
5208 }
5209 };
5210 LeafNode.prototype.withIndex = function () {
5211 return this;
5212 };
5213 LeafNode.prototype.isIndexed = function () {
5214 return true;
5215 };
5216 LeafNode.prototype.equals = function (other) {
5217 if (other === this) {
5218 return true;
5219 }
5220 else if (other.isLeafNode()) {
5221 var otherLeaf = other;
5222 return (this.value_ === otherLeaf.value_ &&
5223 this.priorityNode_.equals(otherLeaf.priorityNode_));
5224 }
5225 else {
5226 return false;
5227 }
5228 };
5229 /**
5230 * The sort order for comparing leaf nodes of different types. If two leaf nodes have
5231 * the same type, the comparison falls back to their value
5232 */
5233 LeafNode.VALUE_TYPE_ORDER = ['object', 'boolean', 'number', 'string'];
5234 return LeafNode;
5235}());
5236
5237/**
5238 * @license
5239 * Copyright 2017 Google LLC
5240 *
5241 * Licensed under the Apache License, Version 2.0 (the "License");
5242 * you may not use this file except in compliance with the License.
5243 * You may obtain a copy of the License at
5244 *
5245 * http://www.apache.org/licenses/LICENSE-2.0
5246 *
5247 * Unless required by applicable law or agreed to in writing, software
5248 * distributed under the License is distributed on an "AS IS" BASIS,
5249 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5250 * See the License for the specific language governing permissions and
5251 * limitations under the License.
5252 */
5253var nodeFromJSON$1;
5254var MAX_NODE$1;
5255function setNodeFromJSON(val) {
5256 nodeFromJSON$1 = val;
5257}
5258function setMaxNode(val) {
5259 MAX_NODE$1 = val;
5260}
5261var PriorityIndex = /** @class */ (function (_super) {
5262 tslib.__extends(PriorityIndex, _super);
5263 function PriorityIndex() {
5264 return _super !== null && _super.apply(this, arguments) || this;
5265 }
5266 PriorityIndex.prototype.compare = function (a, b) {
5267 var aPriority = a.node.getPriority();
5268 var bPriority = b.node.getPriority();
5269 var indexCmp = aPriority.compareTo(bPriority);
5270 if (indexCmp === 0) {
5271 return nameCompare(a.name, b.name);
5272 }
5273 else {
5274 return indexCmp;
5275 }
5276 };
5277 PriorityIndex.prototype.isDefinedOn = function (node) {
5278 return !node.getPriority().isEmpty();
5279 };
5280 PriorityIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
5281 return !oldNode.getPriority().equals(newNode.getPriority());
5282 };
5283 PriorityIndex.prototype.minPost = function () {
5284 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5285 return NamedNode.MIN;
5286 };
5287 PriorityIndex.prototype.maxPost = function () {
5288 return new NamedNode(MAX_NAME, new LeafNode('[PRIORITY-POST]', MAX_NODE$1));
5289 };
5290 PriorityIndex.prototype.makePost = function (indexValue, name) {
5291 var priorityNode = nodeFromJSON$1(indexValue);
5292 return new NamedNode(name, new LeafNode('[PRIORITY-POST]', priorityNode));
5293 };
5294 /**
5295 * @returns String representation for inclusion in a query spec
5296 */
5297 PriorityIndex.prototype.toString = function () {
5298 return '.priority';
5299 };
5300 return PriorityIndex;
5301}(Index));
5302var PRIORITY_INDEX = new PriorityIndex();
5303
5304/**
5305 * @license
5306 * Copyright 2017 Google LLC
5307 *
5308 * Licensed under the Apache License, Version 2.0 (the "License");
5309 * you may not use this file except in compliance with the License.
5310 * You may obtain a copy of the License at
5311 *
5312 * http://www.apache.org/licenses/LICENSE-2.0
5313 *
5314 * Unless required by applicable law or agreed to in writing, software
5315 * distributed under the License is distributed on an "AS IS" BASIS,
5316 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5317 * See the License for the specific language governing permissions and
5318 * limitations under the License.
5319 */
5320var LOG_2 = Math.log(2);
5321var Base12Num = /** @class */ (function () {
5322 function Base12Num(length) {
5323 var logBase2 = function (num) {
5324 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5325 return parseInt((Math.log(num) / LOG_2), 10);
5326 };
5327 var bitMask = function (bits) { return parseInt(Array(bits + 1).join('1'), 2); };
5328 this.count = logBase2(length + 1);
5329 this.current_ = this.count - 1;
5330 var mask = bitMask(this.count);
5331 this.bits_ = (length + 1) & mask;
5332 }
5333 Base12Num.prototype.nextBitIsOne = function () {
5334 //noinspection JSBitwiseOperatorUsage
5335 var result = !(this.bits_ & (0x1 << this.current_));
5336 this.current_--;
5337 return result;
5338 };
5339 return Base12Num;
5340}());
5341/**
5342 * Takes a list of child nodes and constructs a SortedSet using the given comparison
5343 * function
5344 *
5345 * Uses the algorithm described in the paper linked here:
5346 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458
5347 *
5348 * @param childList - Unsorted list of children
5349 * @param cmp - The comparison method to be used
5350 * @param keyFn - An optional function to extract K from a node wrapper, if K's
5351 * type is not NamedNode
5352 * @param mapSortFn - An optional override for comparator used by the generated sorted map
5353 */
5354var buildChildSet = function (childList, cmp, keyFn, mapSortFn) {
5355 childList.sort(cmp);
5356 var buildBalancedTree = function (low, high) {
5357 var length = high - low;
5358 var namedNode;
5359 var key;
5360 if (length === 0) {
5361 return null;
5362 }
5363 else if (length === 1) {
5364 namedNode = childList[low];
5365 key = keyFn ? keyFn(namedNode) : namedNode;
5366 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, null, null);
5367 }
5368 else {
5369 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5370 var middle = parseInt((length / 2), 10) + low;
5371 var left = buildBalancedTree(low, middle);
5372 var right = buildBalancedTree(middle + 1, high);
5373 namedNode = childList[middle];
5374 key = keyFn ? keyFn(namedNode) : namedNode;
5375 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, left, right);
5376 }
5377 };
5378 var buildFrom12Array = function (base12) {
5379 var node = null;
5380 var root = null;
5381 var index = childList.length;
5382 var buildPennant = function (chunkSize, color) {
5383 var low = index - chunkSize;
5384 var high = index;
5385 index -= chunkSize;
5386 var childTree = buildBalancedTree(low + 1, high);
5387 var namedNode = childList[low];
5388 var key = keyFn ? keyFn(namedNode) : namedNode;
5389 attachPennant(new LLRBNode(key, namedNode.node, color, null, childTree));
5390 };
5391 var attachPennant = function (pennant) {
5392 if (node) {
5393 node.left = pennant;
5394 node = pennant;
5395 }
5396 else {
5397 root = pennant;
5398 node = pennant;
5399 }
5400 };
5401 for (var i = 0; i < base12.count; ++i) {
5402 var isOne = base12.nextBitIsOne();
5403 // The number of nodes taken in each slice is 2^(arr.length - (i + 1))
5404 var chunkSize = Math.pow(2, base12.count - (i + 1));
5405 if (isOne) {
5406 buildPennant(chunkSize, LLRBNode.BLACK);
5407 }
5408 else {
5409 // current == 2
5410 buildPennant(chunkSize, LLRBNode.BLACK);
5411 buildPennant(chunkSize, LLRBNode.RED);
5412 }
5413 }
5414 return root;
5415 };
5416 var base12 = new Base12Num(childList.length);
5417 var root = buildFrom12Array(base12);
5418 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5419 return new SortedMap(mapSortFn || cmp, root);
5420};
5421
5422/**
5423 * @license
5424 * Copyright 2017 Google LLC
5425 *
5426 * Licensed under the Apache License, Version 2.0 (the "License");
5427 * you may not use this file except in compliance with the License.
5428 * You may obtain a copy of the License at
5429 *
5430 * http://www.apache.org/licenses/LICENSE-2.0
5431 *
5432 * Unless required by applicable law or agreed to in writing, software
5433 * distributed under the License is distributed on an "AS IS" BASIS,
5434 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5435 * See the License for the specific language governing permissions and
5436 * limitations under the License.
5437 */
5438var _defaultIndexMap;
5439var fallbackObject = {};
5440var IndexMap = /** @class */ (function () {
5441 function IndexMap(indexes_, indexSet_) {
5442 this.indexes_ = indexes_;
5443 this.indexSet_ = indexSet_;
5444 }
5445 Object.defineProperty(IndexMap, "Default", {
5446 /**
5447 * The default IndexMap for nodes without a priority
5448 */
5449 get: function () {
5450 util.assert(fallbackObject && PRIORITY_INDEX, 'ChildrenNode.ts has not been loaded');
5451 _defaultIndexMap =
5452 _defaultIndexMap ||
5453 new IndexMap({ '.priority': fallbackObject }, { '.priority': PRIORITY_INDEX });
5454 return _defaultIndexMap;
5455 },
5456 enumerable: false,
5457 configurable: true
5458 });
5459 IndexMap.prototype.get = function (indexKey) {
5460 var sortedMap = util.safeGet(this.indexes_, indexKey);
5461 if (!sortedMap) {
5462 throw new Error('No index defined for ' + indexKey);
5463 }
5464 if (sortedMap instanceof SortedMap) {
5465 return sortedMap;
5466 }
5467 else {
5468 // The index exists, but it falls back to just name comparison. Return null so that the calling code uses the
5469 // regular child map
5470 return null;
5471 }
5472 };
5473 IndexMap.prototype.hasIndex = function (indexDefinition) {
5474 return util.contains(this.indexSet_, indexDefinition.toString());
5475 };
5476 IndexMap.prototype.addIndex = function (indexDefinition, existingChildren) {
5477 util.assert(indexDefinition !== KEY_INDEX, "KeyIndex always exists and isn't meant to be added to the IndexMap.");
5478 var childList = [];
5479 var sawIndexedValue = false;
5480 var iter = existingChildren.getIterator(NamedNode.Wrap);
5481 var next = iter.getNext();
5482 while (next) {
5483 sawIndexedValue =
5484 sawIndexedValue || indexDefinition.isDefinedOn(next.node);
5485 childList.push(next);
5486 next = iter.getNext();
5487 }
5488 var newIndex;
5489 if (sawIndexedValue) {
5490 newIndex = buildChildSet(childList, indexDefinition.getCompare());
5491 }
5492 else {
5493 newIndex = fallbackObject;
5494 }
5495 var indexName = indexDefinition.toString();
5496 var newIndexSet = tslib.__assign({}, this.indexSet_);
5497 newIndexSet[indexName] = indexDefinition;
5498 var newIndexes = tslib.__assign({}, this.indexes_);
5499 newIndexes[indexName] = newIndex;
5500 return new IndexMap(newIndexes, newIndexSet);
5501 };
5502 /**
5503 * Ensure that this node is properly tracked in any indexes that we're maintaining
5504 */
5505 IndexMap.prototype.addToIndexes = function (namedNode, existingChildren) {
5506 var _this = this;
5507 var newIndexes = util.map(this.indexes_, function (indexedChildren, indexName) {
5508 var index = util.safeGet(_this.indexSet_, indexName);
5509 util.assert(index, 'Missing index implementation for ' + indexName);
5510 if (indexedChildren === fallbackObject) {
5511 // Check to see if we need to index everything
5512 if (index.isDefinedOn(namedNode.node)) {
5513 // We need to build this index
5514 var childList = [];
5515 var iter = existingChildren.getIterator(NamedNode.Wrap);
5516 var next = iter.getNext();
5517 while (next) {
5518 if (next.name !== namedNode.name) {
5519 childList.push(next);
5520 }
5521 next = iter.getNext();
5522 }
5523 childList.push(namedNode);
5524 return buildChildSet(childList, index.getCompare());
5525 }
5526 else {
5527 // No change, this remains a fallback
5528 return fallbackObject;
5529 }
5530 }
5531 else {
5532 var existingSnap = existingChildren.get(namedNode.name);
5533 var newChildren = indexedChildren;
5534 if (existingSnap) {
5535 newChildren = newChildren.remove(new NamedNode(namedNode.name, existingSnap));
5536 }
5537 return newChildren.insert(namedNode, namedNode.node);
5538 }
5539 });
5540 return new IndexMap(newIndexes, this.indexSet_);
5541 };
5542 /**
5543 * Create a new IndexMap instance with the given value removed
5544 */
5545 IndexMap.prototype.removeFromIndexes = function (namedNode, existingChildren) {
5546 var newIndexes = util.map(this.indexes_, function (indexedChildren) {
5547 if (indexedChildren === fallbackObject) {
5548 // This is the fallback. Just return it, nothing to do in this case
5549 return indexedChildren;
5550 }
5551 else {
5552 var existingSnap = existingChildren.get(namedNode.name);
5553 if (existingSnap) {
5554 return indexedChildren.remove(new NamedNode(namedNode.name, existingSnap));
5555 }
5556 else {
5557 // No record of this child
5558 return indexedChildren;
5559 }
5560 }
5561 });
5562 return new IndexMap(newIndexes, this.indexSet_);
5563 };
5564 return IndexMap;
5565}());
5566
5567/**
5568 * @license
5569 * Copyright 2017 Google LLC
5570 *
5571 * Licensed under the Apache License, Version 2.0 (the "License");
5572 * you may not use this file except in compliance with the License.
5573 * You may obtain a copy of the License at
5574 *
5575 * http://www.apache.org/licenses/LICENSE-2.0
5576 *
5577 * Unless required by applicable law or agreed to in writing, software
5578 * distributed under the License is distributed on an "AS IS" BASIS,
5579 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5580 * See the License for the specific language governing permissions and
5581 * limitations under the License.
5582 */
5583// TODO: For memory savings, don't store priorityNode_ if it's empty.
5584var EMPTY_NODE;
5585/**
5586 * ChildrenNode is a class for storing internal nodes in a DataSnapshot
5587 * (i.e. nodes with children). It implements Node and stores the
5588 * list of children in the children property, sorted by child name.
5589 */
5590var ChildrenNode = /** @class */ (function () {
5591 /**
5592 * @param children_ - List of children of this node..
5593 * @param priorityNode_ - The priority of this node (as a snapshot node).
5594 */
5595 function ChildrenNode(children_, priorityNode_, indexMap_) {
5596 this.children_ = children_;
5597 this.priorityNode_ = priorityNode_;
5598 this.indexMap_ = indexMap_;
5599 this.lazyHash_ = null;
5600 /**
5601 * Note: The only reason we allow null priority is for EMPTY_NODE, since we can't use
5602 * EMPTY_NODE as the priority of EMPTY_NODE. We might want to consider making EMPTY_NODE its own
5603 * class instead of an empty ChildrenNode.
5604 */
5605 if (this.priorityNode_) {
5606 validatePriorityNode(this.priorityNode_);
5607 }
5608 if (this.children_.isEmpty()) {
5609 util.assert(!this.priorityNode_ || this.priorityNode_.isEmpty(), 'An empty node cannot have a priority');
5610 }
5611 }
5612 Object.defineProperty(ChildrenNode, "EMPTY_NODE", {
5613 get: function () {
5614 return (EMPTY_NODE ||
5615 (EMPTY_NODE = new ChildrenNode(new SortedMap(NAME_COMPARATOR), null, IndexMap.Default)));
5616 },
5617 enumerable: false,
5618 configurable: true
5619 });
5620 /** @inheritDoc */
5621 ChildrenNode.prototype.isLeafNode = function () {
5622 return false;
5623 };
5624 /** @inheritDoc */
5625 ChildrenNode.prototype.getPriority = function () {
5626 return this.priorityNode_ || EMPTY_NODE;
5627 };
5628 /** @inheritDoc */
5629 ChildrenNode.prototype.updatePriority = function (newPriorityNode) {
5630 if (this.children_.isEmpty()) {
5631 // Don't allow priorities on empty nodes
5632 return this;
5633 }
5634 else {
5635 return new ChildrenNode(this.children_, newPriorityNode, this.indexMap_);
5636 }
5637 };
5638 /** @inheritDoc */
5639 ChildrenNode.prototype.getImmediateChild = function (childName) {
5640 // Hack to treat priority as a regular child
5641 if (childName === '.priority') {
5642 return this.getPriority();
5643 }
5644 else {
5645 var child = this.children_.get(childName);
5646 return child === null ? EMPTY_NODE : child;
5647 }
5648 };
5649 /** @inheritDoc */
5650 ChildrenNode.prototype.getChild = function (path) {
5651 var front = pathGetFront(path);
5652 if (front === null) {
5653 return this;
5654 }
5655 return this.getImmediateChild(front).getChild(pathPopFront(path));
5656 };
5657 /** @inheritDoc */
5658 ChildrenNode.prototype.hasChild = function (childName) {
5659 return this.children_.get(childName) !== null;
5660 };
5661 /** @inheritDoc */
5662 ChildrenNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5663 util.assert(newChildNode, 'We should always be passing snapshot nodes');
5664 if (childName === '.priority') {
5665 return this.updatePriority(newChildNode);
5666 }
5667 else {
5668 var namedNode = new NamedNode(childName, newChildNode);
5669 var newChildren = void 0, newIndexMap = void 0;
5670 if (newChildNode.isEmpty()) {
5671 newChildren = this.children_.remove(childName);
5672 newIndexMap = this.indexMap_.removeFromIndexes(namedNode, this.children_);
5673 }
5674 else {
5675 newChildren = this.children_.insert(childName, newChildNode);
5676 newIndexMap = this.indexMap_.addToIndexes(namedNode, this.children_);
5677 }
5678 var newPriority = newChildren.isEmpty()
5679 ? EMPTY_NODE
5680 : this.priorityNode_;
5681 return new ChildrenNode(newChildren, newPriority, newIndexMap);
5682 }
5683 };
5684 /** @inheritDoc */
5685 ChildrenNode.prototype.updateChild = function (path, newChildNode) {
5686 var front = pathGetFront(path);
5687 if (front === null) {
5688 return newChildNode;
5689 }
5690 else {
5691 util.assert(pathGetFront(path) !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5692 var newImmediateChild = this.getImmediateChild(front).updateChild(pathPopFront(path), newChildNode);
5693 return this.updateImmediateChild(front, newImmediateChild);
5694 }
5695 };
5696 /** @inheritDoc */
5697 ChildrenNode.prototype.isEmpty = function () {
5698 return this.children_.isEmpty();
5699 };
5700 /** @inheritDoc */
5701 ChildrenNode.prototype.numChildren = function () {
5702 return this.children_.count();
5703 };
5704 /** @inheritDoc */
5705 ChildrenNode.prototype.val = function (exportFormat) {
5706 if (this.isEmpty()) {
5707 return null;
5708 }
5709 var obj = {};
5710 var numKeys = 0, maxKey = 0, allIntegerKeys = true;
5711 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5712 obj[key] = childNode.val(exportFormat);
5713 numKeys++;
5714 if (allIntegerKeys && ChildrenNode.INTEGER_REGEXP_.test(key)) {
5715 maxKey = Math.max(maxKey, Number(key));
5716 }
5717 else {
5718 allIntegerKeys = false;
5719 }
5720 });
5721 if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
5722 // convert to array.
5723 var array = [];
5724 // eslint-disable-next-line guard-for-in
5725 for (var key in obj) {
5726 array[key] = obj[key];
5727 }
5728 return array;
5729 }
5730 else {
5731 if (exportFormat && !this.getPriority().isEmpty()) {
5732 obj['.priority'] = this.getPriority().val();
5733 }
5734 return obj;
5735 }
5736 };
5737 /** @inheritDoc */
5738 ChildrenNode.prototype.hash = function () {
5739 if (this.lazyHash_ === null) {
5740 var toHash_1 = '';
5741 if (!this.getPriority().isEmpty()) {
5742 toHash_1 +=
5743 'priority:' +
5744 priorityHashText(this.getPriority().val()) +
5745 ':';
5746 }
5747 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5748 var childHash = childNode.hash();
5749 if (childHash !== '') {
5750 toHash_1 += ':' + key + ':' + childHash;
5751 }
5752 });
5753 this.lazyHash_ = toHash_1 === '' ? '' : sha1(toHash_1);
5754 }
5755 return this.lazyHash_;
5756 };
5757 /** @inheritDoc */
5758 ChildrenNode.prototype.getPredecessorChildName = function (childName, childNode, index) {
5759 var idx = this.resolveIndex_(index);
5760 if (idx) {
5761 var predecessor = idx.getPredecessorKey(new NamedNode(childName, childNode));
5762 return predecessor ? predecessor.name : null;
5763 }
5764 else {
5765 return this.children_.getPredecessorKey(childName);
5766 }
5767 };
5768 ChildrenNode.prototype.getFirstChildName = function (indexDefinition) {
5769 var idx = this.resolveIndex_(indexDefinition);
5770 if (idx) {
5771 var minKey = idx.minKey();
5772 return minKey && minKey.name;
5773 }
5774 else {
5775 return this.children_.minKey();
5776 }
5777 };
5778 ChildrenNode.prototype.getFirstChild = function (indexDefinition) {
5779 var minKey = this.getFirstChildName(indexDefinition);
5780 if (minKey) {
5781 return new NamedNode(minKey, this.children_.get(minKey));
5782 }
5783 else {
5784 return null;
5785 }
5786 };
5787 /**
5788 * Given an index, return the key name of the largest value we have, according to that index
5789 */
5790 ChildrenNode.prototype.getLastChildName = function (indexDefinition) {
5791 var idx = this.resolveIndex_(indexDefinition);
5792 if (idx) {
5793 var maxKey = idx.maxKey();
5794 return maxKey && maxKey.name;
5795 }
5796 else {
5797 return this.children_.maxKey();
5798 }
5799 };
5800 ChildrenNode.prototype.getLastChild = function (indexDefinition) {
5801 var maxKey = this.getLastChildName(indexDefinition);
5802 if (maxKey) {
5803 return new NamedNode(maxKey, this.children_.get(maxKey));
5804 }
5805 else {
5806 return null;
5807 }
5808 };
5809 ChildrenNode.prototype.forEachChild = function (index, action) {
5810 var idx = this.resolveIndex_(index);
5811 if (idx) {
5812 return idx.inorderTraversal(function (wrappedNode) {
5813 return action(wrappedNode.name, wrappedNode.node);
5814 });
5815 }
5816 else {
5817 return this.children_.inorderTraversal(action);
5818 }
5819 };
5820 ChildrenNode.prototype.getIterator = function (indexDefinition) {
5821 return this.getIteratorFrom(indexDefinition.minPost(), indexDefinition);
5822 };
5823 ChildrenNode.prototype.getIteratorFrom = function (startPost, indexDefinition) {
5824 var idx = this.resolveIndex_(indexDefinition);
5825 if (idx) {
5826 return idx.getIteratorFrom(startPost, function (key) { return key; });
5827 }
5828 else {
5829 var iterator = this.children_.getIteratorFrom(startPost.name, NamedNode.Wrap);
5830 var next = iterator.peek();
5831 while (next != null && indexDefinition.compare(next, startPost) < 0) {
5832 iterator.getNext();
5833 next = iterator.peek();
5834 }
5835 return iterator;
5836 }
5837 };
5838 ChildrenNode.prototype.getReverseIterator = function (indexDefinition) {
5839 return this.getReverseIteratorFrom(indexDefinition.maxPost(), indexDefinition);
5840 };
5841 ChildrenNode.prototype.getReverseIteratorFrom = function (endPost, indexDefinition) {
5842 var idx = this.resolveIndex_(indexDefinition);
5843 if (idx) {
5844 return idx.getReverseIteratorFrom(endPost, function (key) {
5845 return key;
5846 });
5847 }
5848 else {
5849 var iterator = this.children_.getReverseIteratorFrom(endPost.name, NamedNode.Wrap);
5850 var next = iterator.peek();
5851 while (next != null && indexDefinition.compare(next, endPost) > 0) {
5852 iterator.getNext();
5853 next = iterator.peek();
5854 }
5855 return iterator;
5856 }
5857 };
5858 ChildrenNode.prototype.compareTo = function (other) {
5859 if (this.isEmpty()) {
5860 if (other.isEmpty()) {
5861 return 0;
5862 }
5863 else {
5864 return -1;
5865 }
5866 }
5867 else if (other.isLeafNode() || other.isEmpty()) {
5868 return 1;
5869 }
5870 else if (other === MAX_NODE) {
5871 return -1;
5872 }
5873 else {
5874 // Must be another node with children.
5875 return 0;
5876 }
5877 };
5878 ChildrenNode.prototype.withIndex = function (indexDefinition) {
5879 if (indexDefinition === KEY_INDEX ||
5880 this.indexMap_.hasIndex(indexDefinition)) {
5881 return this;
5882 }
5883 else {
5884 var newIndexMap = this.indexMap_.addIndex(indexDefinition, this.children_);
5885 return new ChildrenNode(this.children_, this.priorityNode_, newIndexMap);
5886 }
5887 };
5888 ChildrenNode.prototype.isIndexed = function (index) {
5889 return index === KEY_INDEX || this.indexMap_.hasIndex(index);
5890 };
5891 ChildrenNode.prototype.equals = function (other) {
5892 if (other === this) {
5893 return true;
5894 }
5895 else if (other.isLeafNode()) {
5896 return false;
5897 }
5898 else {
5899 var otherChildrenNode = other;
5900 if (!this.getPriority().equals(otherChildrenNode.getPriority())) {
5901 return false;
5902 }
5903 else if (this.children_.count() === otherChildrenNode.children_.count()) {
5904 var thisIter = this.getIterator(PRIORITY_INDEX);
5905 var otherIter = otherChildrenNode.getIterator(PRIORITY_INDEX);
5906 var thisCurrent = thisIter.getNext();
5907 var otherCurrent = otherIter.getNext();
5908 while (thisCurrent && otherCurrent) {
5909 if (thisCurrent.name !== otherCurrent.name ||
5910 !thisCurrent.node.equals(otherCurrent.node)) {
5911 return false;
5912 }
5913 thisCurrent = thisIter.getNext();
5914 otherCurrent = otherIter.getNext();
5915 }
5916 return thisCurrent === null && otherCurrent === null;
5917 }
5918 else {
5919 return false;
5920 }
5921 }
5922 };
5923 /**
5924 * Returns a SortedMap ordered by index, or null if the default (by-key) ordering can be used
5925 * instead.
5926 *
5927 */
5928 ChildrenNode.prototype.resolveIndex_ = function (indexDefinition) {
5929 if (indexDefinition === KEY_INDEX) {
5930 return null;
5931 }
5932 else {
5933 return this.indexMap_.get(indexDefinition.toString());
5934 }
5935 };
5936 ChildrenNode.INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;
5937 return ChildrenNode;
5938}());
5939var MaxNode = /** @class */ (function (_super) {
5940 tslib.__extends(MaxNode, _super);
5941 function MaxNode() {
5942 return _super.call(this, new SortedMap(NAME_COMPARATOR), ChildrenNode.EMPTY_NODE, IndexMap.Default) || this;
5943 }
5944 MaxNode.prototype.compareTo = function (other) {
5945 if (other === this) {
5946 return 0;
5947 }
5948 else {
5949 return 1;
5950 }
5951 };
5952 MaxNode.prototype.equals = function (other) {
5953 // Not that we every compare it, but MAX_NODE is only ever equal to itself
5954 return other === this;
5955 };
5956 MaxNode.prototype.getPriority = function () {
5957 return this;
5958 };
5959 MaxNode.prototype.getImmediateChild = function (childName) {
5960 return ChildrenNode.EMPTY_NODE;
5961 };
5962 MaxNode.prototype.isEmpty = function () {
5963 return false;
5964 };
5965 return MaxNode;
5966}(ChildrenNode));
5967/**
5968 * Marker that will sort higher than any other snapshot.
5969 */
5970var MAX_NODE = new MaxNode();
5971Object.defineProperties(NamedNode, {
5972 MIN: {
5973 value: new NamedNode(MIN_NAME, ChildrenNode.EMPTY_NODE)
5974 },
5975 MAX: {
5976 value: new NamedNode(MAX_NAME, MAX_NODE)
5977 }
5978});
5979/**
5980 * Reference Extensions
5981 */
5982KeyIndex.__EMPTY_NODE = ChildrenNode.EMPTY_NODE;
5983LeafNode.__childrenNodeConstructor = ChildrenNode;
5984setMaxNode$1(MAX_NODE);
5985setMaxNode(MAX_NODE);
5986
5987/**
5988 * @license
5989 * Copyright 2017 Google LLC
5990 *
5991 * Licensed under the Apache License, Version 2.0 (the "License");
5992 * you may not use this file except in compliance with the License.
5993 * You may obtain a copy of the License at
5994 *
5995 * http://www.apache.org/licenses/LICENSE-2.0
5996 *
5997 * Unless required by applicable law or agreed to in writing, software
5998 * distributed under the License is distributed on an "AS IS" BASIS,
5999 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6000 * See the License for the specific language governing permissions and
6001 * limitations under the License.
6002 */
6003var USE_HINZE = true;
6004/**
6005 * Constructs a snapshot node representing the passed JSON and returns it.
6006 * @param json - JSON to create a node for.
6007 * @param priority - Optional priority to use. This will be ignored if the
6008 * passed JSON contains a .priority property.
6009 */
6010function nodeFromJSON(json, priority) {
6011 if (priority === void 0) { priority = null; }
6012 if (json === null) {
6013 return ChildrenNode.EMPTY_NODE;
6014 }
6015 if (typeof json === 'object' && '.priority' in json) {
6016 priority = json['.priority'];
6017 }
6018 util.assert(priority === null ||
6019 typeof priority === 'string' ||
6020 typeof priority === 'number' ||
6021 (typeof priority === 'object' && '.sv' in priority), 'Invalid priority type found: ' + typeof priority);
6022 if (typeof json === 'object' && '.value' in json && json['.value'] !== null) {
6023 json = json['.value'];
6024 }
6025 // Valid leaf nodes include non-objects or server-value wrapper objects
6026 if (typeof json !== 'object' || '.sv' in json) {
6027 var jsonLeaf = json;
6028 return new LeafNode(jsonLeaf, nodeFromJSON(priority));
6029 }
6030 if (!(json instanceof Array) && USE_HINZE) {
6031 var children_1 = [];
6032 var childrenHavePriority_1 = false;
6033 var hinzeJsonObj = json;
6034 each(hinzeJsonObj, function (key, child) {
6035 if (key.substring(0, 1) !== '.') {
6036 // Ignore metadata nodes
6037 var childNode = nodeFromJSON(child);
6038 if (!childNode.isEmpty()) {
6039 childrenHavePriority_1 =
6040 childrenHavePriority_1 || !childNode.getPriority().isEmpty();
6041 children_1.push(new NamedNode(key, childNode));
6042 }
6043 }
6044 });
6045 if (children_1.length === 0) {
6046 return ChildrenNode.EMPTY_NODE;
6047 }
6048 var childSet = buildChildSet(children_1, NAME_ONLY_COMPARATOR, function (namedNode) { return namedNode.name; }, NAME_COMPARATOR);
6049 if (childrenHavePriority_1) {
6050 var sortedChildSet = buildChildSet(children_1, PRIORITY_INDEX.getCompare());
6051 return new ChildrenNode(childSet, nodeFromJSON(priority), new IndexMap({ '.priority': sortedChildSet }, { '.priority': PRIORITY_INDEX }));
6052 }
6053 else {
6054 return new ChildrenNode(childSet, nodeFromJSON(priority), IndexMap.Default);
6055 }
6056 }
6057 else {
6058 var node_1 = ChildrenNode.EMPTY_NODE;
6059 each(json, function (key, childData) {
6060 if (util.contains(json, key)) {
6061 if (key.substring(0, 1) !== '.') {
6062 // ignore metadata nodes.
6063 var childNode = nodeFromJSON(childData);
6064 if (childNode.isLeafNode() || !childNode.isEmpty()) {
6065 node_1 = node_1.updateImmediateChild(key, childNode);
6066 }
6067 }
6068 }
6069 });
6070 return node_1.updatePriority(nodeFromJSON(priority));
6071 }
6072}
6073setNodeFromJSON(nodeFromJSON);
6074
6075/**
6076 * @license
6077 * Copyright 2017 Google LLC
6078 *
6079 * Licensed under the Apache License, Version 2.0 (the "License");
6080 * you may not use this file except in compliance with the License.
6081 * You may obtain a copy of the License at
6082 *
6083 * http://www.apache.org/licenses/LICENSE-2.0
6084 *
6085 * Unless required by applicable law or agreed to in writing, software
6086 * distributed under the License is distributed on an "AS IS" BASIS,
6087 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6088 * See the License for the specific language governing permissions and
6089 * limitations under the License.
6090 */
6091var PathIndex = /** @class */ (function (_super) {
6092 tslib.__extends(PathIndex, _super);
6093 function PathIndex(indexPath_) {
6094 var _this = _super.call(this) || this;
6095 _this.indexPath_ = indexPath_;
6096 util.assert(!pathIsEmpty(indexPath_) && pathGetFront(indexPath_) !== '.priority', "Can't create PathIndex with empty path or .priority key");
6097 return _this;
6098 }
6099 PathIndex.prototype.extractChild = function (snap) {
6100 return snap.getChild(this.indexPath_);
6101 };
6102 PathIndex.prototype.isDefinedOn = function (node) {
6103 return !node.getChild(this.indexPath_).isEmpty();
6104 };
6105 PathIndex.prototype.compare = function (a, b) {
6106 var aChild = this.extractChild(a.node);
6107 var bChild = this.extractChild(b.node);
6108 var indexCmp = aChild.compareTo(bChild);
6109 if (indexCmp === 0) {
6110 return nameCompare(a.name, b.name);
6111 }
6112 else {
6113 return indexCmp;
6114 }
6115 };
6116 PathIndex.prototype.makePost = function (indexValue, name) {
6117 var valueNode = nodeFromJSON(indexValue);
6118 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, valueNode);
6119 return new NamedNode(name, node);
6120 };
6121 PathIndex.prototype.maxPost = function () {
6122 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, MAX_NODE);
6123 return new NamedNode(MAX_NAME, node);
6124 };
6125 PathIndex.prototype.toString = function () {
6126 return pathSlice(this.indexPath_, 0).join('/');
6127 };
6128 return PathIndex;
6129}(Index));
6130
6131/**
6132 * @license
6133 * Copyright 2017 Google LLC
6134 *
6135 * Licensed under the Apache License, Version 2.0 (the "License");
6136 * you may not use this file except in compliance with the License.
6137 * You may obtain a copy of the License at
6138 *
6139 * http://www.apache.org/licenses/LICENSE-2.0
6140 *
6141 * Unless required by applicable law or agreed to in writing, software
6142 * distributed under the License is distributed on an "AS IS" BASIS,
6143 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6144 * See the License for the specific language governing permissions and
6145 * limitations under the License.
6146 */
6147var ValueIndex = /** @class */ (function (_super) {
6148 tslib.__extends(ValueIndex, _super);
6149 function ValueIndex() {
6150 return _super !== null && _super.apply(this, arguments) || this;
6151 }
6152 ValueIndex.prototype.compare = function (a, b) {
6153 var indexCmp = a.node.compareTo(b.node);
6154 if (indexCmp === 0) {
6155 return nameCompare(a.name, b.name);
6156 }
6157 else {
6158 return indexCmp;
6159 }
6160 };
6161 ValueIndex.prototype.isDefinedOn = function (node) {
6162 return true;
6163 };
6164 ValueIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
6165 return !oldNode.equals(newNode);
6166 };
6167 ValueIndex.prototype.minPost = function () {
6168 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6169 return NamedNode.MIN;
6170 };
6171 ValueIndex.prototype.maxPost = function () {
6172 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6173 return NamedNode.MAX;
6174 };
6175 ValueIndex.prototype.makePost = function (indexValue, name) {
6176 var valueNode = nodeFromJSON(indexValue);
6177 return new NamedNode(name, valueNode);
6178 };
6179 /**
6180 * @returns String representation for inclusion in a query spec
6181 */
6182 ValueIndex.prototype.toString = function () {
6183 return '.value';
6184 };
6185 return ValueIndex;
6186}(Index));
6187var VALUE_INDEX = new ValueIndex();
6188
6189/**
6190 * @license
6191 * Copyright 2017 Google LLC
6192 *
6193 * Licensed under the Apache License, Version 2.0 (the "License");
6194 * you may not use this file except in compliance with the License.
6195 * You may obtain a copy of the License at
6196 *
6197 * http://www.apache.org/licenses/LICENSE-2.0
6198 *
6199 * Unless required by applicable law or agreed to in writing, software
6200 * distributed under the License is distributed on an "AS IS" BASIS,
6201 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6202 * See the License for the specific language governing permissions and
6203 * limitations under the License.
6204 */
6205// Modeled after base64 web-safe chars, but ordered by ASCII.
6206var PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
6207var MIN_PUSH_CHAR = '-';
6208var MAX_PUSH_CHAR = 'z';
6209var MAX_KEY_LEN = 786;
6210/**
6211 * Fancy ID generator that creates 20-character string identifiers with the
6212 * following properties:
6213 *
6214 * 1. They're based on timestamp so that they sort *after* any existing ids.
6215 * 2. They contain 72-bits of random data after the timestamp so that IDs won't
6216 * collide with other clients' IDs.
6217 * 3. They sort *lexicographically* (so the timestamp is converted to characters
6218 * that will sort properly).
6219 * 4. They're monotonically increasing. Even if you generate more than one in
6220 * the same timestamp, the latter ones will sort after the former ones. We do
6221 * this by using the previous random bits but "incrementing" them by 1 (only
6222 * in the case of a timestamp collision).
6223 */
6224var nextPushId = (function () {
6225 // Timestamp of last push, used to prevent local collisions if you push twice
6226 // in one ms.
6227 var lastPushTime = 0;
6228 // We generate 72-bits of randomness which get turned into 12 characters and
6229 // appended to the timestamp to prevent collisions with other clients. We
6230 // store the last characters we generated because in the event of a collision,
6231 // we'll use those same characters except "incremented" by one.
6232 var lastRandChars = [];
6233 return function (now) {
6234 var duplicateTime = now === lastPushTime;
6235 lastPushTime = now;
6236 var i;
6237 var timeStampChars = new Array(8);
6238 for (i = 7; i >= 0; i--) {
6239 timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
6240 // NOTE: Can't use << here because javascript will convert to int and lose
6241 // the upper bits.
6242 now = Math.floor(now / 64);
6243 }
6244 util.assert(now === 0, 'Cannot push at time == 0');
6245 var id = timeStampChars.join('');
6246 if (!duplicateTime) {
6247 for (i = 0; i < 12; i++) {
6248 lastRandChars[i] = Math.floor(Math.random() * 64);
6249 }
6250 }
6251 else {
6252 // If the timestamp hasn't changed since last push, use the same random
6253 // number, except incremented by 1.
6254 for (i = 11; i >= 0 && lastRandChars[i] === 63; i--) {
6255 lastRandChars[i] = 0;
6256 }
6257 lastRandChars[i]++;
6258 }
6259 for (i = 0; i < 12; i++) {
6260 id += PUSH_CHARS.charAt(lastRandChars[i]);
6261 }
6262 util.assert(id.length === 20, 'nextPushId: Length should be 20.');
6263 return id;
6264 };
6265})();
6266var successor = function (key) {
6267 if (key === '' + INTEGER_32_MAX) {
6268 // See https://firebase.google.com/docs/database/web/lists-of-data#data-order
6269 return MIN_PUSH_CHAR;
6270 }
6271 var keyAsInt = tryParseInt(key);
6272 if (keyAsInt != null) {
6273 return '' + (keyAsInt + 1);
6274 }
6275 var next = new Array(key.length);
6276 for (var i_1 = 0; i_1 < next.length; i_1++) {
6277 next[i_1] = key.charAt(i_1);
6278 }
6279 if (next.length < MAX_KEY_LEN) {
6280 next.push(MIN_PUSH_CHAR);
6281 return next.join('');
6282 }
6283 var i = next.length - 1;
6284 while (i >= 0 && next[i] === MAX_PUSH_CHAR) {
6285 i--;
6286 }
6287 // `successor` was called on the largest possible key, so return the
6288 // MAX_NAME, which sorts larger than all keys.
6289 if (i === -1) {
6290 return MAX_NAME;
6291 }
6292 var source = next[i];
6293 var sourcePlusOne = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(source) + 1);
6294 next[i] = sourcePlusOne;
6295 return next.slice(0, i + 1).join('');
6296};
6297// `key` is assumed to be non-empty.
6298var predecessor = function (key) {
6299 if (key === '' + INTEGER_32_MIN) {
6300 return MIN_NAME;
6301 }
6302 var keyAsInt = tryParseInt(key);
6303 if (keyAsInt != null) {
6304 return '' + (keyAsInt - 1);
6305 }
6306 var next = new Array(key.length);
6307 for (var i = 0; i < next.length; i++) {
6308 next[i] = key.charAt(i);
6309 }
6310 // If `key` ends in `MIN_PUSH_CHAR`, the largest key lexicographically
6311 // smaller than `key`, is `key[0:key.length - 1]`. The next key smaller
6312 // than that, `predecessor(predecessor(key))`, is
6313 //
6314 // `key[0:key.length - 2] + (key[key.length - 1] - 1) + \
6315 // { MAX_PUSH_CHAR repeated MAX_KEY_LEN - (key.length - 1) times }
6316 //
6317 // analogous to increment/decrement for base-10 integers.
6318 //
6319 // This works because lexigographic comparison works character-by-character,
6320 // using length as a tie-breaker if one key is a prefix of the other.
6321 if (next[next.length - 1] === MIN_PUSH_CHAR) {
6322 if (next.length === 1) {
6323 // See https://firebase.google.com/docs/database/web/lists-of-data#orderbykey
6324 return '' + INTEGER_32_MAX;
6325 }
6326 delete next[next.length - 1];
6327 return next.join('');
6328 }
6329 // Replace the last character with it's immediate predecessor, and
6330 // fill the suffix of the key with MAX_PUSH_CHAR. This is the
6331 // lexicographically largest possible key smaller than `key`.
6332 next[next.length - 1] = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(next[next.length - 1]) - 1);
6333 return next.join('') + MAX_PUSH_CHAR.repeat(MAX_KEY_LEN - next.length);
6334};
6335
6336/**
6337 * @license
6338 * Copyright 2017 Google LLC
6339 *
6340 * Licensed under the Apache License, Version 2.0 (the "License");
6341 * you may not use this file except in compliance with the License.
6342 * You may obtain a copy of the License at
6343 *
6344 * http://www.apache.org/licenses/LICENSE-2.0
6345 *
6346 * Unless required by applicable law or agreed to in writing, software
6347 * distributed under the License is distributed on an "AS IS" BASIS,
6348 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6349 * See the License for the specific language governing permissions and
6350 * limitations under the License.
6351 */
6352function changeValue(snapshotNode) {
6353 return { type: "value" /* VALUE */, snapshotNode: snapshotNode };
6354}
6355function changeChildAdded(childName, snapshotNode) {
6356 return { type: "child_added" /* CHILD_ADDED */, snapshotNode: snapshotNode, childName: childName };
6357}
6358function changeChildRemoved(childName, snapshotNode) {
6359 return { type: "child_removed" /* CHILD_REMOVED */, snapshotNode: snapshotNode, childName: childName };
6360}
6361function changeChildChanged(childName, snapshotNode, oldSnap) {
6362 return {
6363 type: "child_changed" /* CHILD_CHANGED */,
6364 snapshotNode: snapshotNode,
6365 childName: childName,
6366 oldSnap: oldSnap
6367 };
6368}
6369function changeChildMoved(childName, snapshotNode) {
6370 return { type: "child_moved" /* CHILD_MOVED */, snapshotNode: snapshotNode, childName: childName };
6371}
6372
6373/**
6374 * @license
6375 * Copyright 2017 Google LLC
6376 *
6377 * Licensed under the Apache License, Version 2.0 (the "License");
6378 * you may not use this file except in compliance with the License.
6379 * You may obtain a copy of the License at
6380 *
6381 * http://www.apache.org/licenses/LICENSE-2.0
6382 *
6383 * Unless required by applicable law or agreed to in writing, software
6384 * distributed under the License is distributed on an "AS IS" BASIS,
6385 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6386 * See the License for the specific language governing permissions and
6387 * limitations under the License.
6388 */
6389/**
6390 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
6391 */
6392var IndexedFilter = /** @class */ (function () {
6393 function IndexedFilter(index_) {
6394 this.index_ = index_;
6395 }
6396 IndexedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6397 util.assert(snap.isIndexed(this.index_), 'A node must be indexed if only a child is updated');
6398 var oldChild = snap.getImmediateChild(key);
6399 // Check if anything actually changed.
6400 if (oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))) {
6401 // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
6402 // In this case, affectedPath will appear null in both the old and new snapshots. So we need
6403 // to avoid treating these cases as "nothing changed."
6404 if (oldChild.isEmpty() === newChild.isEmpty()) {
6405 // Nothing changed.
6406 // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
6407 //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
6408 return snap;
6409 }
6410 }
6411 if (optChangeAccumulator != null) {
6412 if (newChild.isEmpty()) {
6413 if (snap.hasChild(key)) {
6414 optChangeAccumulator.trackChildChange(changeChildRemoved(key, oldChild));
6415 }
6416 else {
6417 util.assert(snap.isLeafNode(), 'A child remove without an old child only makes sense on a leaf node');
6418 }
6419 }
6420 else if (oldChild.isEmpty()) {
6421 optChangeAccumulator.trackChildChange(changeChildAdded(key, newChild));
6422 }
6423 else {
6424 optChangeAccumulator.trackChildChange(changeChildChanged(key, newChild, oldChild));
6425 }
6426 }
6427 if (snap.isLeafNode() && newChild.isEmpty()) {
6428 return snap;
6429 }
6430 else {
6431 // Make sure the node is indexed
6432 return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
6433 }
6434 };
6435 IndexedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6436 if (optChangeAccumulator != null) {
6437 if (!oldSnap.isLeafNode()) {
6438 oldSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6439 if (!newSnap.hasChild(key)) {
6440 optChangeAccumulator.trackChildChange(changeChildRemoved(key, childNode));
6441 }
6442 });
6443 }
6444 if (!newSnap.isLeafNode()) {
6445 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6446 if (oldSnap.hasChild(key)) {
6447 var oldChild = oldSnap.getImmediateChild(key);
6448 if (!oldChild.equals(childNode)) {
6449 optChangeAccumulator.trackChildChange(changeChildChanged(key, childNode, oldChild));
6450 }
6451 }
6452 else {
6453 optChangeAccumulator.trackChildChange(changeChildAdded(key, childNode));
6454 }
6455 });
6456 }
6457 }
6458 return newSnap.withIndex(this.index_);
6459 };
6460 IndexedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6461 if (oldSnap.isEmpty()) {
6462 return ChildrenNode.EMPTY_NODE;
6463 }
6464 else {
6465 return oldSnap.updatePriority(newPriority);
6466 }
6467 };
6468 IndexedFilter.prototype.filtersNodes = function () {
6469 return false;
6470 };
6471 IndexedFilter.prototype.getIndexedFilter = function () {
6472 return this;
6473 };
6474 IndexedFilter.prototype.getIndex = function () {
6475 return this.index_;
6476 };
6477 return IndexedFilter;
6478}());
6479
6480/**
6481 * @license
6482 * Copyright 2017 Google LLC
6483 *
6484 * Licensed under the Apache License, Version 2.0 (the "License");
6485 * you may not use this file except in compliance with the License.
6486 * You may obtain a copy of the License at
6487 *
6488 * http://www.apache.org/licenses/LICENSE-2.0
6489 *
6490 * Unless required by applicable law or agreed to in writing, software
6491 * distributed under the License is distributed on an "AS IS" BASIS,
6492 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6493 * See the License for the specific language governing permissions and
6494 * limitations under the License.
6495 */
6496/**
6497 * Filters nodes by range and uses an IndexFilter to track any changes after filtering the node
6498 */
6499var RangedFilter = /** @class */ (function () {
6500 function RangedFilter(params) {
6501 this.indexedFilter_ = new IndexedFilter(params.getIndex());
6502 this.index_ = params.getIndex();
6503 this.startPost_ = RangedFilter.getStartPost_(params);
6504 this.endPost_ = RangedFilter.getEndPost_(params);
6505 }
6506 RangedFilter.prototype.getStartPost = function () {
6507 return this.startPost_;
6508 };
6509 RangedFilter.prototype.getEndPost = function () {
6510 return this.endPost_;
6511 };
6512 RangedFilter.prototype.matches = function (node) {
6513 return (this.index_.compare(this.getStartPost(), node) <= 0 &&
6514 this.index_.compare(node, this.getEndPost()) <= 0);
6515 };
6516 RangedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6517 if (!this.matches(new NamedNode(key, newChild))) {
6518 newChild = ChildrenNode.EMPTY_NODE;
6519 }
6520 return this.indexedFilter_.updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6521 };
6522 RangedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6523 if (newSnap.isLeafNode()) {
6524 // Make sure we have a children node with the correct index, not a leaf node;
6525 newSnap = ChildrenNode.EMPTY_NODE;
6526 }
6527 var filtered = newSnap.withIndex(this.index_);
6528 // Don't support priorities on queries
6529 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6530 var self = this;
6531 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6532 if (!self.matches(new NamedNode(key, childNode))) {
6533 filtered = filtered.updateImmediateChild(key, ChildrenNode.EMPTY_NODE);
6534 }
6535 });
6536 return this.indexedFilter_.updateFullNode(oldSnap, filtered, optChangeAccumulator);
6537 };
6538 RangedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6539 // Don't support priorities on queries
6540 return oldSnap;
6541 };
6542 RangedFilter.prototype.filtersNodes = function () {
6543 return true;
6544 };
6545 RangedFilter.prototype.getIndexedFilter = function () {
6546 return this.indexedFilter_;
6547 };
6548 RangedFilter.prototype.getIndex = function () {
6549 return this.index_;
6550 };
6551 RangedFilter.getStartPost_ = function (params) {
6552 if (params.hasStart()) {
6553 var startName = params.getIndexStartName();
6554 return params.getIndex().makePost(params.getIndexStartValue(), startName);
6555 }
6556 else {
6557 return params.getIndex().minPost();
6558 }
6559 };
6560 RangedFilter.getEndPost_ = function (params) {
6561 if (params.hasEnd()) {
6562 var endName = params.getIndexEndName();
6563 return params.getIndex().makePost(params.getIndexEndValue(), endName);
6564 }
6565 else {
6566 return params.getIndex().maxPost();
6567 }
6568 };
6569 return RangedFilter;
6570}());
6571
6572/**
6573 * @license
6574 * Copyright 2017 Google LLC
6575 *
6576 * Licensed under the Apache License, Version 2.0 (the "License");
6577 * you may not use this file except in compliance with the License.
6578 * You may obtain a copy of the License at
6579 *
6580 * http://www.apache.org/licenses/LICENSE-2.0
6581 *
6582 * Unless required by applicable law or agreed to in writing, software
6583 * distributed under the License is distributed on an "AS IS" BASIS,
6584 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6585 * See the License for the specific language governing permissions and
6586 * limitations under the License.
6587 */
6588/**
6589 * Applies a limit and a range to a node and uses RangedFilter to do the heavy lifting where possible
6590 */
6591var LimitedFilter = /** @class */ (function () {
6592 function LimitedFilter(params) {
6593 this.rangedFilter_ = new RangedFilter(params);
6594 this.index_ = params.getIndex();
6595 this.limit_ = params.getLimit();
6596 this.reverse_ = !params.isViewFromLeft();
6597 }
6598 LimitedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6599 if (!this.rangedFilter_.matches(new NamedNode(key, newChild))) {
6600 newChild = ChildrenNode.EMPTY_NODE;
6601 }
6602 if (snap.getImmediateChild(key).equals(newChild)) {
6603 // No change
6604 return snap;
6605 }
6606 else if (snap.numChildren() < this.limit_) {
6607 return this.rangedFilter_
6608 .getIndexedFilter()
6609 .updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6610 }
6611 else {
6612 return this.fullLimitUpdateChild_(snap, key, newChild, source, optChangeAccumulator);
6613 }
6614 };
6615 LimitedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6616 var filtered;
6617 if (newSnap.isLeafNode() || newSnap.isEmpty()) {
6618 // Make sure we have a children node with the correct index, not a leaf node;
6619 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6620 }
6621 else {
6622 if (this.limit_ * 2 < newSnap.numChildren() &&
6623 newSnap.isIndexed(this.index_)) {
6624 // Easier to build up a snapshot, since what we're given has more than twice the elements we want
6625 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6626 // anchor to the startPost, endPost, or last element as appropriate
6627 var iterator = void 0;
6628 if (this.reverse_) {
6629 iterator = newSnap.getReverseIteratorFrom(this.rangedFilter_.getEndPost(), this.index_);
6630 }
6631 else {
6632 iterator = newSnap.getIteratorFrom(this.rangedFilter_.getStartPost(), this.index_);
6633 }
6634 var count = 0;
6635 while (iterator.hasNext() && count < this.limit_) {
6636 var next = iterator.getNext();
6637 var inRange = void 0;
6638 if (this.reverse_) {
6639 inRange =
6640 this.index_.compare(this.rangedFilter_.getStartPost(), next) <= 0;
6641 }
6642 else {
6643 inRange =
6644 this.index_.compare(next, this.rangedFilter_.getEndPost()) <= 0;
6645 }
6646 if (inRange) {
6647 filtered = filtered.updateImmediateChild(next.name, next.node);
6648 count++;
6649 }
6650 else {
6651 // if we have reached the end post, we cannot keep adding elemments
6652 break;
6653 }
6654 }
6655 }
6656 else {
6657 // The snap contains less than twice the limit. Faster to delete from the snap than build up a new one
6658 filtered = newSnap.withIndex(this.index_);
6659 // Don't support priorities on queries
6660 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6661 var startPost = void 0;
6662 var endPost = void 0;
6663 var cmp = void 0;
6664 var iterator = void 0;
6665 if (this.reverse_) {
6666 iterator = filtered.getReverseIterator(this.index_);
6667 startPost = this.rangedFilter_.getEndPost();
6668 endPost = this.rangedFilter_.getStartPost();
6669 var indexCompare_1 = this.index_.getCompare();
6670 cmp = function (a, b) { return indexCompare_1(b, a); };
6671 }
6672 else {
6673 iterator = filtered.getIterator(this.index_);
6674 startPost = this.rangedFilter_.getStartPost();
6675 endPost = this.rangedFilter_.getEndPost();
6676 cmp = this.index_.getCompare();
6677 }
6678 var count = 0;
6679 var foundStartPost = false;
6680 while (iterator.hasNext()) {
6681 var next = iterator.getNext();
6682 if (!foundStartPost && cmp(startPost, next) <= 0) {
6683 // start adding
6684 foundStartPost = true;
6685 }
6686 var inRange = foundStartPost && count < this.limit_ && cmp(next, endPost) <= 0;
6687 if (inRange) {
6688 count++;
6689 }
6690 else {
6691 filtered = filtered.updateImmediateChild(next.name, ChildrenNode.EMPTY_NODE);
6692 }
6693 }
6694 }
6695 }
6696 return this.rangedFilter_
6697 .getIndexedFilter()
6698 .updateFullNode(oldSnap, filtered, optChangeAccumulator);
6699 };
6700 LimitedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6701 // Don't support priorities on queries
6702 return oldSnap;
6703 };
6704 LimitedFilter.prototype.filtersNodes = function () {
6705 return true;
6706 };
6707 LimitedFilter.prototype.getIndexedFilter = function () {
6708 return this.rangedFilter_.getIndexedFilter();
6709 };
6710 LimitedFilter.prototype.getIndex = function () {
6711 return this.index_;
6712 };
6713 LimitedFilter.prototype.fullLimitUpdateChild_ = function (snap, childKey, childSnap, source, changeAccumulator) {
6714 // TODO: rename all cache stuff etc to general snap terminology
6715 var cmp;
6716 if (this.reverse_) {
6717 var indexCmp_1 = this.index_.getCompare();
6718 cmp = function (a, b) { return indexCmp_1(b, a); };
6719 }
6720 else {
6721 cmp = this.index_.getCompare();
6722 }
6723 var oldEventCache = snap;
6724 util.assert(oldEventCache.numChildren() === this.limit_, '');
6725 var newChildNamedNode = new NamedNode(childKey, childSnap);
6726 var windowBoundary = this.reverse_
6727 ? oldEventCache.getFirstChild(this.index_)
6728 : oldEventCache.getLastChild(this.index_);
6729 var inRange = this.rangedFilter_.matches(newChildNamedNode);
6730 if (oldEventCache.hasChild(childKey)) {
6731 var oldChildSnap = oldEventCache.getImmediateChild(childKey);
6732 var nextChild = source.getChildAfterChild(this.index_, windowBoundary, this.reverse_);
6733 while (nextChild != null &&
6734 (nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))) {
6735 // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
6736 // been applied to the limited filter yet. Ignore this next child which will be updated later in
6737 // the limited filter...
6738 nextChild = source.getChildAfterChild(this.index_, nextChild, this.reverse_);
6739 }
6740 var compareNext = nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
6741 var remainsInWindow = inRange && !childSnap.isEmpty() && compareNext >= 0;
6742 if (remainsInWindow) {
6743 if (changeAccumulator != null) {
6744 changeAccumulator.trackChildChange(changeChildChanged(childKey, childSnap, oldChildSnap));
6745 }
6746 return oldEventCache.updateImmediateChild(childKey, childSnap);
6747 }
6748 else {
6749 if (changeAccumulator != null) {
6750 changeAccumulator.trackChildChange(changeChildRemoved(childKey, oldChildSnap));
6751 }
6752 var newEventCache = oldEventCache.updateImmediateChild(childKey, ChildrenNode.EMPTY_NODE);
6753 var nextChildInRange = nextChild != null && this.rangedFilter_.matches(nextChild);
6754 if (nextChildInRange) {
6755 if (changeAccumulator != null) {
6756 changeAccumulator.trackChildChange(changeChildAdded(nextChild.name, nextChild.node));
6757 }
6758 return newEventCache.updateImmediateChild(nextChild.name, nextChild.node);
6759 }
6760 else {
6761 return newEventCache;
6762 }
6763 }
6764 }
6765 else if (childSnap.isEmpty()) {
6766 // we're deleting a node, but it was not in the window, so ignore it
6767 return snap;
6768 }
6769 else if (inRange) {
6770 if (cmp(windowBoundary, newChildNamedNode) >= 0) {
6771 if (changeAccumulator != null) {
6772 changeAccumulator.trackChildChange(changeChildRemoved(windowBoundary.name, windowBoundary.node));
6773 changeAccumulator.trackChildChange(changeChildAdded(childKey, childSnap));
6774 }
6775 return oldEventCache
6776 .updateImmediateChild(childKey, childSnap)
6777 .updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
6778 }
6779 else {
6780 return snap;
6781 }
6782 }
6783 else {
6784 return snap;
6785 }
6786 };
6787 return LimitedFilter;
6788}());
6789
6790/**
6791 * @license
6792 * Copyright 2017 Google LLC
6793 *
6794 * Licensed under the Apache License, Version 2.0 (the "License");
6795 * you may not use this file except in compliance with the License.
6796 * You may obtain a copy of the License at
6797 *
6798 * http://www.apache.org/licenses/LICENSE-2.0
6799 *
6800 * Unless required by applicable law or agreed to in writing, software
6801 * distributed under the License is distributed on an "AS IS" BASIS,
6802 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6803 * See the License for the specific language governing permissions and
6804 * limitations under the License.
6805 */
6806/**
6807 * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a
6808 * range to be returned for a particular location. It is assumed that validation of parameters is done at the
6809 * user-facing API level, so it is not done here.
6810 *
6811 * @internal
6812 */
6813var QueryParams = /** @class */ (function () {
6814 function QueryParams() {
6815 this.limitSet_ = false;
6816 this.startSet_ = false;
6817 this.startNameSet_ = false;
6818 this.startAfterSet_ = false;
6819 this.endSet_ = false;
6820 this.endNameSet_ = false;
6821 this.endBeforeSet_ = false;
6822 this.limit_ = 0;
6823 this.viewFrom_ = '';
6824 this.indexStartValue_ = null;
6825 this.indexStartName_ = '';
6826 this.indexEndValue_ = null;
6827 this.indexEndName_ = '';
6828 this.index_ = PRIORITY_INDEX;
6829 }
6830 QueryParams.prototype.hasStart = function () {
6831 return this.startSet_;
6832 };
6833 QueryParams.prototype.hasStartAfter = function () {
6834 return this.startAfterSet_;
6835 };
6836 QueryParams.prototype.hasEndBefore = function () {
6837 return this.endBeforeSet_;
6838 };
6839 /**
6840 * @returns True if it would return from left.
6841 */
6842 QueryParams.prototype.isViewFromLeft = function () {
6843 if (this.viewFrom_ === '') {
6844 // limit(), rather than limitToFirst or limitToLast was called.
6845 // This means that only one of startSet_ and endSet_ is true. Use them
6846 // to calculate which side of the view to anchor to. If neither is set,
6847 // anchor to the end.
6848 return this.startSet_;
6849 }
6850 else {
6851 return this.viewFrom_ === "l" /* VIEW_FROM_LEFT */;
6852 }
6853 };
6854 /**
6855 * Only valid to call if hasStart() returns true
6856 */
6857 QueryParams.prototype.getIndexStartValue = function () {
6858 util.assert(this.startSet_, 'Only valid if start has been set');
6859 return this.indexStartValue_;
6860 };
6861 /**
6862 * Only valid to call if hasStart() returns true.
6863 * Returns the starting key name for the range defined by these query parameters
6864 */
6865 QueryParams.prototype.getIndexStartName = function () {
6866 util.assert(this.startSet_, 'Only valid if start has been set');
6867 if (this.startNameSet_) {
6868 return this.indexStartName_;
6869 }
6870 else {
6871 return MIN_NAME;
6872 }
6873 };
6874 QueryParams.prototype.hasEnd = function () {
6875 return this.endSet_;
6876 };
6877 /**
6878 * Only valid to call if hasEnd() returns true.
6879 */
6880 QueryParams.prototype.getIndexEndValue = function () {
6881 util.assert(this.endSet_, 'Only valid if end has been set');
6882 return this.indexEndValue_;
6883 };
6884 /**
6885 * Only valid to call if hasEnd() returns true.
6886 * Returns the end key name for the range defined by these query parameters
6887 */
6888 QueryParams.prototype.getIndexEndName = function () {
6889 util.assert(this.endSet_, 'Only valid if end has been set');
6890 if (this.endNameSet_) {
6891 return this.indexEndName_;
6892 }
6893 else {
6894 return MAX_NAME;
6895 }
6896 };
6897 QueryParams.prototype.hasLimit = function () {
6898 return this.limitSet_;
6899 };
6900 /**
6901 * @returns True if a limit has been set and it has been explicitly anchored
6902 */
6903 QueryParams.prototype.hasAnchoredLimit = function () {
6904 return this.limitSet_ && this.viewFrom_ !== '';
6905 };
6906 /**
6907 * Only valid to call if hasLimit() returns true
6908 */
6909 QueryParams.prototype.getLimit = function () {
6910 util.assert(this.limitSet_, 'Only valid if limit has been set');
6911 return this.limit_;
6912 };
6913 QueryParams.prototype.getIndex = function () {
6914 return this.index_;
6915 };
6916 QueryParams.prototype.loadsAllData = function () {
6917 return !(this.startSet_ || this.endSet_ || this.limitSet_);
6918 };
6919 QueryParams.prototype.isDefault = function () {
6920 return this.loadsAllData() && this.index_ === PRIORITY_INDEX;
6921 };
6922 QueryParams.prototype.copy = function () {
6923 var copy = new QueryParams();
6924 copy.limitSet_ = this.limitSet_;
6925 copy.limit_ = this.limit_;
6926 copy.startSet_ = this.startSet_;
6927 copy.indexStartValue_ = this.indexStartValue_;
6928 copy.startNameSet_ = this.startNameSet_;
6929 copy.indexStartName_ = this.indexStartName_;
6930 copy.endSet_ = this.endSet_;
6931 copy.indexEndValue_ = this.indexEndValue_;
6932 copy.endNameSet_ = this.endNameSet_;
6933 copy.indexEndName_ = this.indexEndName_;
6934 copy.index_ = this.index_;
6935 copy.viewFrom_ = this.viewFrom_;
6936 return copy;
6937 };
6938 return QueryParams;
6939}());
6940function queryParamsGetNodeFilter(queryParams) {
6941 if (queryParams.loadsAllData()) {
6942 return new IndexedFilter(queryParams.getIndex());
6943 }
6944 else if (queryParams.hasLimit()) {
6945 return new LimitedFilter(queryParams);
6946 }
6947 else {
6948 return new RangedFilter(queryParams);
6949 }
6950}
6951function queryParamsLimitToFirst(queryParams, newLimit) {
6952 var newParams = queryParams.copy();
6953 newParams.limitSet_ = true;
6954 newParams.limit_ = newLimit;
6955 newParams.viewFrom_ = "l" /* VIEW_FROM_LEFT */;
6956 return newParams;
6957}
6958function queryParamsLimitToLast(queryParams, newLimit) {
6959 var newParams = queryParams.copy();
6960 newParams.limitSet_ = true;
6961 newParams.limit_ = newLimit;
6962 newParams.viewFrom_ = "r" /* VIEW_FROM_RIGHT */;
6963 return newParams;
6964}
6965function queryParamsStartAt(queryParams, indexValue, key) {
6966 var newParams = queryParams.copy();
6967 newParams.startSet_ = true;
6968 if (indexValue === undefined) {
6969 indexValue = null;
6970 }
6971 newParams.indexStartValue_ = indexValue;
6972 if (key != null) {
6973 newParams.startNameSet_ = true;
6974 newParams.indexStartName_ = key;
6975 }
6976 else {
6977 newParams.startNameSet_ = false;
6978 newParams.indexStartName_ = '';
6979 }
6980 return newParams;
6981}
6982function queryParamsStartAfter(queryParams, indexValue, key) {
6983 var params;
6984 if (queryParams.index_ === KEY_INDEX) {
6985 if (typeof indexValue === 'string') {
6986 indexValue = successor(indexValue);
6987 }
6988 params = queryParamsStartAt(queryParams, indexValue, key);
6989 }
6990 else {
6991 var childKey = void 0;
6992 if (key == null) {
6993 childKey = MAX_NAME;
6994 }
6995 else {
6996 childKey = successor(key);
6997 }
6998 params = queryParamsStartAt(queryParams, indexValue, childKey);
6999 }
7000 params.startAfterSet_ = true;
7001 return params;
7002}
7003function queryParamsEndAt(queryParams, indexValue, key) {
7004 var newParams = queryParams.copy();
7005 newParams.endSet_ = true;
7006 if (indexValue === undefined) {
7007 indexValue = null;
7008 }
7009 newParams.indexEndValue_ = indexValue;
7010 if (key !== undefined) {
7011 newParams.endNameSet_ = true;
7012 newParams.indexEndName_ = key;
7013 }
7014 else {
7015 newParams.endNameSet_ = false;
7016 newParams.indexEndName_ = '';
7017 }
7018 return newParams;
7019}
7020function queryParamsEndBefore(queryParams, indexValue, key) {
7021 var childKey;
7022 var params;
7023 if (queryParams.index_ === KEY_INDEX) {
7024 if (typeof indexValue === 'string') {
7025 indexValue = predecessor(indexValue);
7026 }
7027 params = queryParamsEndAt(queryParams, indexValue, key);
7028 }
7029 else {
7030 if (key == null) {
7031 childKey = MIN_NAME;
7032 }
7033 else {
7034 childKey = predecessor(key);
7035 }
7036 params = queryParamsEndAt(queryParams, indexValue, childKey);
7037 }
7038 params.endBeforeSet_ = true;
7039 return params;
7040}
7041function queryParamsOrderBy(queryParams, index) {
7042 var newParams = queryParams.copy();
7043 newParams.index_ = index;
7044 return newParams;
7045}
7046/**
7047 * Returns a set of REST query string parameters representing this query.
7048 *
7049 * @returns query string parameters
7050 */
7051function queryParamsToRestQueryStringParameters(queryParams) {
7052 var qs = {};
7053 if (queryParams.isDefault()) {
7054 return qs;
7055 }
7056 var orderBy;
7057 if (queryParams.index_ === PRIORITY_INDEX) {
7058 orderBy = "$priority" /* PRIORITY_INDEX */;
7059 }
7060 else if (queryParams.index_ === VALUE_INDEX) {
7061 orderBy = "$value" /* VALUE_INDEX */;
7062 }
7063 else if (queryParams.index_ === KEY_INDEX) {
7064 orderBy = "$key" /* KEY_INDEX */;
7065 }
7066 else {
7067 util.assert(queryParams.index_ instanceof PathIndex, 'Unrecognized index type!');
7068 orderBy = queryParams.index_.toString();
7069 }
7070 qs["orderBy" /* ORDER_BY */] = util.stringify(orderBy);
7071 if (queryParams.startSet_) {
7072 qs["startAt" /* START_AT */] = util.stringify(queryParams.indexStartValue_);
7073 if (queryParams.startNameSet_) {
7074 qs["startAt" /* START_AT */] +=
7075 ',' + util.stringify(queryParams.indexStartName_);
7076 }
7077 }
7078 if (queryParams.endSet_) {
7079 qs["endAt" /* END_AT */] = util.stringify(queryParams.indexEndValue_);
7080 if (queryParams.endNameSet_) {
7081 qs["endAt" /* END_AT */] +=
7082 ',' + util.stringify(queryParams.indexEndName_);
7083 }
7084 }
7085 if (queryParams.limitSet_) {
7086 if (queryParams.isViewFromLeft()) {
7087 qs["limitToFirst" /* LIMIT_TO_FIRST */] = queryParams.limit_;
7088 }
7089 else {
7090 qs["limitToLast" /* LIMIT_TO_LAST */] = queryParams.limit_;
7091 }
7092 }
7093 return qs;
7094}
7095function queryParamsGetQueryObject(queryParams) {
7096 var obj = {};
7097 if (queryParams.startSet_) {
7098 obj["sp" /* INDEX_START_VALUE */] =
7099 queryParams.indexStartValue_;
7100 if (queryParams.startNameSet_) {
7101 obj["sn" /* INDEX_START_NAME */] =
7102 queryParams.indexStartName_;
7103 }
7104 }
7105 if (queryParams.endSet_) {
7106 obj["ep" /* INDEX_END_VALUE */] = queryParams.indexEndValue_;
7107 if (queryParams.endNameSet_) {
7108 obj["en" /* INDEX_END_NAME */] = queryParams.indexEndName_;
7109 }
7110 }
7111 if (queryParams.limitSet_) {
7112 obj["l" /* LIMIT */] = queryParams.limit_;
7113 var viewFrom = queryParams.viewFrom_;
7114 if (viewFrom === '') {
7115 if (queryParams.isViewFromLeft()) {
7116 viewFrom = "l" /* VIEW_FROM_LEFT */;
7117 }
7118 else {
7119 viewFrom = "r" /* VIEW_FROM_RIGHT */;
7120 }
7121 }
7122 obj["vf" /* VIEW_FROM */] = viewFrom;
7123 }
7124 // For now, priority index is the default, so we only specify if it's some other index
7125 if (queryParams.index_ !== PRIORITY_INDEX) {
7126 obj["i" /* INDEX */] = queryParams.index_.toString();
7127 }
7128 return obj;
7129}
7130
7131/**
7132 * @license
7133 * Copyright 2017 Google LLC
7134 *
7135 * Licensed under the Apache License, Version 2.0 (the "License");
7136 * you may not use this file except in compliance with the License.
7137 * You may obtain a copy of the License at
7138 *
7139 * http://www.apache.org/licenses/LICENSE-2.0
7140 *
7141 * Unless required by applicable law or agreed to in writing, software
7142 * distributed under the License is distributed on an "AS IS" BASIS,
7143 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7144 * See the License for the specific language governing permissions and
7145 * limitations under the License.
7146 */
7147/**
7148 * An implementation of ServerActions that communicates with the server via REST requests.
7149 * This is mostly useful for compatibility with crawlers, where we don't want to spin up a full
7150 * persistent connection (using WebSockets or long-polling)
7151 */
7152var ReadonlyRestClient = /** @class */ (function (_super) {
7153 tslib.__extends(ReadonlyRestClient, _super);
7154 /**
7155 * @param repoInfo_ - Data about the namespace we are connecting to
7156 * @param onDataUpdate_ - A callback for new data from the server
7157 */
7158 function ReadonlyRestClient(repoInfo_, onDataUpdate_, authTokenProvider_, appCheckTokenProvider_) {
7159 var _this = _super.call(this) || this;
7160 _this.repoInfo_ = repoInfo_;
7161 _this.onDataUpdate_ = onDataUpdate_;
7162 _this.authTokenProvider_ = authTokenProvider_;
7163 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
7164 /** @private {function(...[*])} */
7165 _this.log_ = logWrapper('p:rest:');
7166 /**
7167 * We don't actually need to track listens, except to prevent us calling an onComplete for a listen
7168 * that's been removed. :-/
7169 */
7170 _this.listens_ = {};
7171 return _this;
7172 }
7173 ReadonlyRestClient.prototype.reportStats = function (stats) {
7174 throw new Error('Method not implemented.');
7175 };
7176 ReadonlyRestClient.getListenId_ = function (query, tag) {
7177 if (tag !== undefined) {
7178 return 'tag$' + tag;
7179 }
7180 else {
7181 util.assert(query._queryParams.isDefault(), "should have a tag if it's not a default query.");
7182 return query._path.toString();
7183 }
7184 };
7185 /** @inheritDoc */
7186 ReadonlyRestClient.prototype.listen = function (query, currentHashFn, tag, onComplete) {
7187 var _this = this;
7188 var pathString = query._path.toString();
7189 this.log_('Listen called for ' + pathString + ' ' + query._queryIdentifier);
7190 // Mark this listener so we can tell if it's removed.
7191 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7192 var thisListen = {};
7193 this.listens_[listenId] = thisListen;
7194 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7195 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7196 var data = result;
7197 if (error === 404) {
7198 data = null;
7199 error = null;
7200 }
7201 if (error === null) {
7202 _this.onDataUpdate_(pathString, data, /*isMerge=*/ false, tag);
7203 }
7204 if (util.safeGet(_this.listens_, listenId) === thisListen) {
7205 var status_1;
7206 if (!error) {
7207 status_1 = 'ok';
7208 }
7209 else if (error === 401) {
7210 status_1 = 'permission_denied';
7211 }
7212 else {
7213 status_1 = 'rest_error:' + error;
7214 }
7215 onComplete(status_1, null);
7216 }
7217 });
7218 };
7219 /** @inheritDoc */
7220 ReadonlyRestClient.prototype.unlisten = function (query, tag) {
7221 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7222 delete this.listens_[listenId];
7223 };
7224 ReadonlyRestClient.prototype.get = function (query) {
7225 var _this = this;
7226 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7227 var pathString = query._path.toString();
7228 var deferred = new util.Deferred();
7229 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7230 var data = result;
7231 if (error === 404) {
7232 data = null;
7233 error = null;
7234 }
7235 if (error === null) {
7236 _this.onDataUpdate_(pathString, data,
7237 /*isMerge=*/ false,
7238 /*tag=*/ null);
7239 deferred.resolve(data);
7240 }
7241 else {
7242 deferred.reject(new Error(data));
7243 }
7244 });
7245 return deferred.promise;
7246 };
7247 /** @inheritDoc */
7248 ReadonlyRestClient.prototype.refreshAuthToken = function (token) {
7249 // no-op since we just always call getToken.
7250 };
7251 /**
7252 * Performs a REST request to the given path, with the provided query string parameters,
7253 * and any auth credentials we have.
7254 */
7255 ReadonlyRestClient.prototype.restRequest_ = function (pathString, queryStringParameters, callback) {
7256 var _this = this;
7257 if (queryStringParameters === void 0) { queryStringParameters = {}; }
7258 queryStringParameters['format'] = 'export';
7259 return Promise.all([
7260 this.authTokenProvider_.getToken(/*forceRefresh=*/ false),
7261 this.appCheckTokenProvider_.getToken(/*forceRefresh=*/ false)
7262 ]).then(function (_a) {
7263 var _b = tslib.__read(_a, 2), authToken = _b[0], appCheckToken = _b[1];
7264 if (authToken && authToken.accessToken) {
7265 queryStringParameters['auth'] = authToken.accessToken;
7266 }
7267 if (appCheckToken && appCheckToken.token) {
7268 queryStringParameters['ac'] = appCheckToken.token;
7269 }
7270 var url = (_this.repoInfo_.secure ? 'https://' : 'http://') +
7271 _this.repoInfo_.host +
7272 pathString +
7273 '?' +
7274 'ns=' +
7275 _this.repoInfo_.namespace +
7276 util.querystring(queryStringParameters);
7277 _this.log_('Sending REST request for ' + url);
7278 var xhr = new XMLHttpRequest();
7279 xhr.onreadystatechange = function () {
7280 if (callback && xhr.readyState === 4) {
7281 _this.log_('REST Response for ' + url + ' received. status:', xhr.status, 'response:', xhr.responseText);
7282 var res = null;
7283 if (xhr.status >= 200 && xhr.status < 300) {
7284 try {
7285 res = util.jsonEval(xhr.responseText);
7286 }
7287 catch (e) {
7288 warn('Failed to parse JSON response for ' +
7289 url +
7290 ': ' +
7291 xhr.responseText);
7292 }
7293 callback(null, res);
7294 }
7295 else {
7296 // 401 and 404 are expected.
7297 if (xhr.status !== 401 && xhr.status !== 404) {
7298 warn('Got unsuccessful REST response for ' +
7299 url +
7300 ' Status: ' +
7301 xhr.status);
7302 }
7303 callback(xhr.status);
7304 }
7305 callback = null;
7306 }
7307 };
7308 xhr.open('GET', url, /*asynchronous=*/ true);
7309 xhr.send();
7310 });
7311 };
7312 return ReadonlyRestClient;
7313}(ServerActions));
7314
7315/**
7316 * @license
7317 * Copyright 2017 Google LLC
7318 *
7319 * Licensed under the Apache License, Version 2.0 (the "License");
7320 * you may not use this file except in compliance with the License.
7321 * You may obtain a copy of the License at
7322 *
7323 * http://www.apache.org/licenses/LICENSE-2.0
7324 *
7325 * Unless required by applicable law or agreed to in writing, software
7326 * distributed under the License is distributed on an "AS IS" BASIS,
7327 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7328 * See the License for the specific language governing permissions and
7329 * limitations under the License.
7330 */
7331/**
7332 * Mutable object which basically just stores a reference to the "latest" immutable snapshot.
7333 */
7334var SnapshotHolder = /** @class */ (function () {
7335 function SnapshotHolder() {
7336 this.rootNode_ = ChildrenNode.EMPTY_NODE;
7337 }
7338 SnapshotHolder.prototype.getNode = function (path) {
7339 return this.rootNode_.getChild(path);
7340 };
7341 SnapshotHolder.prototype.updateSnapshot = function (path, newSnapshotNode) {
7342 this.rootNode_ = this.rootNode_.updateChild(path, newSnapshotNode);
7343 };
7344 return SnapshotHolder;
7345}());
7346
7347/**
7348 * @license
7349 * Copyright 2017 Google LLC
7350 *
7351 * Licensed under the Apache License, Version 2.0 (the "License");
7352 * you may not use this file except in compliance with the License.
7353 * You may obtain a copy of the License at
7354 *
7355 * http://www.apache.org/licenses/LICENSE-2.0
7356 *
7357 * Unless required by applicable law or agreed to in writing, software
7358 * distributed under the License is distributed on an "AS IS" BASIS,
7359 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7360 * See the License for the specific language governing permissions and
7361 * limitations under the License.
7362 */
7363function newSparseSnapshotTree() {
7364 return {
7365 value: null,
7366 children: new Map()
7367 };
7368}
7369/**
7370 * Stores the given node at the specified path. If there is already a node
7371 * at a shallower path, it merges the new data into that snapshot node.
7372 *
7373 * @param path - Path to look up snapshot for.
7374 * @param data - The new data, or null.
7375 */
7376function sparseSnapshotTreeRemember(sparseSnapshotTree, path, data) {
7377 if (pathIsEmpty(path)) {
7378 sparseSnapshotTree.value = data;
7379 sparseSnapshotTree.children.clear();
7380 }
7381 else if (sparseSnapshotTree.value !== null) {
7382 sparseSnapshotTree.value = sparseSnapshotTree.value.updateChild(path, data);
7383 }
7384 else {
7385 var childKey = pathGetFront(path);
7386 if (!sparseSnapshotTree.children.has(childKey)) {
7387 sparseSnapshotTree.children.set(childKey, newSparseSnapshotTree());
7388 }
7389 var child = sparseSnapshotTree.children.get(childKey);
7390 path = pathPopFront(path);
7391 sparseSnapshotTreeRemember(child, path, data);
7392 }
7393}
7394/**
7395 * Purge the data at path from the cache.
7396 *
7397 * @param path - Path to look up snapshot for.
7398 * @returns True if this node should now be removed.
7399 */
7400function sparseSnapshotTreeForget(sparseSnapshotTree, path) {
7401 if (pathIsEmpty(path)) {
7402 sparseSnapshotTree.value = null;
7403 sparseSnapshotTree.children.clear();
7404 return true;
7405 }
7406 else {
7407 if (sparseSnapshotTree.value !== null) {
7408 if (sparseSnapshotTree.value.isLeafNode()) {
7409 // We're trying to forget a node that doesn't exist
7410 return false;
7411 }
7412 else {
7413 var value = sparseSnapshotTree.value;
7414 sparseSnapshotTree.value = null;
7415 value.forEachChild(PRIORITY_INDEX, function (key, tree) {
7416 sparseSnapshotTreeRemember(sparseSnapshotTree, new Path(key), tree);
7417 });
7418 return sparseSnapshotTreeForget(sparseSnapshotTree, path);
7419 }
7420 }
7421 else if (sparseSnapshotTree.children.size > 0) {
7422 var childKey = pathGetFront(path);
7423 path = pathPopFront(path);
7424 if (sparseSnapshotTree.children.has(childKey)) {
7425 var safeToRemove = sparseSnapshotTreeForget(sparseSnapshotTree.children.get(childKey), path);
7426 if (safeToRemove) {
7427 sparseSnapshotTree.children.delete(childKey);
7428 }
7429 }
7430 return sparseSnapshotTree.children.size === 0;
7431 }
7432 else {
7433 return true;
7434 }
7435 }
7436}
7437/**
7438 * Recursively iterates through all of the stored tree and calls the
7439 * callback on each one.
7440 *
7441 * @param prefixPath - Path to look up node for.
7442 * @param func - The function to invoke for each tree.
7443 */
7444function sparseSnapshotTreeForEachTree(sparseSnapshotTree, prefixPath, func) {
7445 if (sparseSnapshotTree.value !== null) {
7446 func(prefixPath, sparseSnapshotTree.value);
7447 }
7448 else {
7449 sparseSnapshotTreeForEachChild(sparseSnapshotTree, function (key, tree) {
7450 var path = new Path(prefixPath.toString() + '/' + key);
7451 sparseSnapshotTreeForEachTree(tree, path, func);
7452 });
7453 }
7454}
7455/**
7456 * Iterates through each immediate child and triggers the callback.
7457 * Only seems to be used in tests.
7458 *
7459 * @param func - The function to invoke for each child.
7460 */
7461function sparseSnapshotTreeForEachChild(sparseSnapshotTree, func) {
7462 sparseSnapshotTree.children.forEach(function (tree, key) {
7463 func(key, tree);
7464 });
7465}
7466
7467/**
7468 * @license
7469 * Copyright 2017 Google LLC
7470 *
7471 * Licensed under the Apache License, Version 2.0 (the "License");
7472 * you may not use this file except in compliance with the License.
7473 * You may obtain a copy of the License at
7474 *
7475 * http://www.apache.org/licenses/LICENSE-2.0
7476 *
7477 * Unless required by applicable law or agreed to in writing, software
7478 * distributed under the License is distributed on an "AS IS" BASIS,
7479 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7480 * See the License for the specific language governing permissions and
7481 * limitations under the License.
7482 */
7483/**
7484 * Returns the delta from the previous call to get stats.
7485 *
7486 * @param collection_ - The collection to "listen" to.
7487 */
7488var StatsListener = /** @class */ (function () {
7489 function StatsListener(collection_) {
7490 this.collection_ = collection_;
7491 this.last_ = null;
7492 }
7493 StatsListener.prototype.get = function () {
7494 var newStats = this.collection_.get();
7495 var delta = tslib.__assign({}, newStats);
7496 if (this.last_) {
7497 each(this.last_, function (stat, value) {
7498 delta[stat] = delta[stat] - value;
7499 });
7500 }
7501 this.last_ = newStats;
7502 return delta;
7503 };
7504 return StatsListener;
7505}());
7506
7507/**
7508 * @license
7509 * Copyright 2017 Google LLC
7510 *
7511 * Licensed under the Apache License, Version 2.0 (the "License");
7512 * you may not use this file except in compliance with the License.
7513 * You may obtain a copy of the License at
7514 *
7515 * http://www.apache.org/licenses/LICENSE-2.0
7516 *
7517 * Unless required by applicable law or agreed to in writing, software
7518 * distributed under the License is distributed on an "AS IS" BASIS,
7519 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7520 * See the License for the specific language governing permissions and
7521 * limitations under the License.
7522 */
7523// Assuming some apps may have a short amount of time on page, and a bulk of firebase operations probably
7524// happen on page load, we try to report our first set of stats pretty quickly, but we wait at least 10
7525// seconds to try to ensure the Firebase connection is established / settled.
7526var FIRST_STATS_MIN_TIME = 10 * 1000;
7527var FIRST_STATS_MAX_TIME = 30 * 1000;
7528// We'll continue to report stats on average every 5 minutes.
7529var REPORT_STATS_INTERVAL = 5 * 60 * 1000;
7530var StatsReporter = /** @class */ (function () {
7531 function StatsReporter(collection, server_) {
7532 this.server_ = server_;
7533 this.statsToReport_ = {};
7534 this.statsListener_ = new StatsListener(collection);
7535 var timeout = FIRST_STATS_MIN_TIME +
7536 (FIRST_STATS_MAX_TIME - FIRST_STATS_MIN_TIME) * Math.random();
7537 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(timeout));
7538 }
7539 StatsReporter.prototype.reportStats_ = function () {
7540 var _this = this;
7541 var stats = this.statsListener_.get();
7542 var reportedStats = {};
7543 var haveStatsToReport = false;
7544 each(stats, function (stat, value) {
7545 if (value > 0 && util.contains(_this.statsToReport_, stat)) {
7546 reportedStats[stat] = value;
7547 haveStatsToReport = true;
7548 }
7549 });
7550 if (haveStatsToReport) {
7551 this.server_.reportStats(reportedStats);
7552 }
7553 // queue our next run.
7554 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(Math.random() * 2 * REPORT_STATS_INTERVAL));
7555 };
7556 return StatsReporter;
7557}());
7558
7559/**
7560 * @license
7561 * Copyright 2017 Google LLC
7562 *
7563 * Licensed under the Apache License, Version 2.0 (the "License");
7564 * you may not use this file except in compliance with the License.
7565 * You may obtain a copy of the License at
7566 *
7567 * http://www.apache.org/licenses/LICENSE-2.0
7568 *
7569 * Unless required by applicable law or agreed to in writing, software
7570 * distributed under the License is distributed on an "AS IS" BASIS,
7571 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7572 * See the License for the specific language governing permissions and
7573 * limitations under the License.
7574 */
7575/**
7576 *
7577 * @enum
7578 */
7579var OperationType;
7580(function (OperationType) {
7581 OperationType[OperationType["OVERWRITE"] = 0] = "OVERWRITE";
7582 OperationType[OperationType["MERGE"] = 1] = "MERGE";
7583 OperationType[OperationType["ACK_USER_WRITE"] = 2] = "ACK_USER_WRITE";
7584 OperationType[OperationType["LISTEN_COMPLETE"] = 3] = "LISTEN_COMPLETE";
7585})(OperationType || (OperationType = {}));
7586function newOperationSourceUser() {
7587 return {
7588 fromUser: true,
7589 fromServer: false,
7590 queryId: null,
7591 tagged: false
7592 };
7593}
7594function newOperationSourceServer() {
7595 return {
7596 fromUser: false,
7597 fromServer: true,
7598 queryId: null,
7599 tagged: false
7600 };
7601}
7602function newOperationSourceServerTaggedQuery(queryId) {
7603 return {
7604 fromUser: false,
7605 fromServer: true,
7606 queryId: queryId,
7607 tagged: true
7608 };
7609}
7610
7611/**
7612 * @license
7613 * Copyright 2017 Google LLC
7614 *
7615 * Licensed under the Apache License, Version 2.0 (the "License");
7616 * you may not use this file except in compliance with the License.
7617 * You may obtain a copy of the License at
7618 *
7619 * http://www.apache.org/licenses/LICENSE-2.0
7620 *
7621 * Unless required by applicable law or agreed to in writing, software
7622 * distributed under the License is distributed on an "AS IS" BASIS,
7623 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7624 * See the License for the specific language governing permissions and
7625 * limitations under the License.
7626 */
7627var AckUserWrite = /** @class */ (function () {
7628 /**
7629 * @param affectedTree - A tree containing true for each affected path. Affected paths can't overlap.
7630 */
7631 function AckUserWrite(
7632 /** @inheritDoc */ path,
7633 /** @inheritDoc */ affectedTree,
7634 /** @inheritDoc */ revert) {
7635 this.path = path;
7636 this.affectedTree = affectedTree;
7637 this.revert = revert;
7638 /** @inheritDoc */
7639 this.type = OperationType.ACK_USER_WRITE;
7640 /** @inheritDoc */
7641 this.source = newOperationSourceUser();
7642 }
7643 AckUserWrite.prototype.operationForChild = function (childName) {
7644 if (!pathIsEmpty(this.path)) {
7645 util.assert(pathGetFront(this.path) === childName, 'operationForChild called for unrelated child.');
7646 return new AckUserWrite(pathPopFront(this.path), this.affectedTree, this.revert);
7647 }
7648 else if (this.affectedTree.value != null) {
7649 util.assert(this.affectedTree.children.isEmpty(), 'affectedTree should not have overlapping affected paths.');
7650 // All child locations are affected as well; just return same operation.
7651 return this;
7652 }
7653 else {
7654 var childTree = this.affectedTree.subtree(new Path(childName));
7655 return new AckUserWrite(newEmptyPath(), childTree, this.revert);
7656 }
7657 };
7658 return AckUserWrite;
7659}());
7660
7661/**
7662 * @license
7663 * Copyright 2017 Google LLC
7664 *
7665 * Licensed under the Apache License, Version 2.0 (the "License");
7666 * you may not use this file except in compliance with the License.
7667 * You may obtain a copy of the License at
7668 *
7669 * http://www.apache.org/licenses/LICENSE-2.0
7670 *
7671 * Unless required by applicable law or agreed to in writing, software
7672 * distributed under the License is distributed on an "AS IS" BASIS,
7673 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7674 * See the License for the specific language governing permissions and
7675 * limitations under the License.
7676 */
7677var ListenComplete = /** @class */ (function () {
7678 function ListenComplete(source, path) {
7679 this.source = source;
7680 this.path = path;
7681 /** @inheritDoc */
7682 this.type = OperationType.LISTEN_COMPLETE;
7683 }
7684 ListenComplete.prototype.operationForChild = function (childName) {
7685 if (pathIsEmpty(this.path)) {
7686 return new ListenComplete(this.source, newEmptyPath());
7687 }
7688 else {
7689 return new ListenComplete(this.source, pathPopFront(this.path));
7690 }
7691 };
7692 return ListenComplete;
7693}());
7694
7695/**
7696 * @license
7697 * Copyright 2017 Google LLC
7698 *
7699 * Licensed under the Apache License, Version 2.0 (the "License");
7700 * you may not use this file except in compliance with the License.
7701 * You may obtain a copy of the License at
7702 *
7703 * http://www.apache.org/licenses/LICENSE-2.0
7704 *
7705 * Unless required by applicable law or agreed to in writing, software
7706 * distributed under the License is distributed on an "AS IS" BASIS,
7707 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7708 * See the License for the specific language governing permissions and
7709 * limitations under the License.
7710 */
7711var Overwrite = /** @class */ (function () {
7712 function Overwrite(source, path, snap) {
7713 this.source = source;
7714 this.path = path;
7715 this.snap = snap;
7716 /** @inheritDoc */
7717 this.type = OperationType.OVERWRITE;
7718 }
7719 Overwrite.prototype.operationForChild = function (childName) {
7720 if (pathIsEmpty(this.path)) {
7721 return new Overwrite(this.source, newEmptyPath(), this.snap.getImmediateChild(childName));
7722 }
7723 else {
7724 return new Overwrite(this.source, pathPopFront(this.path), this.snap);
7725 }
7726 };
7727 return Overwrite;
7728}());
7729
7730/**
7731 * @license
7732 * Copyright 2017 Google LLC
7733 *
7734 * Licensed under the Apache License, Version 2.0 (the "License");
7735 * you may not use this file except in compliance with the License.
7736 * You may obtain a copy of the License at
7737 *
7738 * http://www.apache.org/licenses/LICENSE-2.0
7739 *
7740 * Unless required by applicable law or agreed to in writing, software
7741 * distributed under the License is distributed on an "AS IS" BASIS,
7742 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7743 * See the License for the specific language governing permissions and
7744 * limitations under the License.
7745 */
7746var Merge = /** @class */ (function () {
7747 function Merge(
7748 /** @inheritDoc */ source,
7749 /** @inheritDoc */ path,
7750 /** @inheritDoc */ children) {
7751 this.source = source;
7752 this.path = path;
7753 this.children = children;
7754 /** @inheritDoc */
7755 this.type = OperationType.MERGE;
7756 }
7757 Merge.prototype.operationForChild = function (childName) {
7758 if (pathIsEmpty(this.path)) {
7759 var childTree = this.children.subtree(new Path(childName));
7760 if (childTree.isEmpty()) {
7761 // This child is unaffected
7762 return null;
7763 }
7764 else if (childTree.value) {
7765 // We have a snapshot for the child in question. This becomes an overwrite of the child.
7766 return new Overwrite(this.source, newEmptyPath(), childTree.value);
7767 }
7768 else {
7769 // This is a merge at a deeper level
7770 return new Merge(this.source, newEmptyPath(), childTree);
7771 }
7772 }
7773 else {
7774 util.assert(pathGetFront(this.path) === childName, "Can't get a merge for a child not on the path of the operation");
7775 return new Merge(this.source, pathPopFront(this.path), this.children);
7776 }
7777 };
7778 Merge.prototype.toString = function () {
7779 return ('Operation(' +
7780 this.path +
7781 ': ' +
7782 this.source.toString() +
7783 ' merge: ' +
7784 this.children.toString() +
7785 ')');
7786 };
7787 return Merge;
7788}());
7789
7790/**
7791 * @license
7792 * Copyright 2017 Google LLC
7793 *
7794 * Licensed under the Apache License, Version 2.0 (the "License");
7795 * you may not use this file except in compliance with the License.
7796 * You may obtain a copy of the License at
7797 *
7798 * http://www.apache.org/licenses/LICENSE-2.0
7799 *
7800 * Unless required by applicable law or agreed to in writing, software
7801 * distributed under the License is distributed on an "AS IS" BASIS,
7802 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7803 * See the License for the specific language governing permissions and
7804 * limitations under the License.
7805 */
7806/**
7807 * A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
7808 * initialized in the sense that we know at one point in time this represented a valid state of the world, e.g.
7809 * initialized with data from the server, or a complete overwrite by the client. The filtered flag also tracks
7810 * whether a node potentially had children removed due to a filter.
7811 */
7812var CacheNode = /** @class */ (function () {
7813 function CacheNode(node_, fullyInitialized_, filtered_) {
7814 this.node_ = node_;
7815 this.fullyInitialized_ = fullyInitialized_;
7816 this.filtered_ = filtered_;
7817 }
7818 /**
7819 * Returns whether this node was fully initialized with either server data or a complete overwrite by the client
7820 */
7821 CacheNode.prototype.isFullyInitialized = function () {
7822 return this.fullyInitialized_;
7823 };
7824 /**
7825 * Returns whether this node is potentially missing children due to a filter applied to the node
7826 */
7827 CacheNode.prototype.isFiltered = function () {
7828 return this.filtered_;
7829 };
7830 CacheNode.prototype.isCompleteForPath = function (path) {
7831 if (pathIsEmpty(path)) {
7832 return this.isFullyInitialized() && !this.filtered_;
7833 }
7834 var childKey = pathGetFront(path);
7835 return this.isCompleteForChild(childKey);
7836 };
7837 CacheNode.prototype.isCompleteForChild = function (key) {
7838 return ((this.isFullyInitialized() && !this.filtered_) || this.node_.hasChild(key));
7839 };
7840 CacheNode.prototype.getNode = function () {
7841 return this.node_;
7842 };
7843 return CacheNode;
7844}());
7845
7846/**
7847 * @license
7848 * Copyright 2017 Google LLC
7849 *
7850 * Licensed under the Apache License, Version 2.0 (the "License");
7851 * you may not use this file except in compliance with the License.
7852 * You may obtain a copy of the License at
7853 *
7854 * http://www.apache.org/licenses/LICENSE-2.0
7855 *
7856 * Unless required by applicable law or agreed to in writing, software
7857 * distributed under the License is distributed on an "AS IS" BASIS,
7858 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7859 * See the License for the specific language governing permissions and
7860 * limitations under the License.
7861 */
7862/**
7863 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
7864 * CacheDiffer into actual events (Event) that can be raised. See generateEventsForChanges()
7865 * for details.
7866 *
7867 */
7868var EventGenerator = /** @class */ (function () {
7869 function EventGenerator(query_) {
7870 this.query_ = query_;
7871 this.index_ = this.query_._queryParams.getIndex();
7872 }
7873 return EventGenerator;
7874}());
7875/**
7876 * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
7877 * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
7878 *
7879 * Notes:
7880 * - child_moved events will be synthesized at this time for any child_changed events that affect
7881 * our index.
7882 * - prevName will be calculated based on the index ordering.
7883 */
7884function eventGeneratorGenerateEventsForChanges(eventGenerator, changes, eventCache, eventRegistrations) {
7885 var events = [];
7886 var moves = [];
7887 changes.forEach(function (change) {
7888 if (change.type === "child_changed" /* CHILD_CHANGED */ &&
7889 eventGenerator.index_.indexedValueChanged(change.oldSnap, change.snapshotNode)) {
7890 moves.push(changeChildMoved(change.childName, change.snapshotNode));
7891 }
7892 });
7893 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_removed" /* CHILD_REMOVED */, changes, eventRegistrations, eventCache);
7894 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_added" /* CHILD_ADDED */, changes, eventRegistrations, eventCache);
7895 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_moved" /* CHILD_MOVED */, moves, eventRegistrations, eventCache);
7896 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_changed" /* CHILD_CHANGED */, changes, eventRegistrations, eventCache);
7897 eventGeneratorGenerateEventsForType(eventGenerator, events, "value" /* VALUE */, changes, eventRegistrations, eventCache);
7898 return events;
7899}
7900/**
7901 * Given changes of a single change type, generate the corresponding events.
7902 */
7903function eventGeneratorGenerateEventsForType(eventGenerator, events, eventType, changes, registrations, eventCache) {
7904 var filteredChanges = changes.filter(function (change) { return change.type === eventType; });
7905 filteredChanges.sort(function (a, b) {
7906 return eventGeneratorCompareChanges(eventGenerator, a, b);
7907 });
7908 filteredChanges.forEach(function (change) {
7909 var materializedChange = eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache);
7910 registrations.forEach(function (registration) {
7911 if (registration.respondsTo(change.type)) {
7912 events.push(registration.createEvent(materializedChange, eventGenerator.query_));
7913 }
7914 });
7915 });
7916}
7917function eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache) {
7918 if (change.type === 'value' || change.type === 'child_removed') {
7919 return change;
7920 }
7921 else {
7922 change.prevName = eventCache.getPredecessorChildName(change.childName, change.snapshotNode, eventGenerator.index_);
7923 return change;
7924 }
7925}
7926function eventGeneratorCompareChanges(eventGenerator, a, b) {
7927 if (a.childName == null || b.childName == null) {
7928 throw util.assertionError('Should only compare child_ events.');
7929 }
7930 var aWrapped = new NamedNode(a.childName, a.snapshotNode);
7931 var bWrapped = new NamedNode(b.childName, b.snapshotNode);
7932 return eventGenerator.index_.compare(aWrapped, bWrapped);
7933}
7934
7935/**
7936 * @license
7937 * Copyright 2017 Google LLC
7938 *
7939 * Licensed under the Apache License, Version 2.0 (the "License");
7940 * you may not use this file except in compliance with the License.
7941 * You may obtain a copy of the License at
7942 *
7943 * http://www.apache.org/licenses/LICENSE-2.0
7944 *
7945 * Unless required by applicable law or agreed to in writing, software
7946 * distributed under the License is distributed on an "AS IS" BASIS,
7947 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7948 * See the License for the specific language governing permissions and
7949 * limitations under the License.
7950 */
7951function newViewCache(eventCache, serverCache) {
7952 return { eventCache: eventCache, serverCache: serverCache };
7953}
7954function viewCacheUpdateEventSnap(viewCache, eventSnap, complete, filtered) {
7955 return newViewCache(new CacheNode(eventSnap, complete, filtered), viewCache.serverCache);
7956}
7957function viewCacheUpdateServerSnap(viewCache, serverSnap, complete, filtered) {
7958 return newViewCache(viewCache.eventCache, new CacheNode(serverSnap, complete, filtered));
7959}
7960function viewCacheGetCompleteEventSnap(viewCache) {
7961 return viewCache.eventCache.isFullyInitialized()
7962 ? viewCache.eventCache.getNode()
7963 : null;
7964}
7965function viewCacheGetCompleteServerSnap(viewCache) {
7966 return viewCache.serverCache.isFullyInitialized()
7967 ? viewCache.serverCache.getNode()
7968 : null;
7969}
7970
7971/**
7972 * @license
7973 * Copyright 2017 Google LLC
7974 *
7975 * Licensed under the Apache License, Version 2.0 (the "License");
7976 * you may not use this file except in compliance with the License.
7977 * You may obtain a copy of the License at
7978 *
7979 * http://www.apache.org/licenses/LICENSE-2.0
7980 *
7981 * Unless required by applicable law or agreed to in writing, software
7982 * distributed under the License is distributed on an "AS IS" BASIS,
7983 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7984 * See the License for the specific language governing permissions and
7985 * limitations under the License.
7986 */
7987var emptyChildrenSingleton;
7988/**
7989 * Singleton empty children collection.
7990 *
7991 */
7992var EmptyChildren = function () {
7993 if (!emptyChildrenSingleton) {
7994 emptyChildrenSingleton = new SortedMap(stringCompare);
7995 }
7996 return emptyChildrenSingleton;
7997};
7998/**
7999 * A tree with immutable elements.
8000 */
8001var ImmutableTree = /** @class */ (function () {
8002 function ImmutableTree(value, children) {
8003 if (children === void 0) { children = EmptyChildren(); }
8004 this.value = value;
8005 this.children = children;
8006 }
8007 ImmutableTree.fromObject = function (obj) {
8008 var tree = new ImmutableTree(null);
8009 each(obj, function (childPath, childSnap) {
8010 tree = tree.set(new Path(childPath), childSnap);
8011 });
8012 return tree;
8013 };
8014 /**
8015 * True if the value is empty and there are no children
8016 */
8017 ImmutableTree.prototype.isEmpty = function () {
8018 return this.value === null && this.children.isEmpty();
8019 };
8020 /**
8021 * Given a path and predicate, return the first node and the path to that node
8022 * where the predicate returns true.
8023 *
8024 * TODO Do a perf test -- If we're creating a bunch of `{path: value:}`
8025 * objects on the way back out, it may be better to pass down a pathSoFar obj.
8026 *
8027 * @param relativePath - The remainder of the path
8028 * @param predicate - The predicate to satisfy to return a node
8029 */
8030 ImmutableTree.prototype.findRootMostMatchingPathAndValue = function (relativePath, predicate) {
8031 if (this.value != null && predicate(this.value)) {
8032 return { path: newEmptyPath(), value: this.value };
8033 }
8034 else {
8035 if (pathIsEmpty(relativePath)) {
8036 return null;
8037 }
8038 else {
8039 var front = pathGetFront(relativePath);
8040 var child = this.children.get(front);
8041 if (child !== null) {
8042 var childExistingPathAndValue = child.findRootMostMatchingPathAndValue(pathPopFront(relativePath), predicate);
8043 if (childExistingPathAndValue != null) {
8044 var fullPath = pathChild(new Path(front), childExistingPathAndValue.path);
8045 return { path: fullPath, value: childExistingPathAndValue.value };
8046 }
8047 else {
8048 return null;
8049 }
8050 }
8051 else {
8052 return null;
8053 }
8054 }
8055 }
8056 };
8057 /**
8058 * Find, if it exists, the shortest subpath of the given path that points a defined
8059 * value in the tree
8060 */
8061 ImmutableTree.prototype.findRootMostValueAndPath = function (relativePath) {
8062 return this.findRootMostMatchingPathAndValue(relativePath, function () { return true; });
8063 };
8064 /**
8065 * @returns The subtree at the given path
8066 */
8067 ImmutableTree.prototype.subtree = function (relativePath) {
8068 if (pathIsEmpty(relativePath)) {
8069 return this;
8070 }
8071 else {
8072 var front = pathGetFront(relativePath);
8073 var childTree = this.children.get(front);
8074 if (childTree !== null) {
8075 return childTree.subtree(pathPopFront(relativePath));
8076 }
8077 else {
8078 return new ImmutableTree(null);
8079 }
8080 }
8081 };
8082 /**
8083 * Sets a value at the specified path.
8084 *
8085 * @param relativePath - Path to set value at.
8086 * @param toSet - Value to set.
8087 * @returns Resulting tree.
8088 */
8089 ImmutableTree.prototype.set = function (relativePath, toSet) {
8090 if (pathIsEmpty(relativePath)) {
8091 return new ImmutableTree(toSet, this.children);
8092 }
8093 else {
8094 var front = pathGetFront(relativePath);
8095 var child = this.children.get(front) || new ImmutableTree(null);
8096 var newChild = child.set(pathPopFront(relativePath), toSet);
8097 var newChildren = this.children.insert(front, newChild);
8098 return new ImmutableTree(this.value, newChildren);
8099 }
8100 };
8101 /**
8102 * Removes the value at the specified path.
8103 *
8104 * @param relativePath - Path to value to remove.
8105 * @returns Resulting tree.
8106 */
8107 ImmutableTree.prototype.remove = function (relativePath) {
8108 if (pathIsEmpty(relativePath)) {
8109 if (this.children.isEmpty()) {
8110 return new ImmutableTree(null);
8111 }
8112 else {
8113 return new ImmutableTree(null, this.children);
8114 }
8115 }
8116 else {
8117 var front = pathGetFront(relativePath);
8118 var child = this.children.get(front);
8119 if (child) {
8120 var newChild = child.remove(pathPopFront(relativePath));
8121 var newChildren = void 0;
8122 if (newChild.isEmpty()) {
8123 newChildren = this.children.remove(front);
8124 }
8125 else {
8126 newChildren = this.children.insert(front, newChild);
8127 }
8128 if (this.value === null && newChildren.isEmpty()) {
8129 return new ImmutableTree(null);
8130 }
8131 else {
8132 return new ImmutableTree(this.value, newChildren);
8133 }
8134 }
8135 else {
8136 return this;
8137 }
8138 }
8139 };
8140 /**
8141 * Gets a value from the tree.
8142 *
8143 * @param relativePath - Path to get value for.
8144 * @returns Value at path, or null.
8145 */
8146 ImmutableTree.prototype.get = function (relativePath) {
8147 if (pathIsEmpty(relativePath)) {
8148 return this.value;
8149 }
8150 else {
8151 var front = pathGetFront(relativePath);
8152 var child = this.children.get(front);
8153 if (child) {
8154 return child.get(pathPopFront(relativePath));
8155 }
8156 else {
8157 return null;
8158 }
8159 }
8160 };
8161 /**
8162 * Replace the subtree at the specified path with the given new tree.
8163 *
8164 * @param relativePath - Path to replace subtree for.
8165 * @param newTree - New tree.
8166 * @returns Resulting tree.
8167 */
8168 ImmutableTree.prototype.setTree = function (relativePath, newTree) {
8169 if (pathIsEmpty(relativePath)) {
8170 return newTree;
8171 }
8172 else {
8173 var front = pathGetFront(relativePath);
8174 var child = this.children.get(front) || new ImmutableTree(null);
8175 var newChild = child.setTree(pathPopFront(relativePath), newTree);
8176 var newChildren = void 0;
8177 if (newChild.isEmpty()) {
8178 newChildren = this.children.remove(front);
8179 }
8180 else {
8181 newChildren = this.children.insert(front, newChild);
8182 }
8183 return new ImmutableTree(this.value, newChildren);
8184 }
8185 };
8186 /**
8187 * Performs a depth first fold on this tree. Transforms a tree into a single
8188 * value, given a function that operates on the path to a node, an optional
8189 * current value, and a map of child names to folded subtrees
8190 */
8191 ImmutableTree.prototype.fold = function (fn) {
8192 return this.fold_(newEmptyPath(), fn);
8193 };
8194 /**
8195 * Recursive helper for public-facing fold() method
8196 */
8197 ImmutableTree.prototype.fold_ = function (pathSoFar, fn) {
8198 var accum = {};
8199 this.children.inorderTraversal(function (childKey, childTree) {
8200 accum[childKey] = childTree.fold_(pathChild(pathSoFar, childKey), fn);
8201 });
8202 return fn(pathSoFar, this.value, accum);
8203 };
8204 /**
8205 * Find the first matching value on the given path. Return the result of applying f to it.
8206 */
8207 ImmutableTree.prototype.findOnPath = function (path, f) {
8208 return this.findOnPath_(path, newEmptyPath(), f);
8209 };
8210 ImmutableTree.prototype.findOnPath_ = function (pathToFollow, pathSoFar, f) {
8211 var result = this.value ? f(pathSoFar, this.value) : false;
8212 if (result) {
8213 return result;
8214 }
8215 else {
8216 if (pathIsEmpty(pathToFollow)) {
8217 return null;
8218 }
8219 else {
8220 var front = pathGetFront(pathToFollow);
8221 var nextChild = this.children.get(front);
8222 if (nextChild) {
8223 return nextChild.findOnPath_(pathPopFront(pathToFollow), pathChild(pathSoFar, front), f);
8224 }
8225 else {
8226 return null;
8227 }
8228 }
8229 }
8230 };
8231 ImmutableTree.prototype.foreachOnPath = function (path, f) {
8232 return this.foreachOnPath_(path, newEmptyPath(), f);
8233 };
8234 ImmutableTree.prototype.foreachOnPath_ = function (pathToFollow, currentRelativePath, f) {
8235 if (pathIsEmpty(pathToFollow)) {
8236 return this;
8237 }
8238 else {
8239 if (this.value) {
8240 f(currentRelativePath, this.value);
8241 }
8242 var front = pathGetFront(pathToFollow);
8243 var nextChild = this.children.get(front);
8244 if (nextChild) {
8245 return nextChild.foreachOnPath_(pathPopFront(pathToFollow), pathChild(currentRelativePath, front), f);
8246 }
8247 else {
8248 return new ImmutableTree(null);
8249 }
8250 }
8251 };
8252 /**
8253 * Calls the given function for each node in the tree that has a value.
8254 *
8255 * @param f - A function to be called with the path from the root of the tree to
8256 * a node, and the value at that node. Called in depth-first order.
8257 */
8258 ImmutableTree.prototype.foreach = function (f) {
8259 this.foreach_(newEmptyPath(), f);
8260 };
8261 ImmutableTree.prototype.foreach_ = function (currentRelativePath, f) {
8262 this.children.inorderTraversal(function (childName, childTree) {
8263 childTree.foreach_(pathChild(currentRelativePath, childName), f);
8264 });
8265 if (this.value) {
8266 f(currentRelativePath, this.value);
8267 }
8268 };
8269 ImmutableTree.prototype.foreachChild = function (f) {
8270 this.children.inorderTraversal(function (childName, childTree) {
8271 if (childTree.value) {
8272 f(childName, childTree.value);
8273 }
8274 });
8275 };
8276 return ImmutableTree;
8277}());
8278
8279/**
8280 * @license
8281 * Copyright 2017 Google LLC
8282 *
8283 * Licensed under the Apache License, Version 2.0 (the "License");
8284 * you may not use this file except in compliance with the License.
8285 * You may obtain a copy of the License at
8286 *
8287 * http://www.apache.org/licenses/LICENSE-2.0
8288 *
8289 * Unless required by applicable law or agreed to in writing, software
8290 * distributed under the License is distributed on an "AS IS" BASIS,
8291 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8292 * See the License for the specific language governing permissions and
8293 * limitations under the License.
8294 */
8295/**
8296 * This class holds a collection of writes that can be applied to nodes in unison. It abstracts away the logic with
8297 * dealing with priority writes and multiple nested writes. At any given path there is only allowed to be one write
8298 * modifying that path. Any write to an existing path or shadowing an existing path will modify that existing write
8299 * to reflect the write added.
8300 */
8301var CompoundWrite = /** @class */ (function () {
8302 function CompoundWrite(writeTree_) {
8303 this.writeTree_ = writeTree_;
8304 }
8305 CompoundWrite.empty = function () {
8306 return new CompoundWrite(new ImmutableTree(null));
8307 };
8308 return CompoundWrite;
8309}());
8310function compoundWriteAddWrite(compoundWrite, path, node) {
8311 if (pathIsEmpty(path)) {
8312 return new CompoundWrite(new ImmutableTree(node));
8313 }
8314 else {
8315 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8316 if (rootmost != null) {
8317 var rootMostPath = rootmost.path;
8318 var value = rootmost.value;
8319 var relativePath = newRelativePath(rootMostPath, path);
8320 value = value.updateChild(relativePath, node);
8321 return new CompoundWrite(compoundWrite.writeTree_.set(rootMostPath, value));
8322 }
8323 else {
8324 var subtree = new ImmutableTree(node);
8325 var newWriteTree = compoundWrite.writeTree_.setTree(path, subtree);
8326 return new CompoundWrite(newWriteTree);
8327 }
8328 }
8329}
8330function compoundWriteAddWrites(compoundWrite, path, updates) {
8331 var newWrite = compoundWrite;
8332 each(updates, function (childKey, node) {
8333 newWrite = compoundWriteAddWrite(newWrite, pathChild(path, childKey), node);
8334 });
8335 return newWrite;
8336}
8337/**
8338 * Will remove a write at the given path and deeper paths. This will <em>not</em> modify a write at a higher
8339 * location, which must be removed by calling this method with that path.
8340 *
8341 * @param compoundWrite - The CompoundWrite to remove.
8342 * @param path - The path at which a write and all deeper writes should be removed
8343 * @returns The new CompoundWrite with the removed path
8344 */
8345function compoundWriteRemoveWrite(compoundWrite, path) {
8346 if (pathIsEmpty(path)) {
8347 return CompoundWrite.empty();
8348 }
8349 else {
8350 var newWriteTree = compoundWrite.writeTree_.setTree(path, new ImmutableTree(null));
8351 return new CompoundWrite(newWriteTree);
8352 }
8353}
8354/**
8355 * Returns whether this CompoundWrite will fully overwrite a node at a given location and can therefore be
8356 * considered "complete".
8357 *
8358 * @param compoundWrite - The CompoundWrite to check.
8359 * @param path - The path to check for
8360 * @returns Whether there is a complete write at that path
8361 */
8362function compoundWriteHasCompleteWrite(compoundWrite, path) {
8363 return compoundWriteGetCompleteNode(compoundWrite, path) != null;
8364}
8365/**
8366 * Returns a node for a path if and only if the node is a "complete" overwrite at that path. This will not aggregate
8367 * writes from deeper paths, but will return child nodes from a more shallow path.
8368 *
8369 * @param compoundWrite - The CompoundWrite to get the node from.
8370 * @param path - The path to get a complete write
8371 * @returns The node if complete at that path, or null otherwise.
8372 */
8373function compoundWriteGetCompleteNode(compoundWrite, path) {
8374 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8375 if (rootmost != null) {
8376 return compoundWrite.writeTree_
8377 .get(rootmost.path)
8378 .getChild(newRelativePath(rootmost.path, path));
8379 }
8380 else {
8381 return null;
8382 }
8383}
8384/**
8385 * Returns all children that are guaranteed to be a complete overwrite.
8386 *
8387 * @param compoundWrite - The CompoundWrite to get children from.
8388 * @returns A list of all complete children.
8389 */
8390function compoundWriteGetCompleteChildren(compoundWrite) {
8391 var children = [];
8392 var node = compoundWrite.writeTree_.value;
8393 if (node != null) {
8394 // If it's a leaf node, it has no children; so nothing to do.
8395 if (!node.isLeafNode()) {
8396 node.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8397 children.push(new NamedNode(childName, childNode));
8398 });
8399 }
8400 }
8401 else {
8402 compoundWrite.writeTree_.children.inorderTraversal(function (childName, childTree) {
8403 if (childTree.value != null) {
8404 children.push(new NamedNode(childName, childTree.value));
8405 }
8406 });
8407 }
8408 return children;
8409}
8410function compoundWriteChildCompoundWrite(compoundWrite, path) {
8411 if (pathIsEmpty(path)) {
8412 return compoundWrite;
8413 }
8414 else {
8415 var shadowingNode = compoundWriteGetCompleteNode(compoundWrite, path);
8416 if (shadowingNode != null) {
8417 return new CompoundWrite(new ImmutableTree(shadowingNode));
8418 }
8419 else {
8420 return new CompoundWrite(compoundWrite.writeTree_.subtree(path));
8421 }
8422 }
8423}
8424/**
8425 * Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
8426 * @returns Whether this CompoundWrite is empty
8427 */
8428function compoundWriteIsEmpty(compoundWrite) {
8429 return compoundWrite.writeTree_.isEmpty();
8430}
8431/**
8432 * Applies this CompoundWrite to a node. The node is returned with all writes from this CompoundWrite applied to the
8433 * node
8434 * @param node - The node to apply this CompoundWrite to
8435 * @returns The node with all writes applied
8436 */
8437function compoundWriteApply(compoundWrite, node) {
8438 return applySubtreeWrite(newEmptyPath(), compoundWrite.writeTree_, node);
8439}
8440function applySubtreeWrite(relativePath, writeTree, node) {
8441 if (writeTree.value != null) {
8442 // Since there a write is always a leaf, we're done here
8443 return node.updateChild(relativePath, writeTree.value);
8444 }
8445 else {
8446 var priorityWrite_1 = null;
8447 writeTree.children.inorderTraversal(function (childKey, childTree) {
8448 if (childKey === '.priority') {
8449 // Apply priorities at the end so we don't update priorities for either empty nodes or forget
8450 // to apply priorities to empty nodes that are later filled
8451 util.assert(childTree.value !== null, 'Priority writes must always be leaf nodes');
8452 priorityWrite_1 = childTree.value;
8453 }
8454 else {
8455 node = applySubtreeWrite(pathChild(relativePath, childKey), childTree, node);
8456 }
8457 });
8458 // If there was a priority write, we only apply it if the node is not empty
8459 if (!node.getChild(relativePath).isEmpty() && priorityWrite_1 !== null) {
8460 node = node.updateChild(pathChild(relativePath, '.priority'), priorityWrite_1);
8461 }
8462 return node;
8463 }
8464}
8465
8466/**
8467 * @license
8468 * Copyright 2017 Google LLC
8469 *
8470 * Licensed under the Apache License, Version 2.0 (the "License");
8471 * you may not use this file except in compliance with the License.
8472 * You may obtain a copy of the License at
8473 *
8474 * http://www.apache.org/licenses/LICENSE-2.0
8475 *
8476 * Unless required by applicable law or agreed to in writing, software
8477 * distributed under the License is distributed on an "AS IS" BASIS,
8478 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8479 * See the License for the specific language governing permissions and
8480 * limitations under the License.
8481 */
8482/**
8483 * Create a new WriteTreeRef for the given path. For use with a new sync point at the given path.
8484 *
8485 */
8486function writeTreeChildWrites(writeTree, path) {
8487 return newWriteTreeRef(path, writeTree);
8488}
8489/**
8490 * Record a new overwrite from user code.
8491 *
8492 * @param visible - This is set to false by some transactions. It should be excluded from event caches
8493 */
8494function writeTreeAddOverwrite(writeTree, path, snap, writeId, visible) {
8495 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older write on top of newer ones');
8496 if (visible === undefined) {
8497 visible = true;
8498 }
8499 writeTree.allWrites.push({
8500 path: path,
8501 snap: snap,
8502 writeId: writeId,
8503 visible: visible
8504 });
8505 if (visible) {
8506 writeTree.visibleWrites = compoundWriteAddWrite(writeTree.visibleWrites, path, snap);
8507 }
8508 writeTree.lastWriteId = writeId;
8509}
8510/**
8511 * Record a new merge from user code.
8512 */
8513function writeTreeAddMerge(writeTree, path, changedChildren, writeId) {
8514 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older merge on top of newer ones');
8515 writeTree.allWrites.push({
8516 path: path,
8517 children: changedChildren,
8518 writeId: writeId,
8519 visible: true
8520 });
8521 writeTree.visibleWrites = compoundWriteAddWrites(writeTree.visibleWrites, path, changedChildren);
8522 writeTree.lastWriteId = writeId;
8523}
8524function writeTreeGetWrite(writeTree, writeId) {
8525 for (var i = 0; i < writeTree.allWrites.length; i++) {
8526 var record = writeTree.allWrites[i];
8527 if (record.writeId === writeId) {
8528 return record;
8529 }
8530 }
8531 return null;
8532}
8533/**
8534 * Remove a write (either an overwrite or merge) that has been successfully acknowledge by the server. Recalculates
8535 * the tree if necessary. We return true if it may have been visible, meaning views need to reevaluate.
8536 *
8537 * @returns true if the write may have been visible (meaning we'll need to reevaluate / raise
8538 * events as a result).
8539 */
8540function writeTreeRemoveWrite(writeTree, writeId) {
8541 // Note: disabling this check. It could be a transaction that preempted another transaction, and thus was applied
8542 // out of order.
8543 //const validClear = revert || this.allWrites_.length === 0 || writeId <= this.allWrites_[0].writeId;
8544 //assert(validClear, "Either we don't have this write, or it's the first one in the queue");
8545 var idx = writeTree.allWrites.findIndex(function (s) {
8546 return s.writeId === writeId;
8547 });
8548 util.assert(idx >= 0, 'removeWrite called with nonexistent writeId.');
8549 var writeToRemove = writeTree.allWrites[idx];
8550 writeTree.allWrites.splice(idx, 1);
8551 var removedWriteWasVisible = writeToRemove.visible;
8552 var removedWriteOverlapsWithOtherWrites = false;
8553 var i = writeTree.allWrites.length - 1;
8554 while (removedWriteWasVisible && i >= 0) {
8555 var currentWrite = writeTree.allWrites[i];
8556 if (currentWrite.visible) {
8557 if (i >= idx &&
8558 writeTreeRecordContainsPath_(currentWrite, writeToRemove.path)) {
8559 // The removed write was completely shadowed by a subsequent write.
8560 removedWriteWasVisible = false;
8561 }
8562 else if (pathContains(writeToRemove.path, currentWrite.path)) {
8563 // Either we're covering some writes or they're covering part of us (depending on which came first).
8564 removedWriteOverlapsWithOtherWrites = true;
8565 }
8566 }
8567 i--;
8568 }
8569 if (!removedWriteWasVisible) {
8570 return false;
8571 }
8572 else if (removedWriteOverlapsWithOtherWrites) {
8573 // There's some shadowing going on. Just rebuild the visible writes from scratch.
8574 writeTreeResetTree_(writeTree);
8575 return true;
8576 }
8577 else {
8578 // There's no shadowing. We can safely just remove the write(s) from visibleWrites.
8579 if (writeToRemove.snap) {
8580 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, writeToRemove.path);
8581 }
8582 else {
8583 var children = writeToRemove.children;
8584 each(children, function (childName) {
8585 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, pathChild(writeToRemove.path, childName));
8586 });
8587 }
8588 return true;
8589 }
8590}
8591function writeTreeRecordContainsPath_(writeRecord, path) {
8592 if (writeRecord.snap) {
8593 return pathContains(writeRecord.path, path);
8594 }
8595 else {
8596 for (var childName in writeRecord.children) {
8597 if (writeRecord.children.hasOwnProperty(childName) &&
8598 pathContains(pathChild(writeRecord.path, childName), path)) {
8599 return true;
8600 }
8601 }
8602 return false;
8603 }
8604}
8605/**
8606 * Re-layer the writes and merges into a tree so we can efficiently calculate event snapshots
8607 */
8608function writeTreeResetTree_(writeTree) {
8609 writeTree.visibleWrites = writeTreeLayerTree_(writeTree.allWrites, writeTreeDefaultFilter_, newEmptyPath());
8610 if (writeTree.allWrites.length > 0) {
8611 writeTree.lastWriteId =
8612 writeTree.allWrites[writeTree.allWrites.length - 1].writeId;
8613 }
8614 else {
8615 writeTree.lastWriteId = -1;
8616 }
8617}
8618/**
8619 * The default filter used when constructing the tree. Keep everything that's visible.
8620 */
8621function writeTreeDefaultFilter_(write) {
8622 return write.visible;
8623}
8624/**
8625 * Static method. Given an array of WriteRecords, a filter for which ones to include, and a path, construct the tree of
8626 * event data at that path.
8627 */
8628function writeTreeLayerTree_(writes, filter, treeRoot) {
8629 var compoundWrite = CompoundWrite.empty();
8630 for (var i = 0; i < writes.length; ++i) {
8631 var write = writes[i];
8632 // Theory, a later set will either:
8633 // a) abort a relevant transaction, so no need to worry about excluding it from calculating that transaction
8634 // b) not be relevant to a transaction (separate branch), so again will not affect the data for that transaction
8635 if (filter(write)) {
8636 var writePath = write.path;
8637 var relativePath = void 0;
8638 if (write.snap) {
8639 if (pathContains(treeRoot, writePath)) {
8640 relativePath = newRelativePath(treeRoot, writePath);
8641 compoundWrite = compoundWriteAddWrite(compoundWrite, relativePath, write.snap);
8642 }
8643 else if (pathContains(writePath, treeRoot)) {
8644 relativePath = newRelativePath(writePath, treeRoot);
8645 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), write.snap.getChild(relativePath));
8646 }
8647 else ;
8648 }
8649 else if (write.children) {
8650 if (pathContains(treeRoot, writePath)) {
8651 relativePath = newRelativePath(treeRoot, writePath);
8652 compoundWrite = compoundWriteAddWrites(compoundWrite, relativePath, write.children);
8653 }
8654 else if (pathContains(writePath, treeRoot)) {
8655 relativePath = newRelativePath(writePath, treeRoot);
8656 if (pathIsEmpty(relativePath)) {
8657 compoundWrite = compoundWriteAddWrites(compoundWrite, newEmptyPath(), write.children);
8658 }
8659 else {
8660 var child = util.safeGet(write.children, pathGetFront(relativePath));
8661 if (child) {
8662 // There exists a child in this node that matches the root path
8663 var deepNode = child.getChild(pathPopFront(relativePath));
8664 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), deepNode);
8665 }
8666 }
8667 }
8668 else ;
8669 }
8670 else {
8671 throw util.assertionError('WriteRecord should have .snap or .children');
8672 }
8673 }
8674 }
8675 return compoundWrite;
8676}
8677/**
8678 * Given optional, underlying server data, and an optional set of constraints (exclude some sets, include hidden
8679 * writes), attempt to calculate a complete snapshot for the given path
8680 *
8681 * @param writeIdsToExclude - An optional set to be excluded
8682 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8683 */
8684function writeTreeCalcCompleteEventCache(writeTree, treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8685 if (!writeIdsToExclude && !includeHiddenWrites) {
8686 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8687 if (shadowingNode != null) {
8688 return shadowingNode;
8689 }
8690 else {
8691 var subMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8692 if (compoundWriteIsEmpty(subMerge)) {
8693 return completeServerCache;
8694 }
8695 else if (completeServerCache == null &&
8696 !compoundWriteHasCompleteWrite(subMerge, newEmptyPath())) {
8697 // We wouldn't have a complete snapshot, since there's no underlying data and no complete shadow
8698 return null;
8699 }
8700 else {
8701 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8702 return compoundWriteApply(subMerge, layeredCache);
8703 }
8704 }
8705 }
8706 else {
8707 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8708 if (!includeHiddenWrites && compoundWriteIsEmpty(merge)) {
8709 return completeServerCache;
8710 }
8711 else {
8712 // If the server cache is null, and we don't have a complete cache, we need to return null
8713 if (!includeHiddenWrites &&
8714 completeServerCache == null &&
8715 !compoundWriteHasCompleteWrite(merge, newEmptyPath())) {
8716 return null;
8717 }
8718 else {
8719 var filter = function (write) {
8720 return ((write.visible || includeHiddenWrites) &&
8721 (!writeIdsToExclude ||
8722 !~writeIdsToExclude.indexOf(write.writeId)) &&
8723 (pathContains(write.path, treePath) ||
8724 pathContains(treePath, write.path)));
8725 };
8726 var mergeAtPath = writeTreeLayerTree_(writeTree.allWrites, filter, treePath);
8727 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8728 return compoundWriteApply(mergeAtPath, layeredCache);
8729 }
8730 }
8731 }
8732}
8733/**
8734 * With optional, underlying server data, attempt to return a children node of children that we have complete data for.
8735 * Used when creating new views, to pre-fill their complete event children snapshot.
8736 */
8737function writeTreeCalcCompleteEventChildren(writeTree, treePath, completeServerChildren) {
8738 var completeChildren = ChildrenNode.EMPTY_NODE;
8739 var topLevelSet = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8740 if (topLevelSet) {
8741 if (!topLevelSet.isLeafNode()) {
8742 // we're shadowing everything. Return the children.
8743 topLevelSet.forEachChild(PRIORITY_INDEX, function (childName, childSnap) {
8744 completeChildren = completeChildren.updateImmediateChild(childName, childSnap);
8745 });
8746 }
8747 return completeChildren;
8748 }
8749 else if (completeServerChildren) {
8750 // Layer any children we have on top of this
8751 // We know we don't have a top-level set, so just enumerate existing children
8752 var merge_1 = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8753 completeServerChildren.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8754 var node = compoundWriteApply(compoundWriteChildCompoundWrite(merge_1, new Path(childName)), childNode);
8755 completeChildren = completeChildren.updateImmediateChild(childName, node);
8756 });
8757 // Add any complete children we have from the set
8758 compoundWriteGetCompleteChildren(merge_1).forEach(function (namedNode) {
8759 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8760 });
8761 return completeChildren;
8762 }
8763 else {
8764 // We don't have anything to layer on top of. Layer on any children we have
8765 // Note that we can return an empty snap if we have a defined delete
8766 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8767 compoundWriteGetCompleteChildren(merge).forEach(function (namedNode) {
8768 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8769 });
8770 return completeChildren;
8771 }
8772}
8773/**
8774 * Given that the underlying server data has updated, determine what, if anything, needs to be
8775 * applied to the event cache.
8776 *
8777 * Possibilities:
8778 *
8779 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8780 *
8781 * 2. Some write is completely shadowing. No events to be raised
8782 *
8783 * 3. Is partially shadowed. Events
8784 *
8785 * Either existingEventSnap or existingServerSnap must exist
8786 */
8787function writeTreeCalcEventCacheAfterServerOverwrite(writeTree, treePath, childPath, existingEventSnap, existingServerSnap) {
8788 util.assert(existingEventSnap || existingServerSnap, 'Either existingEventSnap or existingServerSnap must exist');
8789 var path = pathChild(treePath, childPath);
8790 if (compoundWriteHasCompleteWrite(writeTree.visibleWrites, path)) {
8791 // At this point we can probably guarantee that we're in case 2, meaning no events
8792 // May need to check visibility while doing the findRootMostValueAndPath call
8793 return null;
8794 }
8795 else {
8796 // No complete shadowing. We're either partially shadowing or not shadowing at all.
8797 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8798 if (compoundWriteIsEmpty(childMerge)) {
8799 // We're not shadowing at all. Case 1
8800 return existingServerSnap.getChild(childPath);
8801 }
8802 else {
8803 // This could be more efficient if the serverNode + updates doesn't change the eventSnap
8804 // However this is tricky to find out, since user updates don't necessary change the server
8805 // snap, e.g. priority updates on empty nodes, or deep deletes. Another special case is if the server
8806 // adds nodes, but doesn't change any existing writes. It is therefore not enough to
8807 // only check if the updates change the serverNode.
8808 // Maybe check if the merge tree contains these special cases and only do a full overwrite in that case?
8809 return compoundWriteApply(childMerge, existingServerSnap.getChild(childPath));
8810 }
8811 }
8812}
8813/**
8814 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8815 * complete child for this ChildKey.
8816 */
8817function writeTreeCalcCompleteChild(writeTree, treePath, childKey, existingServerSnap) {
8818 var path = pathChild(treePath, childKey);
8819 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8820 if (shadowingNode != null) {
8821 return shadowingNode;
8822 }
8823 else {
8824 if (existingServerSnap.isCompleteForChild(childKey)) {
8825 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8826 return compoundWriteApply(childMerge, existingServerSnap.getNode().getImmediateChild(childKey));
8827 }
8828 else {
8829 return null;
8830 }
8831 }
8832}
8833/**
8834 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8835 * a higher path, this will return the child of that write relative to the write and this path.
8836 * Returns null if there is no write at this path.
8837 */
8838function writeTreeShadowingWrite(writeTree, path) {
8839 return compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8840}
8841/**
8842 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8843 * the window, but may now be in the window.
8844 */
8845function writeTreeCalcIndexedSlice(writeTree, treePath, completeServerData, startPost, count, reverse, index) {
8846 var toIterate;
8847 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8848 var shadowingNode = compoundWriteGetCompleteNode(merge, newEmptyPath());
8849 if (shadowingNode != null) {
8850 toIterate = shadowingNode;
8851 }
8852 else if (completeServerData != null) {
8853 toIterate = compoundWriteApply(merge, completeServerData);
8854 }
8855 else {
8856 // no children to iterate on
8857 return [];
8858 }
8859 toIterate = toIterate.withIndex(index);
8860 if (!toIterate.isEmpty() && !toIterate.isLeafNode()) {
8861 var nodes = [];
8862 var cmp = index.getCompare();
8863 var iter = reverse
8864 ? toIterate.getReverseIteratorFrom(startPost, index)
8865 : toIterate.getIteratorFrom(startPost, index);
8866 var next = iter.getNext();
8867 while (next && nodes.length < count) {
8868 if (cmp(next, startPost) !== 0) {
8869 nodes.push(next);
8870 }
8871 next = iter.getNext();
8872 }
8873 return nodes;
8874 }
8875 else {
8876 return [];
8877 }
8878}
8879function newWriteTree() {
8880 return {
8881 visibleWrites: CompoundWrite.empty(),
8882 allWrites: [],
8883 lastWriteId: -1
8884 };
8885}
8886/**
8887 * If possible, returns a complete event cache, using the underlying server data if possible. In addition, can be used
8888 * to get a cache that includes hidden writes, and excludes arbitrary writes. Note that customizing the returned node
8889 * can lead to a more expensive calculation.
8890 *
8891 * @param writeIdsToExclude - Optional writes to exclude.
8892 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8893 */
8894function writeTreeRefCalcCompleteEventCache(writeTreeRef, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8895 return writeTreeCalcCompleteEventCache(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites);
8896}
8897/**
8898 * If possible, returns a children node containing all of the complete children we have data for. The returned data is a
8899 * mix of the given server data and write data.
8900 *
8901 */
8902function writeTreeRefCalcCompleteEventChildren(writeTreeRef, completeServerChildren) {
8903 return writeTreeCalcCompleteEventChildren(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerChildren);
8904}
8905/**
8906 * Given that either the underlying server data has updated or the outstanding writes have updated, determine what,
8907 * if anything, needs to be applied to the event cache.
8908 *
8909 * Possibilities:
8910 *
8911 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8912 *
8913 * 2. Some write is completely shadowing. No events to be raised
8914 *
8915 * 3. Is partially shadowed. Events should be raised
8916 *
8917 * Either existingEventSnap or existingServerSnap must exist, this is validated via an assert
8918 *
8919 *
8920 */
8921function writeTreeRefCalcEventCacheAfterServerOverwrite(writeTreeRef, path, existingEventSnap, existingServerSnap) {
8922 return writeTreeCalcEventCacheAfterServerOverwrite(writeTreeRef.writeTree, writeTreeRef.treePath, path, existingEventSnap, existingServerSnap);
8923}
8924/**
8925 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8926 * a higher path, this will return the child of that write relative to the write and this path.
8927 * Returns null if there is no write at this path.
8928 *
8929 */
8930function writeTreeRefShadowingWrite(writeTreeRef, path) {
8931 return writeTreeShadowingWrite(writeTreeRef.writeTree, pathChild(writeTreeRef.treePath, path));
8932}
8933/**
8934 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8935 * the window, but may now be in the window
8936 */
8937function writeTreeRefCalcIndexedSlice(writeTreeRef, completeServerData, startPost, count, reverse, index) {
8938 return writeTreeCalcIndexedSlice(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerData, startPost, count, reverse, index);
8939}
8940/**
8941 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8942 * complete child for this ChildKey.
8943 */
8944function writeTreeRefCalcCompleteChild(writeTreeRef, childKey, existingServerCache) {
8945 return writeTreeCalcCompleteChild(writeTreeRef.writeTree, writeTreeRef.treePath, childKey, existingServerCache);
8946}
8947/**
8948 * Return a WriteTreeRef for a child.
8949 */
8950function writeTreeRefChild(writeTreeRef, childName) {
8951 return newWriteTreeRef(pathChild(writeTreeRef.treePath, childName), writeTreeRef.writeTree);
8952}
8953function newWriteTreeRef(path, writeTree) {
8954 return {
8955 treePath: path,
8956 writeTree: writeTree
8957 };
8958}
8959
8960/**
8961 * @license
8962 * Copyright 2017 Google LLC
8963 *
8964 * Licensed under the Apache License, Version 2.0 (the "License");
8965 * you may not use this file except in compliance with the License.
8966 * You may obtain a copy of the License at
8967 *
8968 * http://www.apache.org/licenses/LICENSE-2.0
8969 *
8970 * Unless required by applicable law or agreed to in writing, software
8971 * distributed under the License is distributed on an "AS IS" BASIS,
8972 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8973 * See the License for the specific language governing permissions and
8974 * limitations under the License.
8975 */
8976var ChildChangeAccumulator = /** @class */ (function () {
8977 function ChildChangeAccumulator() {
8978 this.changeMap = new Map();
8979 }
8980 ChildChangeAccumulator.prototype.trackChildChange = function (change) {
8981 var type = change.type;
8982 var childKey = change.childName;
8983 util.assert(type === "child_added" /* CHILD_ADDED */ ||
8984 type === "child_changed" /* CHILD_CHANGED */ ||
8985 type === "child_removed" /* CHILD_REMOVED */, 'Only child changes supported for tracking');
8986 util.assert(childKey !== '.priority', 'Only non-priority child changes can be tracked.');
8987 var oldChange = this.changeMap.get(childKey);
8988 if (oldChange) {
8989 var oldType = oldChange.type;
8990 if (type === "child_added" /* CHILD_ADDED */ &&
8991 oldType === "child_removed" /* CHILD_REMOVED */) {
8992 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.snapshotNode));
8993 }
8994 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8995 oldType === "child_added" /* CHILD_ADDED */) {
8996 this.changeMap.delete(childKey);
8997 }
8998 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8999 oldType === "child_changed" /* CHILD_CHANGED */) {
9000 this.changeMap.set(childKey, changeChildRemoved(childKey, oldChange.oldSnap));
9001 }
9002 else if (type === "child_changed" /* CHILD_CHANGED */ &&
9003 oldType === "child_added" /* CHILD_ADDED */) {
9004 this.changeMap.set(childKey, changeChildAdded(childKey, change.snapshotNode));
9005 }
9006 else if (type === "child_changed" /* CHILD_CHANGED */ &&
9007 oldType === "child_changed" /* CHILD_CHANGED */) {
9008 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.oldSnap));
9009 }
9010 else {
9011 throw util.assertionError('Illegal combination of changes: ' +
9012 change +
9013 ' occurred after ' +
9014 oldChange);
9015 }
9016 }
9017 else {
9018 this.changeMap.set(childKey, change);
9019 }
9020 };
9021 ChildChangeAccumulator.prototype.getChanges = function () {
9022 return Array.from(this.changeMap.values());
9023 };
9024 return ChildChangeAccumulator;
9025}());
9026
9027/**
9028 * @license
9029 * Copyright 2017 Google LLC
9030 *
9031 * Licensed under the Apache License, Version 2.0 (the "License");
9032 * you may not use this file except in compliance with the License.
9033 * You may obtain a copy of the License at
9034 *
9035 * http://www.apache.org/licenses/LICENSE-2.0
9036 *
9037 * Unless required by applicable law or agreed to in writing, software
9038 * distributed under the License is distributed on an "AS IS" BASIS,
9039 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9040 * See the License for the specific language governing permissions and
9041 * limitations under the License.
9042 */
9043/**
9044 * An implementation of CompleteChildSource that never returns any additional children
9045 */
9046// eslint-disable-next-line @typescript-eslint/naming-convention
9047var NoCompleteChildSource_ = /** @class */ (function () {
9048 function NoCompleteChildSource_() {
9049 }
9050 NoCompleteChildSource_.prototype.getCompleteChild = function (childKey) {
9051 return null;
9052 };
9053 NoCompleteChildSource_.prototype.getChildAfterChild = function (index, child, reverse) {
9054 return null;
9055 };
9056 return NoCompleteChildSource_;
9057}());
9058/**
9059 * Singleton instance.
9060 */
9061var NO_COMPLETE_CHILD_SOURCE = new NoCompleteChildSource_();
9062/**
9063 * An implementation of CompleteChildSource that uses a WriteTree in addition to any other server data or
9064 * old event caches available to calculate complete children.
9065 */
9066var WriteTreeCompleteChildSource = /** @class */ (function () {
9067 function WriteTreeCompleteChildSource(writes_, viewCache_, optCompleteServerCache_) {
9068 if (optCompleteServerCache_ === void 0) { optCompleteServerCache_ = null; }
9069 this.writes_ = writes_;
9070 this.viewCache_ = viewCache_;
9071 this.optCompleteServerCache_ = optCompleteServerCache_;
9072 }
9073 WriteTreeCompleteChildSource.prototype.getCompleteChild = function (childKey) {
9074 var node = this.viewCache_.eventCache;
9075 if (node.isCompleteForChild(childKey)) {
9076 return node.getNode().getImmediateChild(childKey);
9077 }
9078 else {
9079 var serverNode = this.optCompleteServerCache_ != null
9080 ? new CacheNode(this.optCompleteServerCache_, true, false)
9081 : this.viewCache_.serverCache;
9082 return writeTreeRefCalcCompleteChild(this.writes_, childKey, serverNode);
9083 }
9084 };
9085 WriteTreeCompleteChildSource.prototype.getChildAfterChild = function (index, child, reverse) {
9086 var completeServerData = this.optCompleteServerCache_ != null
9087 ? this.optCompleteServerCache_
9088 : viewCacheGetCompleteServerSnap(this.viewCache_);
9089 var nodes = writeTreeRefCalcIndexedSlice(this.writes_, completeServerData, child, 1, reverse, index);
9090 if (nodes.length === 0) {
9091 return null;
9092 }
9093 else {
9094 return nodes[0];
9095 }
9096 };
9097 return WriteTreeCompleteChildSource;
9098}());
9099
9100/**
9101 * @license
9102 * Copyright 2017 Google LLC
9103 *
9104 * Licensed under the Apache License, Version 2.0 (the "License");
9105 * you may not use this file except in compliance with the License.
9106 * You may obtain a copy of the License at
9107 *
9108 * http://www.apache.org/licenses/LICENSE-2.0
9109 *
9110 * Unless required by applicable law or agreed to in writing, software
9111 * distributed under the License is distributed on an "AS IS" BASIS,
9112 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9113 * See the License for the specific language governing permissions and
9114 * limitations under the License.
9115 */
9116function newViewProcessor(filter) {
9117 return { filter: filter };
9118}
9119function viewProcessorAssertIndexed(viewProcessor, viewCache) {
9120 util.assert(viewCache.eventCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Event snap not indexed');
9121 util.assert(viewCache.serverCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Server snap not indexed');
9122}
9123function viewProcessorApplyOperation(viewProcessor, oldViewCache, operation, writesCache, completeCache) {
9124 var accumulator = new ChildChangeAccumulator();
9125 var newViewCache, filterServerNode;
9126 if (operation.type === OperationType.OVERWRITE) {
9127 var overwrite = operation;
9128 if (overwrite.source.fromUser) {
9129 newViewCache = viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, accumulator);
9130 }
9131 else {
9132 util.assert(overwrite.source.fromServer, 'Unknown source.');
9133 // We filter the node if it's a tagged update or the node has been previously filtered and the
9134 // update is not at the root in which case it is ok (and necessary) to mark the node unfiltered
9135 // again
9136 filterServerNode =
9137 overwrite.source.tagged ||
9138 (oldViewCache.serverCache.isFiltered() && !pathIsEmpty(overwrite.path));
9139 newViewCache = viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, filterServerNode, accumulator);
9140 }
9141 }
9142 else if (operation.type === OperationType.MERGE) {
9143 var merge = operation;
9144 if (merge.source.fromUser) {
9145 newViewCache = viewProcessorApplyUserMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, accumulator);
9146 }
9147 else {
9148 util.assert(merge.source.fromServer, 'Unknown source.');
9149 // We filter the node if it's a tagged update or the node has been previously filtered
9150 filterServerNode =
9151 merge.source.tagged || oldViewCache.serverCache.isFiltered();
9152 newViewCache = viewProcessorApplyServerMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, filterServerNode, accumulator);
9153 }
9154 }
9155 else if (operation.type === OperationType.ACK_USER_WRITE) {
9156 var ackUserWrite = operation;
9157 if (!ackUserWrite.revert) {
9158 newViewCache = viewProcessorAckUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, ackUserWrite.affectedTree, writesCache, completeCache, accumulator);
9159 }
9160 else {
9161 newViewCache = viewProcessorRevertUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, writesCache, completeCache, accumulator);
9162 }
9163 }
9164 else if (operation.type === OperationType.LISTEN_COMPLETE) {
9165 newViewCache = viewProcessorListenComplete(viewProcessor, oldViewCache, operation.path, writesCache, accumulator);
9166 }
9167 else {
9168 throw util.assertionError('Unknown operation type: ' + operation.type);
9169 }
9170 var changes = accumulator.getChanges();
9171 viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, changes);
9172 return { viewCache: newViewCache, changes: changes };
9173}
9174function viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, accumulator) {
9175 var eventSnap = newViewCache.eventCache;
9176 if (eventSnap.isFullyInitialized()) {
9177 var isLeafOrEmpty = eventSnap.getNode().isLeafNode() || eventSnap.getNode().isEmpty();
9178 var oldCompleteSnap = viewCacheGetCompleteEventSnap(oldViewCache);
9179 if (accumulator.length > 0 ||
9180 !oldViewCache.eventCache.isFullyInitialized() ||
9181 (isLeafOrEmpty && !eventSnap.getNode().equals(oldCompleteSnap)) ||
9182 !eventSnap.getNode().getPriority().equals(oldCompleteSnap.getPriority())) {
9183 accumulator.push(changeValue(viewCacheGetCompleteEventSnap(newViewCache)));
9184 }
9185 }
9186}
9187function viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, viewCache, changePath, writesCache, source, accumulator) {
9188 var oldEventSnap = viewCache.eventCache;
9189 if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
9190 // we have a shadowing write, ignore changes
9191 return viewCache;
9192 }
9193 else {
9194 var newEventCache = void 0, serverNode = void 0;
9195 if (pathIsEmpty(changePath)) {
9196 // TODO: figure out how this plays with "sliding ack windows"
9197 util.assert(viewCache.serverCache.isFullyInitialized(), 'If change path is empty, we must have complete server data');
9198 if (viewCache.serverCache.isFiltered()) {
9199 // We need to special case this, because we need to only apply writes to complete children, or
9200 // we might end up raising events for incomplete children. If the server data is filtered deep
9201 // writes cannot be guaranteed to be complete
9202 var serverCache = viewCacheGetCompleteServerSnap(viewCache);
9203 var completeChildren = serverCache instanceof ChildrenNode
9204 ? serverCache
9205 : ChildrenNode.EMPTY_NODE;
9206 var completeEventChildren = writeTreeRefCalcCompleteEventChildren(writesCache, completeChildren);
9207 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeEventChildren, accumulator);
9208 }
9209 else {
9210 var completeNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9211 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeNode, accumulator);
9212 }
9213 }
9214 else {
9215 var childKey = pathGetFront(changePath);
9216 if (childKey === '.priority') {
9217 util.assert(pathGetLength(changePath) === 1, "Can't have a priority with additional path components");
9218 var oldEventNode = oldEventSnap.getNode();
9219 serverNode = viewCache.serverCache.getNode();
9220 // we might have overwrites for this priority
9221 var updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventNode, serverNode);
9222 if (updatedPriority != null) {
9223 newEventCache = viewProcessor.filter.updatePriority(oldEventNode, updatedPriority);
9224 }
9225 else {
9226 // priority didn't change, keep old node
9227 newEventCache = oldEventSnap.getNode();
9228 }
9229 }
9230 else {
9231 var childChangePath = pathPopFront(changePath);
9232 // update child
9233 var newEventChild = void 0;
9234 if (oldEventSnap.isCompleteForChild(childKey)) {
9235 serverNode = viewCache.serverCache.getNode();
9236 var eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventSnap.getNode(), serverNode);
9237 if (eventChildUpdate != null) {
9238 newEventChild = oldEventSnap
9239 .getNode()
9240 .getImmediateChild(childKey)
9241 .updateChild(childChangePath, eventChildUpdate);
9242 }
9243 else {
9244 // Nothing changed, just keep the old child
9245 newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
9246 }
9247 }
9248 else {
9249 newEventChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9250 }
9251 if (newEventChild != null) {
9252 newEventCache = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newEventChild, childChangePath, source, accumulator);
9253 }
9254 else {
9255 // no complete child available or no change
9256 newEventCache = oldEventSnap.getNode();
9257 }
9258 }
9259 }
9260 return viewCacheUpdateEventSnap(viewCache, newEventCache, oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath), viewProcessor.filter.filtersNodes());
9261 }
9262}
9263function viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, filterServerNode, accumulator) {
9264 var oldServerSnap = oldViewCache.serverCache;
9265 var newServerCache;
9266 var serverFilter = filterServerNode
9267 ? viewProcessor.filter
9268 : viewProcessor.filter.getIndexedFilter();
9269 if (pathIsEmpty(changePath)) {
9270 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), changedSnap, null);
9271 }
9272 else if (serverFilter.filtersNodes() && !oldServerSnap.isFiltered()) {
9273 // we want to filter the server node, but we didn't filter the server node yet, so simulate a full update
9274 var newServerNode = oldServerSnap
9275 .getNode()
9276 .updateChild(changePath, changedSnap);
9277 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), newServerNode, null);
9278 }
9279 else {
9280 var childKey = pathGetFront(changePath);
9281 if (!oldServerSnap.isCompleteForPath(changePath) &&
9282 pathGetLength(changePath) > 1) {
9283 // We don't update incomplete nodes with updates intended for other listeners
9284 return oldViewCache;
9285 }
9286 var childChangePath = pathPopFront(changePath);
9287 var childNode = oldServerSnap.getNode().getImmediateChild(childKey);
9288 var newChildNode = childNode.updateChild(childChangePath, changedSnap);
9289 if (childKey === '.priority') {
9290 newServerCache = serverFilter.updatePriority(oldServerSnap.getNode(), newChildNode);
9291 }
9292 else {
9293 newServerCache = serverFilter.updateChild(oldServerSnap.getNode(), childKey, newChildNode, childChangePath, NO_COMPLETE_CHILD_SOURCE, null);
9294 }
9295 }
9296 var newViewCache = viewCacheUpdateServerSnap(oldViewCache, newServerCache, oldServerSnap.isFullyInitialized() || pathIsEmpty(changePath), serverFilter.filtersNodes());
9297 var source = new WriteTreeCompleteChildSource(writesCache, newViewCache, completeCache);
9298 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, changePath, writesCache, source, accumulator);
9299}
9300function viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, accumulator) {
9301 var oldEventSnap = oldViewCache.eventCache;
9302 var newViewCache, newEventCache;
9303 var source = new WriteTreeCompleteChildSource(writesCache, oldViewCache, completeCache);
9304 if (pathIsEmpty(changePath)) {
9305 newEventCache = viewProcessor.filter.updateFullNode(oldViewCache.eventCache.getNode(), changedSnap, accumulator);
9306 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, true, viewProcessor.filter.filtersNodes());
9307 }
9308 else {
9309 var childKey = pathGetFront(changePath);
9310 if (childKey === '.priority') {
9311 newEventCache = viewProcessor.filter.updatePriority(oldViewCache.eventCache.getNode(), changedSnap);
9312 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, oldEventSnap.isFullyInitialized(), oldEventSnap.isFiltered());
9313 }
9314 else {
9315 var childChangePath = pathPopFront(changePath);
9316 var oldChild = oldEventSnap.getNode().getImmediateChild(childKey);
9317 var newChild = void 0;
9318 if (pathIsEmpty(childChangePath)) {
9319 // Child overwrite, we can replace the child
9320 newChild = changedSnap;
9321 }
9322 else {
9323 var childNode = source.getCompleteChild(childKey);
9324 if (childNode != null) {
9325 if (pathGetBack(childChangePath) === '.priority' &&
9326 childNode.getChild(pathParent(childChangePath)).isEmpty()) {
9327 // This is a priority update on an empty node. If this node exists on the server, the
9328 // server will send down the priority in the update, so ignore for now
9329 newChild = childNode;
9330 }
9331 else {
9332 newChild = childNode.updateChild(childChangePath, changedSnap);
9333 }
9334 }
9335 else {
9336 // There is no complete child node available
9337 newChild = ChildrenNode.EMPTY_NODE;
9338 }
9339 }
9340 if (!oldChild.equals(newChild)) {
9341 var newEventSnap = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newChild, childChangePath, source, accumulator);
9342 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventSnap, oldEventSnap.isFullyInitialized(), viewProcessor.filter.filtersNodes());
9343 }
9344 else {
9345 newViewCache = oldViewCache;
9346 }
9347 }
9348 }
9349 return newViewCache;
9350}
9351function viewProcessorCacheHasChild(viewCache, childKey) {
9352 return viewCache.eventCache.isCompleteForChild(childKey);
9353}
9354function viewProcessorApplyUserMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, accumulator) {
9355 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9356 // window leaving room for new items. It's important we process these changes first, so we
9357 // iterate the changes twice, first processing any that affect items currently in view.
9358 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9359 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9360 // not the other.
9361 var curViewCache = viewCache;
9362 changedChildren.foreach(function (relativePath, childNode) {
9363 var writePath = pathChild(path, relativePath);
9364 if (viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9365 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9366 }
9367 });
9368 changedChildren.foreach(function (relativePath, childNode) {
9369 var writePath = pathChild(path, relativePath);
9370 if (!viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9371 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9372 }
9373 });
9374 return curViewCache;
9375}
9376function viewProcessorApplyMerge(viewProcessor, node, merge) {
9377 merge.foreach(function (relativePath, childNode) {
9378 node = node.updateChild(relativePath, childNode);
9379 });
9380 return node;
9381}
9382function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, filterServerNode, accumulator) {
9383 // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
9384 // wait for the complete data update coming soon.
9385 if (viewCache.serverCache.getNode().isEmpty() &&
9386 !viewCache.serverCache.isFullyInitialized()) {
9387 return viewCache;
9388 }
9389 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9390 // window leaving room for new items. It's important we process these changes first, so we
9391 // iterate the changes twice, first processing any that affect items currently in view.
9392 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9393 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9394 // not the other.
9395 var curViewCache = viewCache;
9396 var viewMergeTree;
9397 if (pathIsEmpty(path)) {
9398 viewMergeTree = changedChildren;
9399 }
9400 else {
9401 viewMergeTree = new ImmutableTree(null).setTree(path, changedChildren);
9402 }
9403 var serverNode = viewCache.serverCache.getNode();
9404 viewMergeTree.children.inorderTraversal(function (childKey, childTree) {
9405 if (serverNode.hasChild(childKey)) {
9406 var serverChild = viewCache.serverCache
9407 .getNode()
9408 .getImmediateChild(childKey);
9409 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childTree);
9410 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9411 }
9412 });
9413 viewMergeTree.children.inorderTraversal(function (childKey, childMergeTree) {
9414 var isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9415 childMergeTree.value === undefined;
9416 if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9417 var serverChild = viewCache.serverCache
9418 .getNode()
9419 .getImmediateChild(childKey);
9420 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childMergeTree);
9421 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9422 }
9423 });
9424 return curViewCache;
9425}
9426function viewProcessorAckUserWrite(viewProcessor, viewCache, ackPath, affectedTree, writesCache, completeCache, accumulator) {
9427 if (writeTreeRefShadowingWrite(writesCache, ackPath) != null) {
9428 return viewCache;
9429 }
9430 // Only filter server node if it is currently filtered
9431 var filterServerNode = viewCache.serverCache.isFiltered();
9432 // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
9433 // now that it won't be shadowed.
9434 var serverCache = viewCache.serverCache;
9435 if (affectedTree.value != null) {
9436 // This is an overwrite.
9437 if ((pathIsEmpty(ackPath) && serverCache.isFullyInitialized()) ||
9438 serverCache.isCompleteForPath(ackPath)) {
9439 return viewProcessorApplyServerOverwrite(viewProcessor, viewCache, ackPath, serverCache.getNode().getChild(ackPath), writesCache, completeCache, filterServerNode, accumulator);
9440 }
9441 else if (pathIsEmpty(ackPath)) {
9442 // This is a goofy edge case where we are acking data at this location but don't have full data. We
9443 // should just re-apply whatever we have in our cache as a merge.
9444 var changedChildren_1 = new ImmutableTree(null);
9445 serverCache.getNode().forEachChild(KEY_INDEX, function (name, node) {
9446 changedChildren_1 = changedChildren_1.set(new Path(name), node);
9447 });
9448 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_1, writesCache, completeCache, filterServerNode, accumulator);
9449 }
9450 else {
9451 return viewCache;
9452 }
9453 }
9454 else {
9455 // This is a merge.
9456 var changedChildren_2 = new ImmutableTree(null);
9457 affectedTree.foreach(function (mergePath, value) {
9458 var serverCachePath = pathChild(ackPath, mergePath);
9459 if (serverCache.isCompleteForPath(serverCachePath)) {
9460 changedChildren_2 = changedChildren_2.set(mergePath, serverCache.getNode().getChild(serverCachePath));
9461 }
9462 });
9463 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_2, writesCache, completeCache, filterServerNode, accumulator);
9464 }
9465}
9466function viewProcessorListenComplete(viewProcessor, viewCache, path, writesCache, accumulator) {
9467 var oldServerNode = viewCache.serverCache;
9468 var newViewCache = viewCacheUpdateServerSnap(viewCache, oldServerNode.getNode(), oldServerNode.isFullyInitialized() || pathIsEmpty(path), oldServerNode.isFiltered());
9469 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, path, writesCache, NO_COMPLETE_CHILD_SOURCE, accumulator);
9470}
9471function viewProcessorRevertUserWrite(viewProcessor, viewCache, path, writesCache, completeServerCache, accumulator) {
9472 var complete;
9473 if (writeTreeRefShadowingWrite(writesCache, path) != null) {
9474 return viewCache;
9475 }
9476 else {
9477 var source = new WriteTreeCompleteChildSource(writesCache, viewCache, completeServerCache);
9478 var oldEventCache = viewCache.eventCache.getNode();
9479 var newEventCache = void 0;
9480 if (pathIsEmpty(path) || pathGetFront(path) === '.priority') {
9481 var newNode = void 0;
9482 if (viewCache.serverCache.isFullyInitialized()) {
9483 newNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9484 }
9485 else {
9486 var serverChildren = viewCache.serverCache.getNode();
9487 util.assert(serverChildren instanceof ChildrenNode, 'serverChildren would be complete if leaf node');
9488 newNode = writeTreeRefCalcCompleteEventChildren(writesCache, serverChildren);
9489 }
9490 newNode = newNode;
9491 newEventCache = viewProcessor.filter.updateFullNode(oldEventCache, newNode, accumulator);
9492 }
9493 else {
9494 var childKey = pathGetFront(path);
9495 var newChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9496 if (newChild == null &&
9497 viewCache.serverCache.isCompleteForChild(childKey)) {
9498 newChild = oldEventCache.getImmediateChild(childKey);
9499 }
9500 if (newChild != null) {
9501 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, newChild, pathPopFront(path), source, accumulator);
9502 }
9503 else if (viewCache.eventCache.getNode().hasChild(childKey)) {
9504 // No complete child available, delete the existing one, if any
9505 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, ChildrenNode.EMPTY_NODE, pathPopFront(path), source, accumulator);
9506 }
9507 else {
9508 newEventCache = oldEventCache;
9509 }
9510 if (newEventCache.isEmpty() &&
9511 viewCache.serverCache.isFullyInitialized()) {
9512 // We might have reverted all child writes. Maybe the old event was a leaf node
9513 complete = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9514 if (complete.isLeafNode()) {
9515 newEventCache = viewProcessor.filter.updateFullNode(newEventCache, complete, accumulator);
9516 }
9517 }
9518 }
9519 complete =
9520 viewCache.serverCache.isFullyInitialized() ||
9521 writeTreeRefShadowingWrite(writesCache, newEmptyPath()) != null;
9522 return viewCacheUpdateEventSnap(viewCache, newEventCache, complete, viewProcessor.filter.filtersNodes());
9523 }
9524}
9525
9526/**
9527 * @license
9528 * Copyright 2017 Google LLC
9529 *
9530 * Licensed under the Apache License, Version 2.0 (the "License");
9531 * you may not use this file except in compliance with the License.
9532 * You may obtain a copy of the License at
9533 *
9534 * http://www.apache.org/licenses/LICENSE-2.0
9535 *
9536 * Unless required by applicable law or agreed to in writing, software
9537 * distributed under the License is distributed on an "AS IS" BASIS,
9538 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9539 * See the License for the specific language governing permissions and
9540 * limitations under the License.
9541 */
9542/**
9543 * A view represents a specific location and query that has 1 or more event registrations.
9544 *
9545 * It does several things:
9546 * - Maintains the list of event registrations for this location/query.
9547 * - Maintains a cache of the data visible for this location/query.
9548 * - Applies new operations (via applyOperation), updates the cache, and based on the event
9549 * registrations returns the set of events to be raised.
9550 */
9551var View = /** @class */ (function () {
9552 function View(query_, initialViewCache) {
9553 this.query_ = query_;
9554 this.eventRegistrations_ = [];
9555 var params = this.query_._queryParams;
9556 var indexFilter = new IndexedFilter(params.getIndex());
9557 var filter = queryParamsGetNodeFilter(params);
9558 this.processor_ = newViewProcessor(filter);
9559 var initialServerCache = initialViewCache.serverCache;
9560 var initialEventCache = initialViewCache.eventCache;
9561 // Don't filter server node with other filter than index, wait for tagged listen
9562 var serverSnap = indexFilter.updateFullNode(ChildrenNode.EMPTY_NODE, initialServerCache.getNode(), null);
9563 var eventSnap = filter.updateFullNode(ChildrenNode.EMPTY_NODE, initialEventCache.getNode(), null);
9564 var newServerCache = new CacheNode(serverSnap, initialServerCache.isFullyInitialized(), indexFilter.filtersNodes());
9565 var newEventCache = new CacheNode(eventSnap, initialEventCache.isFullyInitialized(), filter.filtersNodes());
9566 this.viewCache_ = newViewCache(newEventCache, newServerCache);
9567 this.eventGenerator_ = new EventGenerator(this.query_);
9568 }
9569 Object.defineProperty(View.prototype, "query", {
9570 get: function () {
9571 return this.query_;
9572 },
9573 enumerable: false,
9574 configurable: true
9575 });
9576 return View;
9577}());
9578function viewGetServerCache(view) {
9579 return view.viewCache_.serverCache.getNode();
9580}
9581function viewGetCompleteNode(view) {
9582 return viewCacheGetCompleteEventSnap(view.viewCache_);
9583}
9584function viewGetCompleteServerCache(view, path) {
9585 var cache = viewCacheGetCompleteServerSnap(view.viewCache_);
9586 if (cache) {
9587 // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
9588 // we need to see if it contains the child we're interested in.
9589 if (view.query._queryParams.loadsAllData() ||
9590 (!pathIsEmpty(path) &&
9591 !cache.getImmediateChild(pathGetFront(path)).isEmpty())) {
9592 return cache.getChild(path);
9593 }
9594 }
9595 return null;
9596}
9597function viewIsEmpty(view) {
9598 return view.eventRegistrations_.length === 0;
9599}
9600function viewAddEventRegistration(view, eventRegistration) {
9601 view.eventRegistrations_.push(eventRegistration);
9602}
9603/**
9604 * @param eventRegistration - If null, remove all callbacks.
9605 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9606 * @returns Cancel events, if cancelError was provided.
9607 */
9608function viewRemoveEventRegistration(view, eventRegistration, cancelError) {
9609 var cancelEvents = [];
9610 if (cancelError) {
9611 util.assert(eventRegistration == null, 'A cancel should cancel all event registrations.');
9612 var path_1 = view.query._path;
9613 view.eventRegistrations_.forEach(function (registration) {
9614 var maybeEvent = registration.createCancelEvent(cancelError, path_1);
9615 if (maybeEvent) {
9616 cancelEvents.push(maybeEvent);
9617 }
9618 });
9619 }
9620 if (eventRegistration) {
9621 var remaining = [];
9622 for (var i = 0; i < view.eventRegistrations_.length; ++i) {
9623 var existing = view.eventRegistrations_[i];
9624 if (!existing.matches(eventRegistration)) {
9625 remaining.push(existing);
9626 }
9627 else if (eventRegistration.hasAnyCallback()) {
9628 // We're removing just this one
9629 remaining = remaining.concat(view.eventRegistrations_.slice(i + 1));
9630 break;
9631 }
9632 }
9633 view.eventRegistrations_ = remaining;
9634 }
9635 else {
9636 view.eventRegistrations_ = [];
9637 }
9638 return cancelEvents;
9639}
9640/**
9641 * Applies the given Operation, updates our cache, and returns the appropriate events.
9642 */
9643function viewApplyOperation(view, operation, writesCache, completeServerCache) {
9644 if (operation.type === OperationType.MERGE &&
9645 operation.source.queryId !== null) {
9646 util.assert(viewCacheGetCompleteServerSnap(view.viewCache_), 'We should always have a full cache before handling merges');
9647 util.assert(viewCacheGetCompleteEventSnap(view.viewCache_), 'Missing event cache, even though we have a server cache');
9648 }
9649 var oldViewCache = view.viewCache_;
9650 var result = viewProcessorApplyOperation(view.processor_, oldViewCache, operation, writesCache, completeServerCache);
9651 viewProcessorAssertIndexed(view.processor_, result.viewCache);
9652 util.assert(result.viewCache.serverCache.isFullyInitialized() ||
9653 !oldViewCache.serverCache.isFullyInitialized(), 'Once a server snap is complete, it should never go back');
9654 view.viewCache_ = result.viewCache;
9655 return viewGenerateEventsForChanges_(view, result.changes, result.viewCache.eventCache.getNode(), null);
9656}
9657function viewGetInitialEvents(view, registration) {
9658 var eventSnap = view.viewCache_.eventCache;
9659 var initialChanges = [];
9660 if (!eventSnap.getNode().isLeafNode()) {
9661 var eventNode = eventSnap.getNode();
9662 eventNode.forEachChild(PRIORITY_INDEX, function (key, childNode) {
9663 initialChanges.push(changeChildAdded(key, childNode));
9664 });
9665 }
9666 if (eventSnap.isFullyInitialized()) {
9667 initialChanges.push(changeValue(eventSnap.getNode()));
9668 }
9669 return viewGenerateEventsForChanges_(view, initialChanges, eventSnap.getNode(), registration);
9670}
9671function viewGenerateEventsForChanges_(view, changes, eventCache, eventRegistration) {
9672 var registrations = eventRegistration
9673 ? [eventRegistration]
9674 : view.eventRegistrations_;
9675 return eventGeneratorGenerateEventsForChanges(view.eventGenerator_, changes, eventCache, registrations);
9676}
9677
9678/**
9679 * @license
9680 * Copyright 2017 Google LLC
9681 *
9682 * Licensed under the Apache License, Version 2.0 (the "License");
9683 * you may not use this file except in compliance with the License.
9684 * You may obtain a copy of the License at
9685 *
9686 * http://www.apache.org/licenses/LICENSE-2.0
9687 *
9688 * Unless required by applicable law or agreed to in writing, software
9689 * distributed under the License is distributed on an "AS IS" BASIS,
9690 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9691 * See the License for the specific language governing permissions and
9692 * limitations under the License.
9693 */
9694var referenceConstructor$1;
9695/**
9696 * SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
9697 * maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
9698 * and user writes (set, transaction, update).
9699 *
9700 * It's responsible for:
9701 * - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
9702 * - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
9703 * applyUserOverwrite, etc.)
9704 */
9705var SyncPoint = /** @class */ (function () {
9706 function SyncPoint() {
9707 /**
9708 * The Views being tracked at this location in the tree, stored as a map where the key is a
9709 * queryId and the value is the View for that query.
9710 *
9711 * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
9712 */
9713 this.views = new Map();
9714 }
9715 return SyncPoint;
9716}());
9717function syncPointSetReferenceConstructor(val) {
9718 util.assert(!referenceConstructor$1, '__referenceConstructor has already been defined');
9719 referenceConstructor$1 = val;
9720}
9721function syncPointGetReferenceConstructor() {
9722 util.assert(referenceConstructor$1, 'Reference.ts has not been loaded');
9723 return referenceConstructor$1;
9724}
9725function syncPointIsEmpty(syncPoint) {
9726 return syncPoint.views.size === 0;
9727}
9728function syncPointApplyOperation(syncPoint, operation, writesCache, optCompleteServerCache) {
9729 var e_1, _a;
9730 var queryId = operation.source.queryId;
9731 if (queryId !== null) {
9732 var view = syncPoint.views.get(queryId);
9733 util.assert(view != null, 'SyncTree gave us an op for an invalid query.');
9734 return viewApplyOperation(view, operation, writesCache, optCompleteServerCache);
9735 }
9736 else {
9737 var events = [];
9738 try {
9739 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9740 var view = _c.value;
9741 events = events.concat(viewApplyOperation(view, operation, writesCache, optCompleteServerCache));
9742 }
9743 }
9744 catch (e_1_1) { e_1 = { error: e_1_1 }; }
9745 finally {
9746 try {
9747 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9748 }
9749 finally { if (e_1) throw e_1.error; }
9750 }
9751 return events;
9752 }
9753}
9754/**
9755 * Get a view for the specified query.
9756 *
9757 * @param query - The query to return a view for
9758 * @param writesCache
9759 * @param serverCache
9760 * @param serverCacheComplete
9761 * @returns Events to raise.
9762 */
9763function syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete) {
9764 var queryId = query._queryIdentifier;
9765 var view = syncPoint.views.get(queryId);
9766 if (!view) {
9767 // TODO: make writesCache take flag for complete server node
9768 var eventCache = writeTreeRefCalcCompleteEventCache(writesCache, serverCacheComplete ? serverCache : null);
9769 var eventCacheComplete = false;
9770 if (eventCache) {
9771 eventCacheComplete = true;
9772 }
9773 else if (serverCache instanceof ChildrenNode) {
9774 eventCache = writeTreeRefCalcCompleteEventChildren(writesCache, serverCache);
9775 eventCacheComplete = false;
9776 }
9777 else {
9778 eventCache = ChildrenNode.EMPTY_NODE;
9779 eventCacheComplete = false;
9780 }
9781 var viewCache = newViewCache(new CacheNode(eventCache, eventCacheComplete, false), new CacheNode(serverCache, serverCacheComplete, false));
9782 return new View(query, viewCache);
9783 }
9784 return view;
9785}
9786/**
9787 * Add an event callback for the specified query.
9788 *
9789 * @param query
9790 * @param eventRegistration
9791 * @param writesCache
9792 * @param serverCache - Complete server cache, if we have it.
9793 * @param serverCacheComplete
9794 * @returns Events to raise.
9795 */
9796function syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete) {
9797 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9798 if (!syncPoint.views.has(query._queryIdentifier)) {
9799 syncPoint.views.set(query._queryIdentifier, view);
9800 }
9801 // This is guaranteed to exist now, we just created anything that was missing
9802 viewAddEventRegistration(view, eventRegistration);
9803 return viewGetInitialEvents(view, eventRegistration);
9804}
9805/**
9806 * Remove event callback(s). Return cancelEvents if a cancelError is specified.
9807 *
9808 * If query is the default query, we'll check all views for the specified eventRegistration.
9809 * If eventRegistration is null, we'll remove all callbacks for the specified view(s).
9810 *
9811 * @param eventRegistration - If null, remove all callbacks.
9812 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9813 * @returns removed queries and any cancel events
9814 */
9815function syncPointRemoveEventRegistration(syncPoint, query, eventRegistration, cancelError) {
9816 var e_2, _a;
9817 var queryId = query._queryIdentifier;
9818 var removed = [];
9819 var cancelEvents = [];
9820 var hadCompleteView = syncPointHasCompleteView(syncPoint);
9821 if (queryId === 'default') {
9822 try {
9823 // When you do ref.off(...), we search all views for the registration to remove.
9824 for (var _b = tslib.__values(syncPoint.views.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
9825 var _d = tslib.__read(_c.value, 2), viewQueryId = _d[0], view = _d[1];
9826 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9827 if (viewIsEmpty(view)) {
9828 syncPoint.views.delete(viewQueryId);
9829 // We'll deal with complete views later.
9830 if (!view.query._queryParams.loadsAllData()) {
9831 removed.push(view.query);
9832 }
9833 }
9834 }
9835 }
9836 catch (e_2_1) { e_2 = { error: e_2_1 }; }
9837 finally {
9838 try {
9839 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9840 }
9841 finally { if (e_2) throw e_2.error; }
9842 }
9843 }
9844 else {
9845 // remove the callback from the specific view.
9846 var view = syncPoint.views.get(queryId);
9847 if (view) {
9848 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9849 if (viewIsEmpty(view)) {
9850 syncPoint.views.delete(queryId);
9851 // We'll deal with complete views later.
9852 if (!view.query._queryParams.loadsAllData()) {
9853 removed.push(view.query);
9854 }
9855 }
9856 }
9857 }
9858 if (hadCompleteView && !syncPointHasCompleteView(syncPoint)) {
9859 // We removed our last complete view.
9860 removed.push(new (syncPointGetReferenceConstructor())(query._repo, query._path));
9861 }
9862 return { removed: removed, events: cancelEvents };
9863}
9864function syncPointGetQueryViews(syncPoint) {
9865 var e_3, _a;
9866 var result = [];
9867 try {
9868 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9869 var view = _c.value;
9870 if (!view.query._queryParams.loadsAllData()) {
9871 result.push(view);
9872 }
9873 }
9874 }
9875 catch (e_3_1) { e_3 = { error: e_3_1 }; }
9876 finally {
9877 try {
9878 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9879 }
9880 finally { if (e_3) throw e_3.error; }
9881 }
9882 return result;
9883}
9884/**
9885 * @param path - The path to the desired complete snapshot
9886 * @returns A complete cache, if it exists
9887 */
9888function syncPointGetCompleteServerCache(syncPoint, path) {
9889 var e_4, _a;
9890 var serverCache = null;
9891 try {
9892 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9893 var view = _c.value;
9894 serverCache = serverCache || viewGetCompleteServerCache(view, path);
9895 }
9896 }
9897 catch (e_4_1) { e_4 = { error: e_4_1 }; }
9898 finally {
9899 try {
9900 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9901 }
9902 finally { if (e_4) throw e_4.error; }
9903 }
9904 return serverCache;
9905}
9906function syncPointViewForQuery(syncPoint, query) {
9907 var params = query._queryParams;
9908 if (params.loadsAllData()) {
9909 return syncPointGetCompleteView(syncPoint);
9910 }
9911 else {
9912 var queryId = query._queryIdentifier;
9913 return syncPoint.views.get(queryId);
9914 }
9915}
9916function syncPointViewExistsForQuery(syncPoint, query) {
9917 return syncPointViewForQuery(syncPoint, query) != null;
9918}
9919function syncPointHasCompleteView(syncPoint) {
9920 return syncPointGetCompleteView(syncPoint) != null;
9921}
9922function syncPointGetCompleteView(syncPoint) {
9923 var e_5, _a;
9924 try {
9925 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9926 var view = _c.value;
9927 if (view.query._queryParams.loadsAllData()) {
9928 return view;
9929 }
9930 }
9931 }
9932 catch (e_5_1) { e_5 = { error: e_5_1 }; }
9933 finally {
9934 try {
9935 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9936 }
9937 finally { if (e_5) throw e_5.error; }
9938 }
9939 return null;
9940}
9941
9942/**
9943 * @license
9944 * Copyright 2017 Google LLC
9945 *
9946 * Licensed under the Apache License, Version 2.0 (the "License");
9947 * you may not use this file except in compliance with the License.
9948 * You may obtain a copy of the License at
9949 *
9950 * http://www.apache.org/licenses/LICENSE-2.0
9951 *
9952 * Unless required by applicable law or agreed to in writing, software
9953 * distributed under the License is distributed on an "AS IS" BASIS,
9954 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9955 * See the License for the specific language governing permissions and
9956 * limitations under the License.
9957 */
9958var referenceConstructor;
9959function syncTreeSetReferenceConstructor(val) {
9960 util.assert(!referenceConstructor, '__referenceConstructor has already been defined');
9961 referenceConstructor = val;
9962}
9963function syncTreeGetReferenceConstructor() {
9964 util.assert(referenceConstructor, 'Reference.ts has not been loaded');
9965 return referenceConstructor;
9966}
9967/**
9968 * Static tracker for next query tag.
9969 */
9970var syncTreeNextQueryTag_ = 1;
9971/**
9972 * SyncTree is the central class for managing event callback registration, data caching, views
9973 * (query processing), and event generation. There are typically two SyncTree instances for
9974 * each Repo, one for the normal Firebase data, and one for the .info data.
9975 *
9976 * It has a number of responsibilities, including:
9977 * - Tracking all user event callbacks (registered via addEventRegistration() and removeEventRegistration()).
9978 * - Applying and caching data changes for user set(), transaction(), and update() calls
9979 * (applyUserOverwrite(), applyUserMerge()).
9980 * - Applying and caching data changes for server data changes (applyServerOverwrite(),
9981 * applyServerMerge()).
9982 * - Generating user-facing events for server and user changes (all of the apply* methods
9983 * return the set of events that need to be raised as a result).
9984 * - Maintaining the appropriate set of server listens to ensure we are always subscribed
9985 * to the correct set of paths and queries to satisfy the current set of user event
9986 * callbacks (listens are started/stopped using the provided listenProvider).
9987 *
9988 * NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
9989 * events are returned to the caller rather than raised synchronously.
9990 *
9991 */
9992var SyncTree = /** @class */ (function () {
9993 /**
9994 * @param listenProvider_ - Used by SyncTree to start / stop listening
9995 * to server data.
9996 */
9997 function SyncTree(listenProvider_) {
9998 this.listenProvider_ = listenProvider_;
9999 /**
10000 * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
10001 */
10002 this.syncPointTree_ = new ImmutableTree(null);
10003 /**
10004 * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
10005 */
10006 this.pendingWriteTree_ = newWriteTree();
10007 this.tagToQueryMap = new Map();
10008 this.queryToTagMap = new Map();
10009 }
10010 return SyncTree;
10011}());
10012/**
10013 * Apply the data changes for a user-generated set() or transaction() call.
10014 *
10015 * @returns Events to raise.
10016 */
10017function syncTreeApplyUserOverwrite(syncTree, path, newData, writeId, visible) {
10018 // Record pending write.
10019 writeTreeAddOverwrite(syncTree.pendingWriteTree_, path, newData, writeId, visible);
10020 if (!visible) {
10021 return [];
10022 }
10023 else {
10024 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceUser(), path, newData));
10025 }
10026}
10027/**
10028 * Apply the data from a user-generated update() call
10029 *
10030 * @returns Events to raise.
10031 */
10032function syncTreeApplyUserMerge(syncTree, path, changedChildren, writeId) {
10033 // Record pending merge.
10034 writeTreeAddMerge(syncTree.pendingWriteTree_, path, changedChildren, writeId);
10035 var changeTree = ImmutableTree.fromObject(changedChildren);
10036 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceUser(), path, changeTree));
10037}
10038/**
10039 * Acknowledge a pending user write that was previously registered with applyUserOverwrite() or applyUserMerge().
10040 *
10041 * @param revert - True if the given write failed and needs to be reverted
10042 * @returns Events to raise.
10043 */
10044function syncTreeAckUserWrite(syncTree, writeId, revert) {
10045 if (revert === void 0) { revert = false; }
10046 var write = writeTreeGetWrite(syncTree.pendingWriteTree_, writeId);
10047 var needToReevaluate = writeTreeRemoveWrite(syncTree.pendingWriteTree_, writeId);
10048 if (!needToReevaluate) {
10049 return [];
10050 }
10051 else {
10052 var affectedTree_1 = new ImmutableTree(null);
10053 if (write.snap != null) {
10054 // overwrite
10055 affectedTree_1 = affectedTree_1.set(newEmptyPath(), true);
10056 }
10057 else {
10058 each(write.children, function (pathString) {
10059 affectedTree_1 = affectedTree_1.set(new Path(pathString), true);
10060 });
10061 }
10062 return syncTreeApplyOperationToSyncPoints_(syncTree, new AckUserWrite(write.path, affectedTree_1, revert));
10063 }
10064}
10065/**
10066 * Apply new server data for the specified path..
10067 *
10068 * @returns Events to raise.
10069 */
10070function syncTreeApplyServerOverwrite(syncTree, path, newData) {
10071 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceServer(), path, newData));
10072}
10073/**
10074 * Apply new server data to be merged in at the specified path.
10075 *
10076 * @returns Events to raise.
10077 */
10078function syncTreeApplyServerMerge(syncTree, path, changedChildren) {
10079 var changeTree = ImmutableTree.fromObject(changedChildren);
10080 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceServer(), path, changeTree));
10081}
10082/**
10083 * Apply a listen complete for a query
10084 *
10085 * @returns Events to raise.
10086 */
10087function syncTreeApplyListenComplete(syncTree, path) {
10088 return syncTreeApplyOperationToSyncPoints_(syncTree, new ListenComplete(newOperationSourceServer(), path));
10089}
10090/**
10091 * Apply a listen complete for a tagged query
10092 *
10093 * @returns Events to raise.
10094 */
10095function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
10096 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10097 if (queryKey) {
10098 var r = syncTreeParseQueryKey_(queryKey);
10099 var queryPath = r.path, queryId = r.queryId;
10100 var relativePath = newRelativePath(queryPath, path);
10101 var op = new ListenComplete(newOperationSourceServerTaggedQuery(queryId), relativePath);
10102 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10103 }
10104 else {
10105 // We've already removed the query. No big deal, ignore the update
10106 return [];
10107 }
10108}
10109/**
10110 * Remove event callback(s).
10111 *
10112 * If query is the default query, we'll check all queries for the specified eventRegistration.
10113 * If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
10114 *
10115 * @param eventRegistration - If null, all callbacks are removed.
10116 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
10117 * @returns Cancel events, if cancelError was provided.
10118 */
10119function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError) {
10120 // Find the syncPoint first. Then deal with whether or not it has matching listeners
10121 var path = query._path;
10122 var maybeSyncPoint = syncTree.syncPointTree_.get(path);
10123 var cancelEvents = [];
10124 // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
10125 // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
10126 // not loadsAllData().
10127 if (maybeSyncPoint &&
10128 (query._queryIdentifier === 'default' ||
10129 syncPointViewExistsForQuery(maybeSyncPoint, query))) {
10130 var removedAndEvents = syncPointRemoveEventRegistration(maybeSyncPoint, query, eventRegistration, cancelError);
10131 if (syncPointIsEmpty(maybeSyncPoint)) {
10132 syncTree.syncPointTree_ = syncTree.syncPointTree_.remove(path);
10133 }
10134 var removed = removedAndEvents.removed;
10135 cancelEvents = removedAndEvents.events;
10136 // We may have just removed one of many listeners and can short-circuit this whole process
10137 // We may also not have removed a default listener, in which case all of the descendant listeners should already be
10138 // properly set up.
10139 //
10140 // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
10141 // queryId === 'default'
10142 var removingDefault = -1 !==
10143 removed.findIndex(function (query) {
10144 return query._queryParams.loadsAllData();
10145 });
10146 var covered = syncTree.syncPointTree_.findOnPath(path, function (relativePath, parentSyncPoint) {
10147 return syncPointHasCompleteView(parentSyncPoint);
10148 });
10149 if (removingDefault && !covered) {
10150 var subtree = syncTree.syncPointTree_.subtree(path);
10151 // There are potentially child listeners. Determine what if any listens we need to send before executing the
10152 // removal
10153 if (!subtree.isEmpty()) {
10154 // We need to fold over our subtree and collect the listeners to send
10155 var newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
10156 // Ok, we've collected all the listens we need. Set them up.
10157 for (var i = 0; i < newViews.length; ++i) {
10158 var view = newViews[i], newQuery = view.query;
10159 var listener = syncTreeCreateListenerForView_(syncTree, view);
10160 syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery_(syncTree, newQuery), listener.hashFn, listener.onComplete);
10161 }
10162 }
10163 }
10164 // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
10165 // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
10166 // Also, note that if we have a cancelError, it's already been removed at the provider level.
10167 if (!covered && removed.length > 0 && !cancelError) {
10168 // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
10169 // default. Otherwise, we need to iterate through and cancel each individual query
10170 if (removingDefault) {
10171 // We don't tag default listeners
10172 var defaultTag = null;
10173 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
10174 }
10175 else {
10176 removed.forEach(function (queryToRemove) {
10177 var tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
10178 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
10179 });
10180 }
10181 }
10182 // Now, clear all of the tags we're tracking for the removed listens
10183 syncTreeRemoveTags_(syncTree, removed);
10184 }
10185 return cancelEvents;
10186}
10187/**
10188 * This function was added to support non-listener queries,
10189 * specifically for use in repoGetValue. It sets up all the same
10190 * local cache data-structures (SyncPoint + View) that are
10191 * needed for listeners without installing an event registration.
10192 * If `query` is not `loadsAllData`, it will also provision a tag for
10193 * the query so that query results can be merged into the sync
10194 * tree using existing logic for tagged listener queries.
10195 *
10196 * @param syncTree - Synctree to add the query to.
10197 * @param query - Query to register
10198 * @returns tag as a string if query is not a default query, null if query is not.
10199 */
10200function syncTreeRegisterQuery(syncTree, query) {
10201 var _a = syncTreeRegisterSyncPoint(query, syncTree), syncPoint = _a.syncPoint, serverCache = _a.serverCache, writesCache = _a.writesCache, serverCacheComplete = _a.serverCacheComplete;
10202 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
10203 if (!syncPoint.views.has(query._queryIdentifier)) {
10204 syncPoint.views.set(query._queryIdentifier, view);
10205 }
10206 if (!query._queryParams.loadsAllData()) {
10207 return syncTreeTagForQuery_(syncTree, query);
10208 }
10209 return null;
10210}
10211/**
10212 * Apply new server data for the specified tagged query.
10213 *
10214 * @returns Events to raise.
10215 */
10216function syncTreeApplyTaggedQueryOverwrite(syncTree, path, snap, tag) {
10217 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10218 if (queryKey != null) {
10219 var r = syncTreeParseQueryKey_(queryKey);
10220 var queryPath = r.path, queryId = r.queryId;
10221 var relativePath = newRelativePath(queryPath, path);
10222 var op = new Overwrite(newOperationSourceServerTaggedQuery(queryId), relativePath, snap);
10223 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10224 }
10225 else {
10226 // Query must have been removed already
10227 return [];
10228 }
10229}
10230/**
10231 * Apply server data to be merged in for the specified tagged query.
10232 *
10233 * @returns Events to raise.
10234 */
10235function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
10236 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10237 if (queryKey) {
10238 var r = syncTreeParseQueryKey_(queryKey);
10239 var queryPath = r.path, queryId = r.queryId;
10240 var relativePath = newRelativePath(queryPath, path);
10241 var changeTree = ImmutableTree.fromObject(changedChildren);
10242 var op = new Merge(newOperationSourceServerTaggedQuery(queryId), relativePath, changeTree);
10243 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10244 }
10245 else {
10246 // We've already removed the query. No big deal, ignore the update
10247 return [];
10248 }
10249}
10250/**
10251 * Creates a new syncpoint for a query and creates a tag if the view doesn't exist.
10252 * Extracted from addEventRegistration to allow `repoGetValue` to properly set up the SyncTree
10253 * without actually listening on a query.
10254 */
10255function syncTreeRegisterSyncPoint(query, syncTree) {
10256 var path = query._path;
10257 var serverCache = null;
10258 var foundAncestorDefaultView = false;
10259 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10260 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10261 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10262 var relativePath = newRelativePath(pathToSyncPoint, path);
10263 serverCache =
10264 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10265 foundAncestorDefaultView =
10266 foundAncestorDefaultView || syncPointHasCompleteView(sp);
10267 });
10268 var syncPoint = syncTree.syncPointTree_.get(path);
10269 if (!syncPoint) {
10270 syncPoint = new SyncPoint();
10271 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10272 }
10273 else {
10274 foundAncestorDefaultView =
10275 foundAncestorDefaultView || syncPointHasCompleteView(syncPoint);
10276 serverCache =
10277 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10278 }
10279 var serverCacheComplete;
10280 if (serverCache != null) {
10281 serverCacheComplete = true;
10282 }
10283 else {
10284 serverCacheComplete = false;
10285 serverCache = ChildrenNode.EMPTY_NODE;
10286 var subtree = syncTree.syncPointTree_.subtree(path);
10287 subtree.foreachChild(function (childName, childSyncPoint) {
10288 var completeCache = syncPointGetCompleteServerCache(childSyncPoint, newEmptyPath());
10289 if (completeCache) {
10290 serverCache = serverCache.updateImmediateChild(childName, completeCache);
10291 }
10292 });
10293 }
10294 var viewAlreadyExists = syncPointViewExistsForQuery(syncPoint, query);
10295 if (!viewAlreadyExists && !query._queryParams.loadsAllData()) {
10296 // We need to track a tag for this query
10297 var queryKey = syncTreeMakeQueryKey_(query);
10298 util.assert(!syncTree.queryToTagMap.has(queryKey), 'View does not exist, but we have a tag');
10299 var tag = syncTreeGetNextQueryTag_();
10300 syncTree.queryToTagMap.set(queryKey, tag);
10301 syncTree.tagToQueryMap.set(tag, queryKey);
10302 }
10303 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
10304 return {
10305 syncPoint: syncPoint,
10306 writesCache: writesCache,
10307 serverCache: serverCache,
10308 serverCacheComplete: serverCacheComplete,
10309 foundAncestorDefaultView: foundAncestorDefaultView,
10310 viewAlreadyExists: viewAlreadyExists
10311 };
10312}
10313/**
10314 * Add an event callback for the specified query.
10315 *
10316 * @returns Events to raise.
10317 */
10318function syncTreeAddEventRegistration(syncTree, query, eventRegistration) {
10319 var _a = syncTreeRegisterSyncPoint(query, syncTree), syncPoint = _a.syncPoint, serverCache = _a.serverCache, writesCache = _a.writesCache, serverCacheComplete = _a.serverCacheComplete, viewAlreadyExists = _a.viewAlreadyExists, foundAncestorDefaultView = _a.foundAncestorDefaultView;
10320 var events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
10321 if (!viewAlreadyExists && !foundAncestorDefaultView) {
10322 var view = syncPointViewForQuery(syncPoint, query);
10323 events = events.concat(syncTreeSetupListener_(syncTree, query, view));
10324 }
10325 return events;
10326}
10327/**
10328 * Returns a complete cache, if we have one, of the data at a particular path. If the location does not have a
10329 * listener above it, we will get a false "null". This shouldn't be a problem because transactions will always
10330 * have a listener above, and atomic operations would correctly show a jitter of <increment value> ->
10331 * <incremented total> as the write is applied locally and then acknowledged at the server.
10332 *
10333 * Note: this method will *include* hidden writes from transaction with applyLocally set to false.
10334 *
10335 * @param path - The path to the data we want
10336 * @param writeIdsToExclude - A specific set to be excluded
10337 */
10338function syncTreeCalcCompleteEventCache(syncTree, path, writeIdsToExclude) {
10339 var includeHiddenSets = true;
10340 var writeTree = syncTree.pendingWriteTree_;
10341 var serverCache = syncTree.syncPointTree_.findOnPath(path, function (pathSoFar, syncPoint) {
10342 var relativePath = newRelativePath(pathSoFar, path);
10343 var serverCache = syncPointGetCompleteServerCache(syncPoint, relativePath);
10344 if (serverCache) {
10345 return serverCache;
10346 }
10347 });
10348 return writeTreeCalcCompleteEventCache(writeTree, path, serverCache, writeIdsToExclude, includeHiddenSets);
10349}
10350function syncTreeGetServerValue(syncTree, query) {
10351 var path = query._path;
10352 var serverCache = null;
10353 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10354 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10355 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10356 var relativePath = newRelativePath(pathToSyncPoint, path);
10357 serverCache =
10358 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10359 });
10360 var syncPoint = syncTree.syncPointTree_.get(path);
10361 if (!syncPoint) {
10362 syncPoint = new SyncPoint();
10363 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10364 }
10365 else {
10366 serverCache =
10367 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10368 }
10369 var serverCacheComplete = serverCache != null;
10370 var serverCacheNode = serverCacheComplete
10371 ? new CacheNode(serverCache, true, false)
10372 : null;
10373 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, query._path);
10374 var view = syncPointGetView(syncPoint, query, writesCache, serverCacheComplete ? serverCacheNode.getNode() : ChildrenNode.EMPTY_NODE, serverCacheComplete);
10375 return viewGetCompleteNode(view);
10376}
10377/**
10378 * A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
10379 *
10380 * NOTES:
10381 * - Descendant SyncPoints will be visited first (since we raise events depth-first).
10382 *
10383 * - We call applyOperation() on each SyncPoint passing three things:
10384 * 1. A version of the Operation that has been made relative to the SyncPoint location.
10385 * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
10386 * 3. A snapshot Node with cached server data, if we have it.
10387 *
10388 * - We concatenate all of the events returned by each SyncPoint and return the result.
10389 */
10390function syncTreeApplyOperationToSyncPoints_(syncTree, operation) {
10391 return syncTreeApplyOperationHelper_(operation, syncTree.syncPointTree_,
10392 /*serverCache=*/ null, writeTreeChildWrites(syncTree.pendingWriteTree_, newEmptyPath()));
10393}
10394/**
10395 * Recursive helper for applyOperationToSyncPoints_
10396 */
10397function syncTreeApplyOperationHelper_(operation, syncPointTree, serverCache, writesCache) {
10398 if (pathIsEmpty(operation.path)) {
10399 return syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache);
10400 }
10401 else {
10402 var syncPoint = syncPointTree.get(newEmptyPath());
10403 // If we don't have cached server data, see if we can get it from this SyncPoint.
10404 if (serverCache == null && syncPoint != null) {
10405 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10406 }
10407 var events = [];
10408 var childName = pathGetFront(operation.path);
10409 var childOperation = operation.operationForChild(childName);
10410 var childTree = syncPointTree.children.get(childName);
10411 if (childTree && childOperation) {
10412 var childServerCache = serverCache
10413 ? serverCache.getImmediateChild(childName)
10414 : null;
10415 var childWritesCache = writeTreeRefChild(writesCache, childName);
10416 events = events.concat(syncTreeApplyOperationHelper_(childOperation, childTree, childServerCache, childWritesCache));
10417 }
10418 if (syncPoint) {
10419 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10420 }
10421 return events;
10422 }
10423}
10424/**
10425 * Recursive helper for applyOperationToSyncPoints_
10426 */
10427function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache) {
10428 var syncPoint = syncPointTree.get(newEmptyPath());
10429 // If we don't have cached server data, see if we can get it from this SyncPoint.
10430 if (serverCache == null && syncPoint != null) {
10431 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10432 }
10433 var events = [];
10434 syncPointTree.children.inorderTraversal(function (childName, childTree) {
10435 var childServerCache = serverCache
10436 ? serverCache.getImmediateChild(childName)
10437 : null;
10438 var childWritesCache = writeTreeRefChild(writesCache, childName);
10439 var childOperation = operation.operationForChild(childName);
10440 if (childOperation) {
10441 events = events.concat(syncTreeApplyOperationDescendantsHelper_(childOperation, childTree, childServerCache, childWritesCache));
10442 }
10443 });
10444 if (syncPoint) {
10445 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10446 }
10447 return events;
10448}
10449function syncTreeCreateListenerForView_(syncTree, view) {
10450 var query = view.query;
10451 var tag = syncTreeTagForQuery_(syncTree, query);
10452 return {
10453 hashFn: function () {
10454 var cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
10455 return cache.hash();
10456 },
10457 onComplete: function (status) {
10458 if (status === 'ok') {
10459 if (tag) {
10460 return syncTreeApplyTaggedListenComplete(syncTree, query._path, tag);
10461 }
10462 else {
10463 return syncTreeApplyListenComplete(syncTree, query._path);
10464 }
10465 }
10466 else {
10467 // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
10468 // Note that this may need to be scoped to just this listener if we change permissions on filtered children
10469 var error = errorForServerCode(status, query);
10470 return syncTreeRemoveEventRegistration(syncTree, query,
10471 /*eventRegistration*/ null, error);
10472 }
10473 }
10474 };
10475}
10476/**
10477 * Return the tag associated with the given query.
10478 */
10479function syncTreeTagForQuery_(syncTree, query) {
10480 var queryKey = syncTreeMakeQueryKey_(query);
10481 return syncTree.queryToTagMap.get(queryKey);
10482}
10483/**
10484 * Given a query, computes a "queryKey" suitable for use in our queryToTagMap_.
10485 */
10486function syncTreeMakeQueryKey_(query) {
10487 return query._path.toString() + '$' + query._queryIdentifier;
10488}
10489/**
10490 * Return the query associated with the given tag, if we have one
10491 */
10492function syncTreeQueryKeyForTag_(syncTree, tag) {
10493 return syncTree.tagToQueryMap.get(tag);
10494}
10495/**
10496 * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId.
10497 */
10498function syncTreeParseQueryKey_(queryKey) {
10499 var splitIndex = queryKey.indexOf('$');
10500 util.assert(splitIndex !== -1 && splitIndex < queryKey.length - 1, 'Bad queryKey.');
10501 return {
10502 queryId: queryKey.substr(splitIndex + 1),
10503 path: new Path(queryKey.substr(0, splitIndex))
10504 };
10505}
10506/**
10507 * A helper method to apply tagged operations
10508 */
10509function syncTreeApplyTaggedOperation_(syncTree, queryPath, operation) {
10510 var syncPoint = syncTree.syncPointTree_.get(queryPath);
10511 util.assert(syncPoint, "Missing sync point for query tag that we're tracking");
10512 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, queryPath);
10513 return syncPointApplyOperation(syncPoint, operation, writesCache, null);
10514}
10515/**
10516 * This collapses multiple unfiltered views into a single view, since we only need a single
10517 * listener for them.
10518 */
10519function syncTreeCollectDistinctViewsForSubTree_(subtree) {
10520 return subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10521 if (maybeChildSyncPoint && syncPointHasCompleteView(maybeChildSyncPoint)) {
10522 var completeView = syncPointGetCompleteView(maybeChildSyncPoint);
10523 return [completeView];
10524 }
10525 else {
10526 // No complete view here, flatten any deeper listens into an array
10527 var views_1 = [];
10528 if (maybeChildSyncPoint) {
10529 views_1 = syncPointGetQueryViews(maybeChildSyncPoint);
10530 }
10531 each(childMap, function (_key, childViews) {
10532 views_1 = views_1.concat(childViews);
10533 });
10534 return views_1;
10535 }
10536 });
10537}
10538/**
10539 * Normalizes a query to a query we send the server for listening
10540 *
10541 * @returns The normalized query
10542 */
10543function syncTreeQueryForListening_(query) {
10544 if (query._queryParams.loadsAllData() && !query._queryParams.isDefault()) {
10545 // We treat queries that load all data as default queries
10546 // Cast is necessary because ref() technically returns Firebase which is actually fb.api.Firebase which inherits
10547 // from Query
10548 return new (syncTreeGetReferenceConstructor())(query._repo, query._path);
10549 }
10550 else {
10551 return query;
10552 }
10553}
10554function syncTreeRemoveTags_(syncTree, queries) {
10555 for (var j = 0; j < queries.length; ++j) {
10556 var removedQuery = queries[j];
10557 if (!removedQuery._queryParams.loadsAllData()) {
10558 // We should have a tag for this
10559 var removedQueryKey = syncTreeMakeQueryKey_(removedQuery);
10560 var removedQueryTag = syncTree.queryToTagMap.get(removedQueryKey);
10561 syncTree.queryToTagMap.delete(removedQueryKey);
10562 syncTree.tagToQueryMap.delete(removedQueryTag);
10563 }
10564 }
10565}
10566/**
10567 * Static accessor for query tags.
10568 */
10569function syncTreeGetNextQueryTag_() {
10570 return syncTreeNextQueryTag_++;
10571}
10572/**
10573 * For a given new listen, manage the de-duplication of outstanding subscriptions.
10574 *
10575 * @returns This method can return events to support synchronous data sources
10576 */
10577function syncTreeSetupListener_(syncTree, query, view) {
10578 var path = query._path;
10579 var tag = syncTreeTagForQuery_(syncTree, query);
10580 var listener = syncTreeCreateListenerForView_(syncTree, view);
10581 var events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10582 var subtree = syncTree.syncPointTree_.subtree(path);
10583 // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
10584 // may need to shadow other listens as well.
10585 if (tag) {
10586 util.assert(!syncPointHasCompleteView(subtree.value), "If we're adding a query, it shouldn't be shadowed");
10587 }
10588 else {
10589 // Shadow everything at or below this location, this is a default listener.
10590 var queriesToStop = subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10591 if (!pathIsEmpty(relativePath) &&
10592 maybeChildSyncPoint &&
10593 syncPointHasCompleteView(maybeChildSyncPoint)) {
10594 return [syncPointGetCompleteView(maybeChildSyncPoint).query];
10595 }
10596 else {
10597 // No default listener here, flatten any deeper queries into an array
10598 var queries_1 = [];
10599 if (maybeChildSyncPoint) {
10600 queries_1 = queries_1.concat(syncPointGetQueryViews(maybeChildSyncPoint).map(function (view) { return view.query; }));
10601 }
10602 each(childMap, function (_key, childQueries) {
10603 queries_1 = queries_1.concat(childQueries);
10604 });
10605 return queries_1;
10606 }
10607 });
10608 for (var i = 0; i < queriesToStop.length; ++i) {
10609 var queryToStop = queriesToStop[i];
10610 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery_(syncTree, queryToStop));
10611 }
10612 }
10613 return events;
10614}
10615
10616/**
10617 * @license
10618 * Copyright 2017 Google LLC
10619 *
10620 * Licensed under the Apache License, Version 2.0 (the "License");
10621 * you may not use this file except in compliance with the License.
10622 * You may obtain a copy of the License at
10623 *
10624 * http://www.apache.org/licenses/LICENSE-2.0
10625 *
10626 * Unless required by applicable law or agreed to in writing, software
10627 * distributed under the License is distributed on an "AS IS" BASIS,
10628 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10629 * See the License for the specific language governing permissions and
10630 * limitations under the License.
10631 */
10632var ExistingValueProvider = /** @class */ (function () {
10633 function ExistingValueProvider(node_) {
10634 this.node_ = node_;
10635 }
10636 ExistingValueProvider.prototype.getImmediateChild = function (childName) {
10637 var child = this.node_.getImmediateChild(childName);
10638 return new ExistingValueProvider(child);
10639 };
10640 ExistingValueProvider.prototype.node = function () {
10641 return this.node_;
10642 };
10643 return ExistingValueProvider;
10644}());
10645var DeferredValueProvider = /** @class */ (function () {
10646 function DeferredValueProvider(syncTree, path) {
10647 this.syncTree_ = syncTree;
10648 this.path_ = path;
10649 }
10650 DeferredValueProvider.prototype.getImmediateChild = function (childName) {
10651 var childPath = pathChild(this.path_, childName);
10652 return new DeferredValueProvider(this.syncTree_, childPath);
10653 };
10654 DeferredValueProvider.prototype.node = function () {
10655 return syncTreeCalcCompleteEventCache(this.syncTree_, this.path_);
10656 };
10657 return DeferredValueProvider;
10658}());
10659/**
10660 * Generate placeholders for deferred values.
10661 */
10662var generateWithValues = function (values) {
10663 values = values || {};
10664 values['timestamp'] = values['timestamp'] || new Date().getTime();
10665 return values;
10666};
10667/**
10668 * Value to use when firing local events. When writing server values, fire
10669 * local events with an approximate value, otherwise return value as-is.
10670 */
10671var resolveDeferredLeafValue = function (value, existingVal, serverValues) {
10672 if (!value || typeof value !== 'object') {
10673 return value;
10674 }
10675 util.assert('.sv' in value, 'Unexpected leaf node or priority contents');
10676 if (typeof value['.sv'] === 'string') {
10677 return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
10678 }
10679 else if (typeof value['.sv'] === 'object') {
10680 return resolveComplexDeferredValue(value['.sv'], existingVal);
10681 }
10682 else {
10683 util.assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
10684 }
10685};
10686var resolveScalarDeferredValue = function (op, existing, serverValues) {
10687 switch (op) {
10688 case 'timestamp':
10689 return serverValues['timestamp'];
10690 default:
10691 util.assert(false, 'Unexpected server value: ' + op);
10692 }
10693};
10694var resolveComplexDeferredValue = function (op, existing, unused) {
10695 if (!op.hasOwnProperty('increment')) {
10696 util.assert(false, 'Unexpected server value: ' + JSON.stringify(op, null, 2));
10697 }
10698 var delta = op['increment'];
10699 if (typeof delta !== 'number') {
10700 util.assert(false, 'Unexpected increment value: ' + delta);
10701 }
10702 var existingNode = existing.node();
10703 util.assert(existingNode !== null && typeof existingNode !== 'undefined', 'Expected ChildrenNode.EMPTY_NODE for nulls');
10704 // Incrementing a non-number sets the value to the incremented amount
10705 if (!existingNode.isLeafNode()) {
10706 return delta;
10707 }
10708 var leaf = existingNode;
10709 var existingVal = leaf.getValue();
10710 if (typeof existingVal !== 'number') {
10711 return delta;
10712 }
10713 // No need to do over/underflow arithmetic here because JS only handles floats under the covers
10714 return existingVal + delta;
10715};
10716/**
10717 * Recursively replace all deferred values and priorities in the tree with the
10718 * specified generated replacement values.
10719 * @param path - path to which write is relative
10720 * @param node - new data written at path
10721 * @param syncTree - current data
10722 */
10723var resolveDeferredValueTree = function (path, node, syncTree, serverValues) {
10724 return resolveDeferredValue(node, new DeferredValueProvider(syncTree, path), serverValues);
10725};
10726/**
10727 * Recursively replace all deferred values and priorities in the node with the
10728 * specified generated replacement values. If there are no server values in the node,
10729 * it'll be returned as-is.
10730 */
10731var resolveDeferredValueSnapshot = function (node, existing, serverValues) {
10732 return resolveDeferredValue(node, new ExistingValueProvider(existing), serverValues);
10733};
10734function resolveDeferredValue(node, existingVal, serverValues) {
10735 var rawPri = node.getPriority().val();
10736 var priority = resolveDeferredLeafValue(rawPri, existingVal.getImmediateChild('.priority'), serverValues);
10737 var newNode;
10738 if (node.isLeafNode()) {
10739 var leafNode = node;
10740 var value = resolveDeferredLeafValue(leafNode.getValue(), existingVal, serverValues);
10741 if (value !== leafNode.getValue() ||
10742 priority !== leafNode.getPriority().val()) {
10743 return new LeafNode(value, nodeFromJSON(priority));
10744 }
10745 else {
10746 return node;
10747 }
10748 }
10749 else {
10750 var childrenNode = node;
10751 newNode = childrenNode;
10752 if (priority !== childrenNode.getPriority().val()) {
10753 newNode = newNode.updatePriority(new LeafNode(priority));
10754 }
10755 childrenNode.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
10756 var newChildNode = resolveDeferredValue(childNode, existingVal.getImmediateChild(childName), serverValues);
10757 if (newChildNode !== childNode) {
10758 newNode = newNode.updateImmediateChild(childName, newChildNode);
10759 }
10760 });
10761 return newNode;
10762 }
10763}
10764
10765/**
10766 * @license
10767 * Copyright 2017 Google LLC
10768 *
10769 * Licensed under the Apache License, Version 2.0 (the "License");
10770 * you may not use this file except in compliance with the License.
10771 * You may obtain a copy of the License at
10772 *
10773 * http://www.apache.org/licenses/LICENSE-2.0
10774 *
10775 * Unless required by applicable law or agreed to in writing, software
10776 * distributed under the License is distributed on an "AS IS" BASIS,
10777 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10778 * See the License for the specific language governing permissions and
10779 * limitations under the License.
10780 */
10781/**
10782 * A light-weight tree, traversable by path. Nodes can have both values and children.
10783 * Nodes are not enumerated (by forEachChild) unless they have a value or non-empty
10784 * children.
10785 */
10786var Tree = /** @class */ (function () {
10787 /**
10788 * @param name - Optional name of the node.
10789 * @param parent - Optional parent node.
10790 * @param node - Optional node to wrap.
10791 */
10792 function Tree(name, parent, node) {
10793 if (name === void 0) { name = ''; }
10794 if (parent === void 0) { parent = null; }
10795 if (node === void 0) { node = { children: {}, childCount: 0 }; }
10796 this.name = name;
10797 this.parent = parent;
10798 this.node = node;
10799 }
10800 return Tree;
10801}());
10802/**
10803 * Returns a sub-Tree for the given path.
10804 *
10805 * @param pathObj - Path to look up.
10806 * @returns Tree for path.
10807 */
10808function treeSubTree(tree, pathObj) {
10809 // TODO: Require pathObj to be Path?
10810 var path = pathObj instanceof Path ? pathObj : new Path(pathObj);
10811 var child = tree, next = pathGetFront(path);
10812 while (next !== null) {
10813 var childNode = util.safeGet(child.node.children, next) || {
10814 children: {},
10815 childCount: 0
10816 };
10817 child = new Tree(next, child, childNode);
10818 path = pathPopFront(path);
10819 next = pathGetFront(path);
10820 }
10821 return child;
10822}
10823/**
10824 * Returns the data associated with this tree node.
10825 *
10826 * @returns The data or null if no data exists.
10827 */
10828function treeGetValue(tree) {
10829 return tree.node.value;
10830}
10831/**
10832 * Sets data to this tree node.
10833 *
10834 * @param value - Value to set.
10835 */
10836function treeSetValue(tree, value) {
10837 tree.node.value = value;
10838 treeUpdateParents(tree);
10839}
10840/**
10841 * @returns Whether the tree has any children.
10842 */
10843function treeHasChildren(tree) {
10844 return tree.node.childCount > 0;
10845}
10846/**
10847 * @returns Whethe rthe tree is empty (no value or children).
10848 */
10849function treeIsEmpty(tree) {
10850 return treeGetValue(tree) === undefined && !treeHasChildren(tree);
10851}
10852/**
10853 * Calls action for each child of this tree node.
10854 *
10855 * @param action - Action to be called for each child.
10856 */
10857function treeForEachChild(tree, action) {
10858 each(tree.node.children, function (child, childTree) {
10859 action(new Tree(child, tree, childTree));
10860 });
10861}
10862/**
10863 * Does a depth-first traversal of this node's descendants, calling action for each one.
10864 *
10865 * @param action - Action to be called for each child.
10866 * @param includeSelf - Whether to call action on this node as well. Defaults to
10867 * false.
10868 * @param childrenFirst - Whether to call action on children before calling it on
10869 * parent.
10870 */
10871function treeForEachDescendant(tree, action, includeSelf, childrenFirst) {
10872 if (includeSelf && !childrenFirst) {
10873 action(tree);
10874 }
10875 treeForEachChild(tree, function (child) {
10876 treeForEachDescendant(child, action, true, childrenFirst);
10877 });
10878 if (includeSelf && childrenFirst) {
10879 action(tree);
10880 }
10881}
10882/**
10883 * Calls action on each ancestor node.
10884 *
10885 * @param action - Action to be called on each parent; return
10886 * true to abort.
10887 * @param includeSelf - Whether to call action on this node as well.
10888 * @returns true if the action callback returned true.
10889 */
10890function treeForEachAncestor(tree, action, includeSelf) {
10891 var node = includeSelf ? tree : tree.parent;
10892 while (node !== null) {
10893 if (action(node)) {
10894 return true;
10895 }
10896 node = node.parent;
10897 }
10898 return false;
10899}
10900/**
10901 * @returns The path of this tree node, as a Path.
10902 */
10903function treeGetPath(tree) {
10904 return new Path(tree.parent === null
10905 ? tree.name
10906 : treeGetPath(tree.parent) + '/' + tree.name);
10907}
10908/**
10909 * Adds or removes this child from its parent based on whether it's empty or not.
10910 */
10911function treeUpdateParents(tree) {
10912 if (tree.parent !== null) {
10913 treeUpdateChild(tree.parent, tree.name, tree);
10914 }
10915}
10916/**
10917 * Adds or removes the passed child to this tree node, depending on whether it's empty.
10918 *
10919 * @param childName - The name of the child to update.
10920 * @param child - The child to update.
10921 */
10922function treeUpdateChild(tree, childName, child) {
10923 var childEmpty = treeIsEmpty(child);
10924 var childExists = util.contains(tree.node.children, childName);
10925 if (childEmpty && childExists) {
10926 delete tree.node.children[childName];
10927 tree.node.childCount--;
10928 treeUpdateParents(tree);
10929 }
10930 else if (!childEmpty && !childExists) {
10931 tree.node.children[childName] = child.node;
10932 tree.node.childCount++;
10933 treeUpdateParents(tree);
10934 }
10935}
10936
10937/**
10938 * @license
10939 * Copyright 2017 Google LLC
10940 *
10941 * Licensed under the Apache License, Version 2.0 (the "License");
10942 * you may not use this file except in compliance with the License.
10943 * You may obtain a copy of the License at
10944 *
10945 * http://www.apache.org/licenses/LICENSE-2.0
10946 *
10947 * Unless required by applicable law or agreed to in writing, software
10948 * distributed under the License is distributed on an "AS IS" BASIS,
10949 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10950 * See the License for the specific language governing permissions and
10951 * limitations under the License.
10952 */
10953/**
10954 * True for invalid Firebase keys
10955 */
10956var INVALID_KEY_REGEX_ = /[\[\].#$\/\u0000-\u001F\u007F]/;
10957/**
10958 * True for invalid Firebase paths.
10959 * Allows '/' in paths.
10960 */
10961var INVALID_PATH_REGEX_ = /[\[\].#$\u0000-\u001F\u007F]/;
10962/**
10963 * Maximum number of characters to allow in leaf value
10964 */
10965var MAX_LEAF_SIZE_ = 10 * 1024 * 1024;
10966var isValidKey = function (key) {
10967 return (typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX_.test(key));
10968};
10969var isValidPathString = function (pathString) {
10970 return (typeof pathString === 'string' &&
10971 pathString.length !== 0 &&
10972 !INVALID_PATH_REGEX_.test(pathString));
10973};
10974var isValidRootPathString = function (pathString) {
10975 if (pathString) {
10976 // Allow '/.info/' at the beginning.
10977 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10978 }
10979 return isValidPathString(pathString);
10980};
10981var isValidPriority = function (priority) {
10982 return (priority === null ||
10983 typeof priority === 'string' ||
10984 (typeof priority === 'number' && !isInvalidJSONNumber(priority)) ||
10985 (priority &&
10986 typeof priority === 'object' &&
10987 // eslint-disable-next-line @typescript-eslint/no-explicit-any
10988 util.contains(priority, '.sv')));
10989};
10990/**
10991 * Pre-validate a datum passed as an argument to Firebase function.
10992 */
10993var validateFirebaseDataArg = function (fnName, value, path, optional) {
10994 if (optional && value === undefined) {
10995 return;
10996 }
10997 validateFirebaseData(util.errorPrefix(fnName, 'value'), value, path);
10998};
10999/**
11000 * Validate a data object client-side before sending to server.
11001 */
11002var validateFirebaseData = function (errorPrefix, data, path_) {
11003 var path = path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;
11004 if (data === undefined) {
11005 throw new Error(errorPrefix + 'contains undefined ' + validationPathToErrorString(path));
11006 }
11007 if (typeof data === 'function') {
11008 throw new Error(errorPrefix +
11009 'contains a function ' +
11010 validationPathToErrorString(path) +
11011 ' with contents = ' +
11012 data.toString());
11013 }
11014 if (isInvalidJSONNumber(data)) {
11015 throw new Error(errorPrefix +
11016 'contains ' +
11017 data.toString() +
11018 ' ' +
11019 validationPathToErrorString(path));
11020 }
11021 // Check max leaf size, but try to avoid the utf8 conversion if we can.
11022 if (typeof data === 'string' &&
11023 data.length > MAX_LEAF_SIZE_ / 3 &&
11024 util.stringLength(data) > MAX_LEAF_SIZE_) {
11025 throw new Error(errorPrefix +
11026 'contains a string greater than ' +
11027 MAX_LEAF_SIZE_ +
11028 ' utf8 bytes ' +
11029 validationPathToErrorString(path) +
11030 " ('" +
11031 data.substring(0, 50) +
11032 "...')");
11033 }
11034 // TODO = Perf = Consider combining the recursive validation of keys into NodeFromJSON
11035 // to save extra walking of large objects.
11036 if (data && typeof data === 'object') {
11037 var hasDotValue_1 = false;
11038 var hasActualChild_1 = false;
11039 each(data, function (key, value) {
11040 if (key === '.value') {
11041 hasDotValue_1 = true;
11042 }
11043 else if (key !== '.priority' && key !== '.sv') {
11044 hasActualChild_1 = true;
11045 if (!isValidKey(key)) {
11046 throw new Error(errorPrefix +
11047 ' contains an invalid key (' +
11048 key +
11049 ') ' +
11050 validationPathToErrorString(path) +
11051 '. Keys must be non-empty strings ' +
11052 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11053 }
11054 }
11055 validationPathPush(path, key);
11056 validateFirebaseData(errorPrefix, value, path);
11057 validationPathPop(path);
11058 });
11059 if (hasDotValue_1 && hasActualChild_1) {
11060 throw new Error(errorPrefix +
11061 ' contains ".value" child ' +
11062 validationPathToErrorString(path) +
11063 ' in addition to actual children.');
11064 }
11065 }
11066};
11067/**
11068 * Pre-validate paths passed in the firebase function.
11069 */
11070var validateFirebaseMergePaths = function (errorPrefix, mergePaths) {
11071 var i, curPath;
11072 for (i = 0; i < mergePaths.length; i++) {
11073 curPath = mergePaths[i];
11074 var keys = pathSlice(curPath);
11075 for (var j = 0; j < keys.length; j++) {
11076 if (keys[j] === '.priority' && j === keys.length - 1) ;
11077 else if (!isValidKey(keys[j])) {
11078 throw new Error(errorPrefix +
11079 'contains an invalid key (' +
11080 keys[j] +
11081 ') in path ' +
11082 curPath.toString() +
11083 '. Keys must be non-empty strings ' +
11084 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11085 }
11086 }
11087 }
11088 // Check that update keys are not descendants of each other.
11089 // We rely on the property that sorting guarantees that ancestors come
11090 // right before descendants.
11091 mergePaths.sort(pathCompare);
11092 var prevPath = null;
11093 for (i = 0; i < mergePaths.length; i++) {
11094 curPath = mergePaths[i];
11095 if (prevPath !== null && pathContains(prevPath, curPath)) {
11096 throw new Error(errorPrefix +
11097 'contains a path ' +
11098 prevPath.toString() +
11099 ' that is ancestor of another path ' +
11100 curPath.toString());
11101 }
11102 prevPath = curPath;
11103 }
11104};
11105/**
11106 * pre-validate an object passed as an argument to firebase function (
11107 * must be an object - e.g. for firebase.update()).
11108 */
11109var validateFirebaseMergeDataArg = function (fnName, data, path, optional) {
11110 if (optional && data === undefined) {
11111 return;
11112 }
11113 var errorPrefix = util.errorPrefix(fnName, 'values');
11114 if (!(data && typeof data === 'object') || Array.isArray(data)) {
11115 throw new Error(errorPrefix + ' must be an object containing the children to replace.');
11116 }
11117 var mergePaths = [];
11118 each(data, function (key, value) {
11119 var curPath = new Path(key);
11120 validateFirebaseData(errorPrefix, value, pathChild(path, curPath));
11121 if (pathGetBack(curPath) === '.priority') {
11122 if (!isValidPriority(value)) {
11123 throw new Error(errorPrefix +
11124 "contains an invalid value for '" +
11125 curPath.toString() +
11126 "', which must be a valid " +
11127 'Firebase priority (a string, finite number, server value, or null).');
11128 }
11129 }
11130 mergePaths.push(curPath);
11131 });
11132 validateFirebaseMergePaths(errorPrefix, mergePaths);
11133};
11134var validatePriority = function (fnName, priority, optional) {
11135 if (optional && priority === undefined) {
11136 return;
11137 }
11138 if (isInvalidJSONNumber(priority)) {
11139 throw new Error(util.errorPrefix(fnName, 'priority') +
11140 'is ' +
11141 priority.toString() +
11142 ', but must be a valid Firebase priority (a string, finite number, ' +
11143 'server value, or null).');
11144 }
11145 // Special case to allow importing data with a .sv.
11146 if (!isValidPriority(priority)) {
11147 throw new Error(util.errorPrefix(fnName, 'priority') +
11148 'must be a valid Firebase priority ' +
11149 '(a string, finite number, server value, or null).');
11150 }
11151};
11152var validateKey = function (fnName, argumentName, key, optional) {
11153 if (optional && key === undefined) {
11154 return;
11155 }
11156 if (!isValidKey(key)) {
11157 throw new Error(util.errorPrefix(fnName, argumentName) +
11158 'was an invalid key = "' +
11159 key +
11160 '". Firebase keys must be non-empty strings and ' +
11161 'can\'t contain ".", "#", "$", "/", "[", or "]").');
11162 }
11163};
11164/**
11165 * @internal
11166 */
11167var validatePathString = function (fnName, argumentName, pathString, optional) {
11168 if (optional && pathString === undefined) {
11169 return;
11170 }
11171 if (!isValidPathString(pathString)) {
11172 throw new Error(util.errorPrefix(fnName, argumentName) +
11173 'was an invalid path = "' +
11174 pathString +
11175 '". Paths must be non-empty strings and ' +
11176 'can\'t contain ".", "#", "$", "[", or "]"');
11177 }
11178};
11179var validateRootPathString = function (fnName, argumentName, pathString, optional) {
11180 if (pathString) {
11181 // Allow '/.info/' at the beginning.
11182 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
11183 }
11184 validatePathString(fnName, argumentName, pathString, optional);
11185};
11186/**
11187 * @internal
11188 */
11189var validateWritablePath = function (fnName, path) {
11190 if (pathGetFront(path) === '.info') {
11191 throw new Error(fnName + " failed = Can't modify data under /.info/");
11192 }
11193};
11194var validateUrl = function (fnName, parsedUrl) {
11195 // TODO = Validate server better.
11196 var pathString = parsedUrl.path.toString();
11197 if (!(typeof parsedUrl.repoInfo.host === 'string') ||
11198 parsedUrl.repoInfo.host.length === 0 ||
11199 (!isValidKey(parsedUrl.repoInfo.namespace) &&
11200 parsedUrl.repoInfo.host.split(':')[0] !== 'localhost') ||
11201 (pathString.length !== 0 && !isValidRootPathString(pathString))) {
11202 throw new Error(util.errorPrefix(fnName, 'url') +
11203 'must be a valid firebase URL and ' +
11204 'the path can\'t contain ".", "#", "$", "[", or "]".');
11205 }
11206};
11207
11208/**
11209 * @license
11210 * Copyright 2017 Google LLC
11211 *
11212 * Licensed under the Apache License, Version 2.0 (the "License");
11213 * you may not use this file except in compliance with the License.
11214 * You may obtain a copy of the License at
11215 *
11216 * http://www.apache.org/licenses/LICENSE-2.0
11217 *
11218 * Unless required by applicable law or agreed to in writing, software
11219 * distributed under the License is distributed on an "AS IS" BASIS,
11220 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11221 * See the License for the specific language governing permissions and
11222 * limitations under the License.
11223 */
11224/**
11225 * The event queue serves a few purposes:
11226 * 1. It ensures we maintain event order in the face of event callbacks doing operations that result in more
11227 * events being queued.
11228 * 2. raiseQueuedEvents() handles being called reentrantly nicely. That is, if in the course of raising events,
11229 * raiseQueuedEvents() is called again, the "inner" call will pick up raising events where the "outer" call
11230 * left off, ensuring that the events are still raised synchronously and in order.
11231 * 3. You can use raiseEventsAtPath and raiseEventsForChangedPath to ensure only relevant previously-queued
11232 * events are raised synchronously.
11233 *
11234 * NOTE: This can all go away if/when we move to async events.
11235 *
11236 */
11237var EventQueue = /** @class */ (function () {
11238 function EventQueue() {
11239 this.eventLists_ = [];
11240 /**
11241 * Tracks recursion depth of raiseQueuedEvents_, for debugging purposes.
11242 */
11243 this.recursionDepth_ = 0;
11244 }
11245 return EventQueue;
11246}());
11247/**
11248 * @param eventDataList - The new events to queue.
11249 */
11250function eventQueueQueueEvents(eventQueue, eventDataList) {
11251 // We group events by path, storing them in a single EventList, to make it easier to skip over them quickly.
11252 var currList = null;
11253 for (var i = 0; i < eventDataList.length; i++) {
11254 var data = eventDataList[i];
11255 var path = data.getPath();
11256 if (currList !== null && !pathEquals(path, currList.path)) {
11257 eventQueue.eventLists_.push(currList);
11258 currList = null;
11259 }
11260 if (currList === null) {
11261 currList = { events: [], path: path };
11262 }
11263 currList.events.push(data);
11264 }
11265 if (currList) {
11266 eventQueue.eventLists_.push(currList);
11267 }
11268}
11269/**
11270 * Queues the specified events and synchronously raises all events (including previously queued ones)
11271 * for the specified path.
11272 *
11273 * It is assumed that the new events are all for the specified path.
11274 *
11275 * @param path - The path to raise events for.
11276 * @param eventDataList - The new events to raise.
11277 */
11278function eventQueueRaiseEventsAtPath(eventQueue, path, eventDataList) {
11279 eventQueueQueueEvents(eventQueue, eventDataList);
11280 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11281 return pathEquals(eventPath, path);
11282 });
11283}
11284/**
11285 * Queues the specified events and synchronously raises all events (including previously queued ones) for
11286 * locations related to the specified change path (i.e. all ancestors and descendants).
11287 *
11288 * It is assumed that the new events are all related (ancestor or descendant) to the specified path.
11289 *
11290 * @param changedPath - The path to raise events for.
11291 * @param eventDataList - The events to raise
11292 */
11293function eventQueueRaiseEventsForChangedPath(eventQueue, changedPath, eventDataList) {
11294 eventQueueQueueEvents(eventQueue, eventDataList);
11295 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11296 return pathContains(eventPath, changedPath) ||
11297 pathContains(changedPath, eventPath);
11298 });
11299}
11300function eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, predicate) {
11301 eventQueue.recursionDepth_++;
11302 var sentAll = true;
11303 for (var i = 0; i < eventQueue.eventLists_.length; i++) {
11304 var eventList = eventQueue.eventLists_[i];
11305 if (eventList) {
11306 var eventPath = eventList.path;
11307 if (predicate(eventPath)) {
11308 eventListRaise(eventQueue.eventLists_[i]);
11309 eventQueue.eventLists_[i] = null;
11310 }
11311 else {
11312 sentAll = false;
11313 }
11314 }
11315 }
11316 if (sentAll) {
11317 eventQueue.eventLists_ = [];
11318 }
11319 eventQueue.recursionDepth_--;
11320}
11321/**
11322 * Iterates through the list and raises each event
11323 */
11324function eventListRaise(eventList) {
11325 for (var i = 0; i < eventList.events.length; i++) {
11326 var eventData = eventList.events[i];
11327 if (eventData !== null) {
11328 eventList.events[i] = null;
11329 var eventFn = eventData.getEventRunner();
11330 if (logger) {
11331 log('event: ' + eventData.toString());
11332 }
11333 exceptionGuard(eventFn);
11334 }
11335 }
11336}
11337
11338/**
11339 * @license
11340 * Copyright 2017 Google LLC
11341 *
11342 * Licensed under the Apache License, Version 2.0 (the "License");
11343 * you may not use this file except in compliance with the License.
11344 * You may obtain a copy of the License at
11345 *
11346 * http://www.apache.org/licenses/LICENSE-2.0
11347 *
11348 * Unless required by applicable law or agreed to in writing, software
11349 * distributed under the License is distributed on an "AS IS" BASIS,
11350 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11351 * See the License for the specific language governing permissions and
11352 * limitations under the License.
11353 */
11354var INTERRUPT_REASON = 'repo_interrupt';
11355/**
11356 * If a transaction does not succeed after 25 retries, we abort it. Among other
11357 * things this ensure that if there's ever a bug causing a mismatch between
11358 * client / server hashes for some data, we won't retry indefinitely.
11359 */
11360var MAX_TRANSACTION_RETRIES = 25;
11361/**
11362 * A connection to a single data repository.
11363 */
11364var Repo = /** @class */ (function () {
11365 function Repo(repoInfo_, forceRestClient_, authTokenProvider_, appCheckProvider_) {
11366 this.repoInfo_ = repoInfo_;
11367 this.forceRestClient_ = forceRestClient_;
11368 this.authTokenProvider_ = authTokenProvider_;
11369 this.appCheckProvider_ = appCheckProvider_;
11370 this.dataUpdateCount = 0;
11371 this.statsListener_ = null;
11372 this.eventQueue_ = new EventQueue();
11373 this.nextWriteId_ = 1;
11374 this.interceptServerDataCallback_ = null;
11375 /** A list of data pieces and paths to be set when this client disconnects. */
11376 this.onDisconnect_ = newSparseSnapshotTree();
11377 /** Stores queues of outstanding transactions for Firebase locations. */
11378 this.transactionQueueTree_ = new Tree();
11379 // TODO: This should be @private but it's used by test_access.js and internal.js
11380 this.persistentConnection_ = null;
11381 // This key is intentionally not updated if RepoInfo is later changed or replaced
11382 this.key = this.repoInfo_.toURLString();
11383 }
11384 /**
11385 * @returns The URL corresponding to the root of this Firebase.
11386 */
11387 Repo.prototype.toString = function () {
11388 return ((this.repoInfo_.secure ? 'https://' : 'http://') + this.repoInfo_.host);
11389 };
11390 return Repo;
11391}());
11392function repoStart(repo, appId, authOverride) {
11393 repo.stats_ = statsManagerGetCollection(repo.repoInfo_);
11394 if (repo.forceRestClient_ || beingCrawled()) {
11395 repo.server_ = new ReadonlyRestClient(repo.repoInfo_, function (pathString, data, isMerge, tag) {
11396 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11397 }, repo.authTokenProvider_, repo.appCheckProvider_);
11398 // Minor hack: Fire onConnect immediately, since there's no actual connection.
11399 setTimeout(function () { return repoOnConnectStatus(repo, /* connectStatus= */ true); }, 0);
11400 }
11401 else {
11402 // Validate authOverride
11403 if (typeof authOverride !== 'undefined' && authOverride !== null) {
11404 if (typeof authOverride !== 'object') {
11405 throw new Error('Only objects are supported for option databaseAuthVariableOverride');
11406 }
11407 try {
11408 util.stringify(authOverride);
11409 }
11410 catch (e) {
11411 throw new Error('Invalid authOverride provided: ' + e);
11412 }
11413 }
11414 repo.persistentConnection_ = new PersistentConnection(repo.repoInfo_, appId, function (pathString, data, isMerge, tag) {
11415 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11416 }, function (connectStatus) {
11417 repoOnConnectStatus(repo, connectStatus);
11418 }, function (updates) {
11419 repoOnServerInfoUpdate(repo, updates);
11420 }, repo.authTokenProvider_, repo.appCheckProvider_, authOverride);
11421 repo.server_ = repo.persistentConnection_;
11422 }
11423 repo.authTokenProvider_.addTokenChangeListener(function (token) {
11424 repo.server_.refreshAuthToken(token);
11425 });
11426 repo.appCheckProvider_.addTokenChangeListener(function (result) {
11427 repo.server_.refreshAppCheckToken(result.token);
11428 });
11429 // In the case of multiple Repos for the same repoInfo (i.e. there are multiple Firebase.Contexts being used),
11430 // we only want to create one StatsReporter. As such, we'll report stats over the first Repo created.
11431 repo.statsReporter_ = statsManagerGetOrCreateReporter(repo.repoInfo_, function () { return new StatsReporter(repo.stats_, repo.server_); });
11432 // Used for .info.
11433 repo.infoData_ = new SnapshotHolder();
11434 repo.infoSyncTree_ = new SyncTree({
11435 startListening: function (query, tag, currentHashFn, onComplete) {
11436 var infoEvents = [];
11437 var node = repo.infoData_.getNode(query._path);
11438 // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
11439 // on initial data...
11440 if (!node.isEmpty()) {
11441 infoEvents = syncTreeApplyServerOverwrite(repo.infoSyncTree_, query._path, node);
11442 setTimeout(function () {
11443 onComplete('ok');
11444 }, 0);
11445 }
11446 return infoEvents;
11447 },
11448 stopListening: function () { }
11449 });
11450 repoUpdateInfo(repo, 'connected', false);
11451 repo.serverSyncTree_ = new SyncTree({
11452 startListening: function (query, tag, currentHashFn, onComplete) {
11453 repo.server_.listen(query, currentHashFn, tag, function (status, data) {
11454 var events = onComplete(status, data);
11455 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11456 });
11457 // No synchronous events for network-backed sync trees
11458 return [];
11459 },
11460 stopListening: function (query, tag) {
11461 repo.server_.unlisten(query, tag);
11462 }
11463 });
11464}
11465/**
11466 * @returns The time in milliseconds, taking the server offset into account if we have one.
11467 */
11468function repoServerTime(repo) {
11469 var offsetNode = repo.infoData_.getNode(new Path('.info/serverTimeOffset'));
11470 var offset = offsetNode.val() || 0;
11471 return new Date().getTime() + offset;
11472}
11473/**
11474 * Generate ServerValues using some variables from the repo object.
11475 */
11476function repoGenerateServerValues(repo) {
11477 return generateWithValues({
11478 timestamp: repoServerTime(repo)
11479 });
11480}
11481/**
11482 * Called by realtime when we get new messages from the server.
11483 */
11484function repoOnDataUpdate(repo, pathString, data, isMerge, tag) {
11485 // For testing.
11486 repo.dataUpdateCount++;
11487 var path = new Path(pathString);
11488 data = repo.interceptServerDataCallback_
11489 ? repo.interceptServerDataCallback_(pathString, data)
11490 : data;
11491 var events = [];
11492 if (tag) {
11493 if (isMerge) {
11494 var taggedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11495 events = syncTreeApplyTaggedQueryMerge(repo.serverSyncTree_, path, taggedChildren, tag);
11496 }
11497 else {
11498 var taggedSnap = nodeFromJSON(data);
11499 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, path, taggedSnap, tag);
11500 }
11501 }
11502 else if (isMerge) {
11503 var changedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11504 events = syncTreeApplyServerMerge(repo.serverSyncTree_, path, changedChildren);
11505 }
11506 else {
11507 var snap = nodeFromJSON(data);
11508 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap);
11509 }
11510 var affectedPath = path;
11511 if (events.length > 0) {
11512 // Since we have a listener outstanding for each transaction, receiving any events
11513 // is a proxy for some change having occurred.
11514 affectedPath = repoRerunTransactions(repo, path);
11515 }
11516 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, events);
11517}
11518function repoOnConnectStatus(repo, connectStatus) {
11519 repoUpdateInfo(repo, 'connected', connectStatus);
11520 if (connectStatus === false) {
11521 repoRunOnDisconnectEvents(repo);
11522 }
11523}
11524function repoOnServerInfoUpdate(repo, updates) {
11525 each(updates, function (key, value) {
11526 repoUpdateInfo(repo, key, value);
11527 });
11528}
11529function repoUpdateInfo(repo, pathString, value) {
11530 var path = new Path('/.info/' + pathString);
11531 var newNode = nodeFromJSON(value);
11532 repo.infoData_.updateSnapshot(path, newNode);
11533 var events = syncTreeApplyServerOverwrite(repo.infoSyncTree_, path, newNode);
11534 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11535}
11536function repoGetNextWriteId(repo) {
11537 return repo.nextWriteId_++;
11538}
11539/**
11540 * The purpose of `getValue` is to return the latest known value
11541 * satisfying `query`.
11542 *
11543 * This method will first check for in-memory cached values
11544 * belonging to active listeners. If they are found, such values
11545 * are considered to be the most up-to-date.
11546 *
11547 * If the client is not connected, this method will try to
11548 * establish a connection and request the value for `query`. If
11549 * the client is not able to retrieve the query result, it reports
11550 * an error.
11551 *
11552 * @param query - The query to surface a value for.
11553 */
11554function repoGetValue(repo, query) {
11555 // Only active queries are cached. There is no persisted cache.
11556 var cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11557 if (cached != null) {
11558 return Promise.resolve(cached);
11559 }
11560 return repo.server_.get(query).then(function (payload) {
11561 var node = nodeFromJSON(payload).withIndex(query._queryParams.getIndex());
11562 // if this is a filtered query, then overwrite at path
11563 if (query._queryParams.loadsAllData()) {
11564 syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11565 }
11566 else {
11567 // Simulate `syncTreeAddEventRegistration` without events/listener setup.
11568 // We do this (along with the syncTreeRemoveEventRegistration` below) so that
11569 // `repoGetValue` results have the same cache effects as initial listener(s)
11570 // updates.
11571 var tag = syncTreeRegisterQuery(repo.serverSyncTree_, query);
11572 syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, query._path, node, tag);
11573 // Call `syncTreeRemoveEventRegistration` with a null event registration, since there is none.
11574 // Note: The below code essentially unregisters the query and cleans up any views/syncpoints temporarily created above.
11575 }
11576 var cancels = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, null);
11577 if (cancels.length > 0) {
11578 repoLog(repo, 'unexpected cancel events in repoGetValue');
11579 }
11580 return node;
11581 }, function (err) {
11582 repoLog(repo, 'get for query ' + util.stringify(query) + ' failed: ' + err);
11583 return Promise.reject(new Error(err));
11584 });
11585}
11586function repoSetWithPriority(repo, path, newVal, newPriority, onComplete) {
11587 repoLog(repo, 'set', {
11588 path: path.toString(),
11589 value: newVal,
11590 priority: newPriority
11591 });
11592 // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
11593 // (b) store unresolved paths on JSON parse
11594 var serverValues = repoGenerateServerValues(repo);
11595 var newNodeUnresolved = nodeFromJSON(newVal, newPriority);
11596 var existing = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path);
11597 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, existing, serverValues);
11598 var writeId = repoGetNextWriteId(repo);
11599 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, writeId, true);
11600 eventQueueQueueEvents(repo.eventQueue_, events);
11601 repo.server_.put(path.toString(), newNodeUnresolved.val(/*export=*/ true), function (status, errorReason) {
11602 var success = status === 'ok';
11603 if (!success) {
11604 warn('set at ' + path + ' failed: ' + status);
11605 }
11606 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId, !success);
11607 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, clearEvents);
11608 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11609 });
11610 var affectedPath = repoAbortTransactions(repo, path);
11611 repoRerunTransactions(repo, affectedPath);
11612 // We queued the events above, so just flush the queue here
11613 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, []);
11614}
11615function repoUpdate(repo, path, childrenToMerge, onComplete) {
11616 repoLog(repo, 'update', { path: path.toString(), value: childrenToMerge });
11617 // Start with our existing data and merge each child into it.
11618 var empty = true;
11619 var serverValues = repoGenerateServerValues(repo);
11620 var changedChildren = {};
11621 each(childrenToMerge, function (changedKey, changedValue) {
11622 empty = false;
11623 changedChildren[changedKey] = resolveDeferredValueTree(pathChild(path, changedKey), nodeFromJSON(changedValue), repo.serverSyncTree_, serverValues);
11624 });
11625 if (!empty) {
11626 var writeId_1 = repoGetNextWriteId(repo);
11627 var events = syncTreeApplyUserMerge(repo.serverSyncTree_, path, changedChildren, writeId_1);
11628 eventQueueQueueEvents(repo.eventQueue_, events);
11629 repo.server_.merge(path.toString(), childrenToMerge, function (status, errorReason) {
11630 var success = status === 'ok';
11631 if (!success) {
11632 warn('update at ' + path + ' failed: ' + status);
11633 }
11634 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId_1, !success);
11635 var affectedPath = clearEvents.length > 0 ? repoRerunTransactions(repo, path) : path;
11636 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, clearEvents);
11637 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11638 });
11639 each(childrenToMerge, function (changedPath) {
11640 var affectedPath = repoAbortTransactions(repo, pathChild(path, changedPath));
11641 repoRerunTransactions(repo, affectedPath);
11642 });
11643 // We queued the events above, so just flush the queue here
11644 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, []);
11645 }
11646 else {
11647 log("update() called with empty data. Don't do anything.");
11648 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11649 }
11650}
11651/**
11652 * Applies all of the changes stored up in the onDisconnect_ tree.
11653 */
11654function repoRunOnDisconnectEvents(repo) {
11655 repoLog(repo, 'onDisconnectEvents');
11656 var serverValues = repoGenerateServerValues(repo);
11657 var resolvedOnDisconnectTree = newSparseSnapshotTree();
11658 sparseSnapshotTreeForEachTree(repo.onDisconnect_, newEmptyPath(), function (path, node) {
11659 var resolved = resolveDeferredValueTree(path, node, repo.serverSyncTree_, serverValues);
11660 sparseSnapshotTreeRemember(resolvedOnDisconnectTree, path, resolved);
11661 });
11662 var events = [];
11663 sparseSnapshotTreeForEachTree(resolvedOnDisconnectTree, newEmptyPath(), function (path, snap) {
11664 events = events.concat(syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap));
11665 var affectedPath = repoAbortTransactions(repo, path);
11666 repoRerunTransactions(repo, affectedPath);
11667 });
11668 repo.onDisconnect_ = newSparseSnapshotTree();
11669 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, newEmptyPath(), events);
11670}
11671function repoOnDisconnectCancel(repo, path, onComplete) {
11672 repo.server_.onDisconnectCancel(path.toString(), function (status, errorReason) {
11673 if (status === 'ok') {
11674 sparseSnapshotTreeForget(repo.onDisconnect_, path);
11675 }
11676 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11677 });
11678}
11679function repoOnDisconnectSet(repo, path, value, onComplete) {
11680 var newNode = nodeFromJSON(value);
11681 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11682 if (status === 'ok') {
11683 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11684 }
11685 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11686 });
11687}
11688function repoOnDisconnectSetWithPriority(repo, path, value, priority, onComplete) {
11689 var newNode = nodeFromJSON(value, priority);
11690 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11691 if (status === 'ok') {
11692 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11693 }
11694 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11695 });
11696}
11697function repoOnDisconnectUpdate(repo, path, childrenToMerge, onComplete) {
11698 if (util.isEmpty(childrenToMerge)) {
11699 log("onDisconnect().update() called with empty data. Don't do anything.");
11700 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11701 return;
11702 }
11703 repo.server_.onDisconnectMerge(path.toString(), childrenToMerge, function (status, errorReason) {
11704 if (status === 'ok') {
11705 each(childrenToMerge, function (childName, childNode) {
11706 var newChildNode = nodeFromJSON(childNode);
11707 sparseSnapshotTreeRemember(repo.onDisconnect_, pathChild(path, childName), newChildNode);
11708 });
11709 }
11710 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11711 });
11712}
11713function repoAddEventCallbackForQuery(repo, query, eventRegistration) {
11714 var events;
11715 if (pathGetFront(query._path) === '.info') {
11716 events = syncTreeAddEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11717 }
11718 else {
11719 events = syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11720 }
11721 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11722}
11723function repoRemoveEventCallbackForQuery(repo, query, eventRegistration) {
11724 // These are guaranteed not to raise events, since we're not passing in a cancelError. However, we can future-proof
11725 // a little bit by handling the return values anyways.
11726 var events;
11727 if (pathGetFront(query._path) === '.info') {
11728 events = syncTreeRemoveEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11729 }
11730 else {
11731 events = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11732 }
11733 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11734}
11735function repoInterrupt(repo) {
11736 if (repo.persistentConnection_) {
11737 repo.persistentConnection_.interrupt(INTERRUPT_REASON);
11738 }
11739}
11740function repoResume(repo) {
11741 if (repo.persistentConnection_) {
11742 repo.persistentConnection_.resume(INTERRUPT_REASON);
11743 }
11744}
11745function repoLog(repo) {
11746 var varArgs = [];
11747 for (var _i = 1; _i < arguments.length; _i++) {
11748 varArgs[_i - 1] = arguments[_i];
11749 }
11750 var prefix = '';
11751 if (repo.persistentConnection_) {
11752 prefix = repo.persistentConnection_.id + ':';
11753 }
11754 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs)));
11755}
11756function repoCallOnCompleteCallback(repo, callback, status, errorReason) {
11757 if (callback) {
11758 exceptionGuard(function () {
11759 if (status === 'ok') {
11760 callback(null);
11761 }
11762 else {
11763 var code = (status || 'error').toUpperCase();
11764 var message = code;
11765 if (errorReason) {
11766 message += ': ' + errorReason;
11767 }
11768 var error = new Error(message);
11769 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11770 error.code = code;
11771 callback(error);
11772 }
11773 });
11774 }
11775}
11776/**
11777 * Creates a new transaction, adds it to the transactions we're tracking, and
11778 * sends it to the server if possible.
11779 *
11780 * @param path - Path at which to do transaction.
11781 * @param transactionUpdate - Update callback.
11782 * @param onComplete - Completion callback.
11783 * @param unwatcher - Function that will be called when the transaction no longer
11784 * need data updates for `path`.
11785 * @param applyLocally - Whether or not to make intermediate results visible
11786 */
11787function repoStartTransaction(repo, path, transactionUpdate, onComplete, unwatcher, applyLocally) {
11788 repoLog(repo, 'transaction on ' + path);
11789 // Initialize transaction.
11790 var transaction = {
11791 path: path,
11792 update: transactionUpdate,
11793 onComplete: onComplete,
11794 // One of TransactionStatus enums.
11795 status: null,
11796 // Used when combining transactions at different locations to figure out
11797 // which one goes first.
11798 order: LUIDGenerator(),
11799 // Whether to raise local events for this transaction.
11800 applyLocally: applyLocally,
11801 // Count of how many times we've retried the transaction.
11802 retryCount: 0,
11803 // Function to call to clean up our .on() listener.
11804 unwatcher: unwatcher,
11805 // Stores why a transaction was aborted.
11806 abortReason: null,
11807 currentWriteId: null,
11808 currentInputSnapshot: null,
11809 currentOutputSnapshotRaw: null,
11810 currentOutputSnapshotResolved: null
11811 };
11812 // Run transaction initially.
11813 var currentState = repoGetLatestState(repo, path, undefined);
11814 transaction.currentInputSnapshot = currentState;
11815 var newVal = transaction.update(currentState.val());
11816 if (newVal === undefined) {
11817 // Abort transaction.
11818 transaction.unwatcher();
11819 transaction.currentOutputSnapshotRaw = null;
11820 transaction.currentOutputSnapshotResolved = null;
11821 if (transaction.onComplete) {
11822 transaction.onComplete(null, false, transaction.currentInputSnapshot);
11823 }
11824 }
11825 else {
11826 validateFirebaseData('transaction failed: Data returned ', newVal, transaction.path);
11827 // Mark as run and add to our queue.
11828 transaction.status = 0 /* RUN */;
11829 var queueNode = treeSubTree(repo.transactionQueueTree_, path);
11830 var nodeQueue = treeGetValue(queueNode) || [];
11831 nodeQueue.push(transaction);
11832 treeSetValue(queueNode, nodeQueue);
11833 // Update visibleData and raise events
11834 // Note: We intentionally raise events after updating all of our
11835 // transaction state, since the user could start new transactions from the
11836 // event callbacks.
11837 var priorityForNode = void 0;
11838 if (typeof newVal === 'object' &&
11839 newVal !== null &&
11840 util.contains(newVal, '.priority')) {
11841 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11842 priorityForNode = util.safeGet(newVal, '.priority');
11843 util.assert(isValidPriority(priorityForNode), 'Invalid priority returned by transaction. ' +
11844 'Priority must be a valid string, finite number, server value, or null.');
11845 }
11846 else {
11847 var currentNode = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path) ||
11848 ChildrenNode.EMPTY_NODE;
11849 priorityForNode = currentNode.getPriority().val();
11850 }
11851 var serverValues = repoGenerateServerValues(repo);
11852 var newNodeUnresolved = nodeFromJSON(newVal, priorityForNode);
11853 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, currentState, serverValues);
11854 transaction.currentOutputSnapshotRaw = newNodeUnresolved;
11855 transaction.currentOutputSnapshotResolved = newNode;
11856 transaction.currentWriteId = repoGetNextWriteId(repo);
11857 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, transaction.currentWriteId, transaction.applyLocally);
11858 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11859 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11860 }
11861}
11862/**
11863 * @param excludeSets - A specific set to exclude
11864 */
11865function repoGetLatestState(repo, path, excludeSets) {
11866 return (syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path, excludeSets) ||
11867 ChildrenNode.EMPTY_NODE);
11868}
11869/**
11870 * Sends any already-run transactions that aren't waiting for outstanding
11871 * transactions to complete.
11872 *
11873 * Externally it's called with no arguments, but it calls itself recursively
11874 * with a particular transactionQueueTree node to recurse through the tree.
11875 *
11876 * @param node - transactionQueueTree node to start at.
11877 */
11878function repoSendReadyTransactions(repo, node) {
11879 if (node === void 0) { node = repo.transactionQueueTree_; }
11880 // Before recursing, make sure any completed transactions are removed.
11881 if (!node) {
11882 repoPruneCompletedTransactionsBelowNode(repo, node);
11883 }
11884 if (treeGetValue(node)) {
11885 var queue = repoBuildTransactionQueue(repo, node);
11886 util.assert(queue.length > 0, 'Sending zero length transaction queue');
11887 var allRun = queue.every(function (transaction) { return transaction.status === 0 /* RUN */; });
11888 // If they're all run (and not sent), we can send them. Else, we must wait.
11889 if (allRun) {
11890 repoSendTransactionQueue(repo, treeGetPath(node), queue);
11891 }
11892 }
11893 else if (treeHasChildren(node)) {
11894 treeForEachChild(node, function (childNode) {
11895 repoSendReadyTransactions(repo, childNode);
11896 });
11897 }
11898}
11899/**
11900 * Given a list of run transactions, send them to the server and then handle
11901 * the result (success or failure).
11902 *
11903 * @param path - The location of the queue.
11904 * @param queue - Queue of transactions under the specified location.
11905 */
11906function repoSendTransactionQueue(repo, path, queue) {
11907 // Mark transactions as sent and increment retry count!
11908 var setsToIgnore = queue.map(function (txn) {
11909 return txn.currentWriteId;
11910 });
11911 var latestState = repoGetLatestState(repo, path, setsToIgnore);
11912 var snapToSend = latestState;
11913 var latestHash = latestState.hash();
11914 for (var i = 0; i < queue.length; i++) {
11915 var txn = queue[i];
11916 util.assert(txn.status === 0 /* RUN */, 'tryToSendTransactionQueue_: items in queue should all be run.');
11917 txn.status = 1 /* SENT */;
11918 txn.retryCount++;
11919 var relativePath = newRelativePath(path, txn.path);
11920 // If we've gotten to this point, the output snapshot must be defined.
11921 snapToSend = snapToSend.updateChild(relativePath /** @type {!Node} */, txn.currentOutputSnapshotRaw);
11922 }
11923 var dataToSend = snapToSend.val(true);
11924 var pathToSend = path;
11925 // Send the put.
11926 repo.server_.put(pathToSend.toString(), dataToSend, function (status) {
11927 repoLog(repo, 'transaction put response', {
11928 path: pathToSend.toString(),
11929 status: status
11930 });
11931 var events = [];
11932 if (status === 'ok') {
11933 // Queue up the callbacks and fire them after cleaning up all of our
11934 // transaction state, since the callback could trigger more
11935 // transactions or sets.
11936 var callbacks = [];
11937 var _loop_1 = function (i) {
11938 queue[i].status = 2 /* COMPLETED */;
11939 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId));
11940 if (queue[i].onComplete) {
11941 // We never unset the output snapshot, and given that this
11942 // transaction is complete, it should be set
11943 callbacks.push(function () {
11944 return queue[i].onComplete(null, true, queue[i].currentOutputSnapshotResolved);
11945 });
11946 }
11947 queue[i].unwatcher();
11948 };
11949 for (var i = 0; i < queue.length; i++) {
11950 _loop_1(i);
11951 }
11952 // Now remove the completed transactions.
11953 repoPruneCompletedTransactionsBelowNode(repo, treeSubTree(repo.transactionQueueTree_, path));
11954 // There may be pending transactions that we can now send.
11955 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11956 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11957 // Finally, trigger onComplete callbacks.
11958 for (var i = 0; i < callbacks.length; i++) {
11959 exceptionGuard(callbacks[i]);
11960 }
11961 }
11962 else {
11963 // transactions are no longer sent. Update their status appropriately.
11964 if (status === 'datastale') {
11965 for (var i = 0; i < queue.length; i++) {
11966 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) {
11967 queue[i].status = 4 /* NEEDS_ABORT */;
11968 }
11969 else {
11970 queue[i].status = 0 /* RUN */;
11971 }
11972 }
11973 }
11974 else {
11975 warn('transaction at ' + pathToSend.toString() + ' failed: ' + status);
11976 for (var i = 0; i < queue.length; i++) {
11977 queue[i].status = 4 /* NEEDS_ABORT */;
11978 queue[i].abortReason = status;
11979 }
11980 }
11981 repoRerunTransactions(repo, path);
11982 }
11983 }, latestHash);
11984}
11985/**
11986 * Finds all transactions dependent on the data at changedPath and reruns them.
11987 *
11988 * Should be called any time cached data changes.
11989 *
11990 * Return the highest path that was affected by rerunning transactions. This
11991 * is the path at which events need to be raised for.
11992 *
11993 * @param changedPath - The path in mergedData that changed.
11994 * @returns The rootmost path that was affected by rerunning transactions.
11995 */
11996function repoRerunTransactions(repo, changedPath) {
11997 var rootMostTransactionNode = repoGetAncestorTransactionNode(repo, changedPath);
11998 var path = treeGetPath(rootMostTransactionNode);
11999 var queue = repoBuildTransactionQueue(repo, rootMostTransactionNode);
12000 repoRerunTransactionQueue(repo, queue, path);
12001 return path;
12002}
12003/**
12004 * Does all the work of rerunning transactions (as well as cleans up aborted
12005 * transactions and whatnot).
12006 *
12007 * @param queue - The queue of transactions to run.
12008 * @param path - The path the queue is for.
12009 */
12010function repoRerunTransactionQueue(repo, queue, path) {
12011 if (queue.length === 0) {
12012 return; // Nothing to do!
12013 }
12014 // Queue up the callbacks and fire them after cleaning up all of our
12015 // transaction state, since the callback could trigger more transactions or
12016 // sets.
12017 var callbacks = [];
12018 var events = [];
12019 // Ignore all of the sets we're going to re-run.
12020 var txnsToRerun = queue.filter(function (q) {
12021 return q.status === 0 /* RUN */;
12022 });
12023 var setsToIgnore = txnsToRerun.map(function (q) {
12024 return q.currentWriteId;
12025 });
12026 var _loop_2 = function (i) {
12027 var transaction = queue[i];
12028 var relativePath = newRelativePath(path, transaction.path);
12029 var abortTransaction = false, abortReason;
12030 util.assert(relativePath !== null, 'rerunTransactionsUnderNode_: relativePath should not be null.');
12031 if (transaction.status === 4 /* NEEDS_ABORT */) {
12032 abortTransaction = true;
12033 abortReason = transaction.abortReason;
12034 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12035 }
12036 else if (transaction.status === 0 /* RUN */) {
12037 if (transaction.retryCount >= MAX_TRANSACTION_RETRIES) {
12038 abortTransaction = true;
12039 abortReason = 'maxretry';
12040 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12041 }
12042 else {
12043 // This code reruns a transaction
12044 var currentNode = repoGetLatestState(repo, transaction.path, setsToIgnore);
12045 transaction.currentInputSnapshot = currentNode;
12046 var newData = queue[i].update(currentNode.val());
12047 if (newData !== undefined) {
12048 validateFirebaseData('transaction failed: Data returned ', newData, transaction.path);
12049 var newDataNode = nodeFromJSON(newData);
12050 var hasExplicitPriority = typeof newData === 'object' &&
12051 newData != null &&
12052 util.contains(newData, '.priority');
12053 if (!hasExplicitPriority) {
12054 // Keep the old priority if there wasn't a priority explicitly specified.
12055 newDataNode = newDataNode.updatePriority(currentNode.getPriority());
12056 }
12057 var oldWriteId = transaction.currentWriteId;
12058 var serverValues = repoGenerateServerValues(repo);
12059 var newNodeResolved = resolveDeferredValueSnapshot(newDataNode, currentNode, serverValues);
12060 transaction.currentOutputSnapshotRaw = newDataNode;
12061 transaction.currentOutputSnapshotResolved = newNodeResolved;
12062 transaction.currentWriteId = repoGetNextWriteId(repo);
12063 // Mutates setsToIgnore in place
12064 setsToIgnore.splice(setsToIgnore.indexOf(oldWriteId), 1);
12065 events = events.concat(syncTreeApplyUserOverwrite(repo.serverSyncTree_, transaction.path, newNodeResolved, transaction.currentWriteId, transaction.applyLocally));
12066 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, oldWriteId, true));
12067 }
12068 else {
12069 abortTransaction = true;
12070 abortReason = 'nodata';
12071 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12072 }
12073 }
12074 }
12075 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
12076 events = [];
12077 if (abortTransaction) {
12078 // Abort.
12079 queue[i].status = 2 /* COMPLETED */;
12080 // Removing a listener can trigger pruning which can muck with
12081 // mergedData/visibleData (as it prunes data). So defer the unwatcher
12082 // until we're done.
12083 (function (unwatcher) {
12084 setTimeout(unwatcher, Math.floor(0));
12085 })(queue[i].unwatcher);
12086 if (queue[i].onComplete) {
12087 if (abortReason === 'nodata') {
12088 callbacks.push(function () {
12089 return queue[i].onComplete(null, false, queue[i].currentInputSnapshot);
12090 });
12091 }
12092 else {
12093 callbacks.push(function () {
12094 return queue[i].onComplete(new Error(abortReason), false, null);
12095 });
12096 }
12097 }
12098 }
12099 };
12100 for (var i = 0; i < queue.length; i++) {
12101 _loop_2(i);
12102 }
12103 // Clean up completed transactions.
12104 repoPruneCompletedTransactionsBelowNode(repo, repo.transactionQueueTree_);
12105 // Now fire callbacks, now that we're in a good, known state.
12106 for (var i = 0; i < callbacks.length; i++) {
12107 exceptionGuard(callbacks[i]);
12108 }
12109 // Try to send the transaction result to the server.
12110 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
12111}
12112/**
12113 * Returns the rootmost ancestor node of the specified path that has a pending
12114 * transaction on it, or just returns the node for the given path if there are
12115 * no pending transactions on any ancestor.
12116 *
12117 * @param path - The location to start at.
12118 * @returns The rootmost node with a transaction.
12119 */
12120function repoGetAncestorTransactionNode(repo, path) {
12121 var front;
12122 // Start at the root and walk deeper into the tree towards path until we
12123 // find a node with pending transactions.
12124 var transactionNode = repo.transactionQueueTree_;
12125 front = pathGetFront(path);
12126 while (front !== null && treeGetValue(transactionNode) === undefined) {
12127 transactionNode = treeSubTree(transactionNode, front);
12128 path = pathPopFront(path);
12129 front = pathGetFront(path);
12130 }
12131 return transactionNode;
12132}
12133/**
12134 * Builds the queue of all transactions at or below the specified
12135 * transactionNode.
12136 *
12137 * @param transactionNode
12138 * @returns The generated queue.
12139 */
12140function repoBuildTransactionQueue(repo, transactionNode) {
12141 // Walk any child transaction queues and aggregate them into a single queue.
12142 var transactionQueue = [];
12143 repoAggregateTransactionQueuesForNode(repo, transactionNode, transactionQueue);
12144 // Sort them by the order the transactions were created.
12145 transactionQueue.sort(function (a, b) { return a.order - b.order; });
12146 return transactionQueue;
12147}
12148function repoAggregateTransactionQueuesForNode(repo, node, queue) {
12149 var nodeQueue = treeGetValue(node);
12150 if (nodeQueue) {
12151 for (var i = 0; i < nodeQueue.length; i++) {
12152 queue.push(nodeQueue[i]);
12153 }
12154 }
12155 treeForEachChild(node, function (child) {
12156 repoAggregateTransactionQueuesForNode(repo, child, queue);
12157 });
12158}
12159/**
12160 * Remove COMPLETED transactions at or below this node in the transactionQueueTree_.
12161 */
12162function repoPruneCompletedTransactionsBelowNode(repo, node) {
12163 var queue = treeGetValue(node);
12164 if (queue) {
12165 var to = 0;
12166 for (var from = 0; from < queue.length; from++) {
12167 if (queue[from].status !== 2 /* COMPLETED */) {
12168 queue[to] = queue[from];
12169 to++;
12170 }
12171 }
12172 queue.length = to;
12173 treeSetValue(node, queue.length > 0 ? queue : undefined);
12174 }
12175 treeForEachChild(node, function (childNode) {
12176 repoPruneCompletedTransactionsBelowNode(repo, childNode);
12177 });
12178}
12179/**
12180 * Aborts all transactions on ancestors or descendants of the specified path.
12181 * Called when doing a set() or update() since we consider them incompatible
12182 * with transactions.
12183 *
12184 * @param path - Path for which we want to abort related transactions.
12185 */
12186function repoAbortTransactions(repo, path) {
12187 var affectedPath = treeGetPath(repoGetAncestorTransactionNode(repo, path));
12188 var transactionNode = treeSubTree(repo.transactionQueueTree_, path);
12189 treeForEachAncestor(transactionNode, function (node) {
12190 repoAbortTransactionsOnNode(repo, node);
12191 });
12192 repoAbortTransactionsOnNode(repo, transactionNode);
12193 treeForEachDescendant(transactionNode, function (node) {
12194 repoAbortTransactionsOnNode(repo, node);
12195 });
12196 return affectedPath;
12197}
12198/**
12199 * Abort transactions stored in this transaction queue node.
12200 *
12201 * @param node - Node to abort transactions for.
12202 */
12203function repoAbortTransactionsOnNode(repo, node) {
12204 var queue = treeGetValue(node);
12205 if (queue) {
12206 // Queue up the callbacks and fire them after cleaning up all of our
12207 // transaction state, since the callback could trigger more transactions
12208 // or sets.
12209 var callbacks = [];
12210 // Go through queue. Any already-sent transactions must be marked for
12211 // abort, while the unsent ones can be immediately aborted and removed.
12212 var events = [];
12213 var lastSent = -1;
12214 for (var i = 0; i < queue.length; i++) {
12215 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) ;
12216 else if (queue[i].status === 1 /* SENT */) {
12217 util.assert(lastSent === i - 1, 'All SENT items should be at beginning of queue.');
12218 lastSent = i;
12219 // Mark transaction for abort when it comes back.
12220 queue[i].status = 3 /* SENT_NEEDS_ABORT */;
12221 queue[i].abortReason = 'set';
12222 }
12223 else {
12224 util.assert(queue[i].status === 0 /* RUN */, 'Unexpected transaction status in abort');
12225 // We can abort it immediately.
12226 queue[i].unwatcher();
12227 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId, true));
12228 if (queue[i].onComplete) {
12229 callbacks.push(queue[i].onComplete.bind(null, new Error('set'), false, null));
12230 }
12231 }
12232 }
12233 if (lastSent === -1) {
12234 // We're not waiting for any sent transactions. We can clear the queue.
12235 treeSetValue(node, undefined);
12236 }
12237 else {
12238 // Remove the transactions we aborted.
12239 queue.length = lastSent + 1;
12240 }
12241 // Now fire the callbacks.
12242 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, treeGetPath(node), events);
12243 for (var i = 0; i < callbacks.length; i++) {
12244 exceptionGuard(callbacks[i]);
12245 }
12246 }
12247}
12248
12249/**
12250 * @license
12251 * Copyright 2017 Google LLC
12252 *
12253 * Licensed under the Apache License, Version 2.0 (the "License");
12254 * you may not use this file except in compliance with the License.
12255 * You may obtain a copy of the License at
12256 *
12257 * http://www.apache.org/licenses/LICENSE-2.0
12258 *
12259 * Unless required by applicable law or agreed to in writing, software
12260 * distributed under the License is distributed on an "AS IS" BASIS,
12261 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12262 * See the License for the specific language governing permissions and
12263 * limitations under the License.
12264 */
12265function decodePath(pathString) {
12266 var pathStringDecoded = '';
12267 var pieces = pathString.split('/');
12268 for (var i = 0; i < pieces.length; i++) {
12269 if (pieces[i].length > 0) {
12270 var piece = pieces[i];
12271 try {
12272 piece = decodeURIComponent(piece.replace(/\+/g, ' '));
12273 }
12274 catch (e) { }
12275 pathStringDecoded += '/' + piece;
12276 }
12277 }
12278 return pathStringDecoded;
12279}
12280/**
12281 * @returns key value hash
12282 */
12283function decodeQuery(queryString) {
12284 var e_1, _a;
12285 var results = {};
12286 if (queryString.charAt(0) === '?') {
12287 queryString = queryString.substring(1);
12288 }
12289 try {
12290 for (var _b = tslib.__values(queryString.split('&')), _c = _b.next(); !_c.done; _c = _b.next()) {
12291 var segment = _c.value;
12292 if (segment.length === 0) {
12293 continue;
12294 }
12295 var kv = segment.split('=');
12296 if (kv.length === 2) {
12297 results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
12298 }
12299 else {
12300 warn("Invalid query segment '" + segment + "' in query '" + queryString + "'");
12301 }
12302 }
12303 }
12304 catch (e_1_1) { e_1 = { error: e_1_1 }; }
12305 finally {
12306 try {
12307 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
12308 }
12309 finally { if (e_1) throw e_1.error; }
12310 }
12311 return results;
12312}
12313var parseRepoInfo = function (dataURL, nodeAdmin) {
12314 var parsedUrl = parseDatabaseURL(dataURL), namespace = parsedUrl.namespace;
12315 if (parsedUrl.domain === 'firebase.com') {
12316 fatal(parsedUrl.host +
12317 ' is no longer supported. ' +
12318 'Please use <YOUR FIREBASE>.firebaseio.com instead');
12319 }
12320 // Catch common error of uninitialized namespace value.
12321 if ((!namespace || namespace === 'undefined') &&
12322 parsedUrl.domain !== 'localhost') {
12323 fatal('Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com');
12324 }
12325 if (!parsedUrl.secure) {
12326 warnIfPageIsSecure();
12327 }
12328 var webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
12329 return {
12330 repoInfo: new RepoInfo(parsedUrl.host, parsedUrl.secure, namespace, webSocketOnly, nodeAdmin,
12331 /*persistenceKey=*/ '',
12332 /*includeNamespaceInQueryParams=*/ namespace !== parsedUrl.subdomain),
12333 path: new Path(parsedUrl.pathString)
12334 };
12335};
12336var parseDatabaseURL = function (dataURL) {
12337 // Default to empty strings in the event of a malformed string.
12338 var host = '', domain = '', subdomain = '', pathString = '', namespace = '';
12339 // Always default to SSL, unless otherwise specified.
12340 var secure = true, scheme = 'https', port = 443;
12341 // Don't do any validation here. The caller is responsible for validating the result of parsing.
12342 if (typeof dataURL === 'string') {
12343 // Parse scheme.
12344 var colonInd = dataURL.indexOf('//');
12345 if (colonInd >= 0) {
12346 scheme = dataURL.substring(0, colonInd - 1);
12347 dataURL = dataURL.substring(colonInd + 2);
12348 }
12349 // Parse host, path, and query string.
12350 var slashInd = dataURL.indexOf('/');
12351 if (slashInd === -1) {
12352 slashInd = dataURL.length;
12353 }
12354 var questionMarkInd = dataURL.indexOf('?');
12355 if (questionMarkInd === -1) {
12356 questionMarkInd = dataURL.length;
12357 }
12358 host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
12359 if (slashInd < questionMarkInd) {
12360 // For pathString, questionMarkInd will always come after slashInd
12361 pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
12362 }
12363 var queryParams = decodeQuery(dataURL.substring(Math.min(dataURL.length, questionMarkInd)));
12364 // If we have a port, use scheme for determining if it's secure.
12365 colonInd = host.indexOf(':');
12366 if (colonInd >= 0) {
12367 secure = scheme === 'https' || scheme === 'wss';
12368 port = parseInt(host.substring(colonInd + 1), 10);
12369 }
12370 else {
12371 colonInd = host.length;
12372 }
12373 var hostWithoutPort = host.slice(0, colonInd);
12374 if (hostWithoutPort.toLowerCase() === 'localhost') {
12375 domain = 'localhost';
12376 }
12377 else if (hostWithoutPort.split('.').length <= 2) {
12378 domain = hostWithoutPort;
12379 }
12380 else {
12381 // Interpret the subdomain of a 3 or more component URL as the namespace name.
12382 var dotInd = host.indexOf('.');
12383 subdomain = host.substring(0, dotInd).toLowerCase();
12384 domain = host.substring(dotInd + 1);
12385 // Normalize namespaces to lowercase to share storage / connection.
12386 namespace = subdomain;
12387 }
12388 // Always treat the value of the `ns` as the namespace name if it is present.
12389 if ('ns' in queryParams) {
12390 namespace = queryParams['ns'];
12391 }
12392 }
12393 return {
12394 host: host,
12395 port: port,
12396 domain: domain,
12397 subdomain: subdomain,
12398 secure: secure,
12399 scheme: scheme,
12400 pathString: pathString,
12401 namespace: namespace
12402 };
12403};
12404
12405/**
12406 * @license
12407 * Copyright 2017 Google LLC
12408 *
12409 * Licensed under the Apache License, Version 2.0 (the "License");
12410 * you may not use this file except in compliance with the License.
12411 * You may obtain a copy of the License at
12412 *
12413 * http://www.apache.org/licenses/LICENSE-2.0
12414 *
12415 * Unless required by applicable law or agreed to in writing, software
12416 * distributed under the License is distributed on an "AS IS" BASIS,
12417 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12418 * See the License for the specific language governing permissions and
12419 * limitations under the License.
12420 */
12421/**
12422 * Encapsulates the data needed to raise an event
12423 */
12424var DataEvent = /** @class */ (function () {
12425 /**
12426 * @param eventType - One of: value, child_added, child_changed, child_moved, child_removed
12427 * @param eventRegistration - The function to call to with the event data. User provided
12428 * @param snapshot - The data backing the event
12429 * @param prevName - Optional, the name of the previous child for child_* events.
12430 */
12431 function DataEvent(eventType, eventRegistration, snapshot, prevName) {
12432 this.eventType = eventType;
12433 this.eventRegistration = eventRegistration;
12434 this.snapshot = snapshot;
12435 this.prevName = prevName;
12436 }
12437 DataEvent.prototype.getPath = function () {
12438 var ref = this.snapshot.ref;
12439 if (this.eventType === 'value') {
12440 return ref._path;
12441 }
12442 else {
12443 return ref.parent._path;
12444 }
12445 };
12446 DataEvent.prototype.getEventType = function () {
12447 return this.eventType;
12448 };
12449 DataEvent.prototype.getEventRunner = function () {
12450 return this.eventRegistration.getEventRunner(this);
12451 };
12452 DataEvent.prototype.toString = function () {
12453 return (this.getPath().toString() +
12454 ':' +
12455 this.eventType +
12456 ':' +
12457 util.stringify(this.snapshot.exportVal()));
12458 };
12459 return DataEvent;
12460}());
12461var CancelEvent = /** @class */ (function () {
12462 function CancelEvent(eventRegistration, error, path) {
12463 this.eventRegistration = eventRegistration;
12464 this.error = error;
12465 this.path = path;
12466 }
12467 CancelEvent.prototype.getPath = function () {
12468 return this.path;
12469 };
12470 CancelEvent.prototype.getEventType = function () {
12471 return 'cancel';
12472 };
12473 CancelEvent.prototype.getEventRunner = function () {
12474 return this.eventRegistration.getEventRunner(this);
12475 };
12476 CancelEvent.prototype.toString = function () {
12477 return this.path.toString() + ':cancel';
12478 };
12479 return CancelEvent;
12480}());
12481
12482/**
12483 * @license
12484 * Copyright 2017 Google LLC
12485 *
12486 * Licensed under the Apache License, Version 2.0 (the "License");
12487 * you may not use this file except in compliance with the License.
12488 * You may obtain a copy of the License at
12489 *
12490 * http://www.apache.org/licenses/LICENSE-2.0
12491 *
12492 * Unless required by applicable law or agreed to in writing, software
12493 * distributed under the License is distributed on an "AS IS" BASIS,
12494 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12495 * See the License for the specific language governing permissions and
12496 * limitations under the License.
12497 */
12498/**
12499 * A wrapper class that converts events from the database@exp SDK to the legacy
12500 * Database SDK. Events are not converted directly as event registration relies
12501 * on reference comparison of the original user callback (see `matches()`) and
12502 * relies on equality of the legacy SDK's `context` object.
12503 */
12504var CallbackContext = /** @class */ (function () {
12505 function CallbackContext(snapshotCallback, cancelCallback) {
12506 this.snapshotCallback = snapshotCallback;
12507 this.cancelCallback = cancelCallback;
12508 }
12509 CallbackContext.prototype.onValue = function (expDataSnapshot, previousChildName) {
12510 this.snapshotCallback.call(null, expDataSnapshot, previousChildName);
12511 };
12512 CallbackContext.prototype.onCancel = function (error) {
12513 util.assert(this.hasCancelCallback, 'Raising a cancel event on a listener with no cancel callback');
12514 return this.cancelCallback.call(null, error);
12515 };
12516 Object.defineProperty(CallbackContext.prototype, "hasCancelCallback", {
12517 get: function () {
12518 return !!this.cancelCallback;
12519 },
12520 enumerable: false,
12521 configurable: true
12522 });
12523 CallbackContext.prototype.matches = function (other) {
12524 return (this.snapshotCallback === other.snapshotCallback ||
12525 (this.snapshotCallback.userCallback !== undefined &&
12526 this.snapshotCallback.userCallback ===
12527 other.snapshotCallback.userCallback &&
12528 this.snapshotCallback.context === other.snapshotCallback.context));
12529 };
12530 return CallbackContext;
12531}());
12532
12533/**
12534 * @license
12535 * Copyright 2021 Google LLC
12536 *
12537 * Licensed under the Apache License, Version 2.0 (the "License");
12538 * you may not use this file except in compliance with the License.
12539 * You may obtain a copy of the License at
12540 *
12541 * http://www.apache.org/licenses/LICENSE-2.0
12542 *
12543 * Unless required by applicable law or agreed to in writing, software
12544 * distributed under the License is distributed on an "AS IS" BASIS,
12545 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12546 * See the License for the specific language governing permissions and
12547 * limitations under the License.
12548 */
12549/**
12550 * The `onDisconnect` class allows you to write or clear data when your client
12551 * disconnects from the Database server. These updates occur whether your
12552 * client disconnects cleanly or not, so you can rely on them to clean up data
12553 * even if a connection is dropped or a client crashes.
12554 *
12555 * The `onDisconnect` class is most commonly used to manage presence in
12556 * applications where it is useful to detect how many clients are connected and
12557 * when other clients disconnect. See
12558 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12559 * for more information.
12560 *
12561 * To avoid problems when a connection is dropped before the requests can be
12562 * transferred to the Database server, these functions should be called before
12563 * writing any data.
12564 *
12565 * Note that `onDisconnect` operations are only triggered once. If you want an
12566 * operation to occur each time a disconnect occurs, you'll need to re-establish
12567 * the `onDisconnect` operations each time you reconnect.
12568 */
12569var OnDisconnect = /** @class */ (function () {
12570 /** @hideconstructor */
12571 function OnDisconnect(_repo, _path) {
12572 this._repo = _repo;
12573 this._path = _path;
12574 }
12575 /**
12576 * Cancels all previously queued `onDisconnect()` set or update events for this
12577 * location and all children.
12578 *
12579 * If a write has been queued for this location via a `set()` or `update()` at a
12580 * parent location, the write at this location will be canceled, though writes
12581 * to sibling locations will still occur.
12582 *
12583 * @returns Resolves when synchronization to the server is complete.
12584 */
12585 OnDisconnect.prototype.cancel = function () {
12586 var deferred = new util.Deferred();
12587 repoOnDisconnectCancel(this._repo, this._path, deferred.wrapCallback(function () { }));
12588 return deferred.promise;
12589 };
12590 /**
12591 * Ensures the data at this location is deleted when the client is disconnected
12592 * (due to closing the browser, navigating to a new page, or network issues).
12593 *
12594 * @returns Resolves when synchronization to the server is complete.
12595 */
12596 OnDisconnect.prototype.remove = function () {
12597 validateWritablePath('OnDisconnect.remove', this._path);
12598 var deferred = new util.Deferred();
12599 repoOnDisconnectSet(this._repo, this._path, null, deferred.wrapCallback(function () { }));
12600 return deferred.promise;
12601 };
12602 /**
12603 * Ensures the data at this location is set to the specified value when the
12604 * client is disconnected (due to closing the browser, navigating to a new page,
12605 * or network issues).
12606 *
12607 * `set()` is especially useful for implementing "presence" systems, where a
12608 * value should be changed or cleared when a user disconnects so that they
12609 * appear "offline" to other users. See
12610 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12611 * for more information.
12612 *
12613 * Note that `onDisconnect` operations are only triggered once. If you want an
12614 * operation to occur each time a disconnect occurs, you'll need to re-establish
12615 * the `onDisconnect` operations each time.
12616 *
12617 * @param value - The value to be written to this location on disconnect (can
12618 * be an object, array, string, number, boolean, or null).
12619 * @returns Resolves when synchronization to the Database is complete.
12620 */
12621 OnDisconnect.prototype.set = function (value) {
12622 validateWritablePath('OnDisconnect.set', this._path);
12623 validateFirebaseDataArg('OnDisconnect.set', value, this._path, false);
12624 var deferred = new util.Deferred();
12625 repoOnDisconnectSet(this._repo, this._path, value, deferred.wrapCallback(function () { }));
12626 return deferred.promise;
12627 };
12628 /**
12629 * Ensures the data at this location is set to the specified value and priority
12630 * when the client is disconnected (due to closing the browser, navigating to a
12631 * new page, or network issues).
12632 *
12633 * @param value - The value to be written to this location on disconnect (can
12634 * be an object, array, string, number, boolean, or null).
12635 * @param priority - The priority to be written (string, number, or null).
12636 * @returns Resolves when synchronization to the Database is complete.
12637 */
12638 OnDisconnect.prototype.setWithPriority = function (value, priority) {
12639 validateWritablePath('OnDisconnect.setWithPriority', this._path);
12640 validateFirebaseDataArg('OnDisconnect.setWithPriority', value, this._path, false);
12641 validatePriority('OnDisconnect.setWithPriority', priority, false);
12642 var deferred = new util.Deferred();
12643 repoOnDisconnectSetWithPriority(this._repo, this._path, value, priority, deferred.wrapCallback(function () { }));
12644 return deferred.promise;
12645 };
12646 /**
12647 * Writes multiple values at this location when the client is disconnected (due
12648 * to closing the browser, navigating to a new page, or network issues).
12649 *
12650 * The `values` argument contains multiple property-value pairs that will be
12651 * written to the Database together. Each child property can either be a simple
12652 * property (for example, "name") or a relative path (for example, "name/first")
12653 * from the current location to the data to update.
12654 *
12655 * As opposed to the `set()` method, `update()` can be use to selectively update
12656 * only the referenced properties at the current location (instead of replacing
12657 * all the child properties at the current location).
12658 *
12659 * @param values - Object containing multiple values.
12660 * @returns Resolves when synchronization to the Database is complete.
12661 */
12662 OnDisconnect.prototype.update = function (values) {
12663 validateWritablePath('OnDisconnect.update', this._path);
12664 validateFirebaseMergeDataArg('OnDisconnect.update', values, this._path, false);
12665 var deferred = new util.Deferred();
12666 repoOnDisconnectUpdate(this._repo, this._path, values, deferred.wrapCallback(function () { }));
12667 return deferred.promise;
12668 };
12669 return OnDisconnect;
12670}());
12671
12672/**
12673 * @license
12674 * Copyright 2020 Google LLC
12675 *
12676 * Licensed under the Apache License, Version 2.0 (the "License");
12677 * you may not use this file except in compliance with the License.
12678 * You may obtain a copy of the License at
12679 *
12680 * http://www.apache.org/licenses/LICENSE-2.0
12681 *
12682 * Unless required by applicable law or agreed to in writing, software
12683 * distributed under the License is distributed on an "AS IS" BASIS,
12684 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12685 * See the License for the specific language governing permissions and
12686 * limitations under the License.
12687 */
12688/**
12689 * @internal
12690 */
12691var QueryImpl = /** @class */ (function () {
12692 /**
12693 * @hideconstructor
12694 */
12695 function QueryImpl(_repo, _path, _queryParams, _orderByCalled) {
12696 this._repo = _repo;
12697 this._path = _path;
12698 this._queryParams = _queryParams;
12699 this._orderByCalled = _orderByCalled;
12700 }
12701 Object.defineProperty(QueryImpl.prototype, "key", {
12702 get: function () {
12703 if (pathIsEmpty(this._path)) {
12704 return null;
12705 }
12706 else {
12707 return pathGetBack(this._path);
12708 }
12709 },
12710 enumerable: false,
12711 configurable: true
12712 });
12713 Object.defineProperty(QueryImpl.prototype, "ref", {
12714 get: function () {
12715 return new ReferenceImpl(this._repo, this._path);
12716 },
12717 enumerable: false,
12718 configurable: true
12719 });
12720 Object.defineProperty(QueryImpl.prototype, "_queryIdentifier", {
12721 get: function () {
12722 var obj = queryParamsGetQueryObject(this._queryParams);
12723 var id = ObjectToUniqueKey(obj);
12724 return id === '{}' ? 'default' : id;
12725 },
12726 enumerable: false,
12727 configurable: true
12728 });
12729 Object.defineProperty(QueryImpl.prototype, "_queryObject", {
12730 /**
12731 * An object representation of the query parameters used by this Query.
12732 */
12733 get: function () {
12734 return queryParamsGetQueryObject(this._queryParams);
12735 },
12736 enumerable: false,
12737 configurable: true
12738 });
12739 QueryImpl.prototype.isEqual = function (other) {
12740 other = util.getModularInstance(other);
12741 if (!(other instanceof QueryImpl)) {
12742 return false;
12743 }
12744 var sameRepo = this._repo === other._repo;
12745 var samePath = pathEquals(this._path, other._path);
12746 var sameQueryIdentifier = this._queryIdentifier === other._queryIdentifier;
12747 return sameRepo && samePath && sameQueryIdentifier;
12748 };
12749 QueryImpl.prototype.toJSON = function () {
12750 return this.toString();
12751 };
12752 QueryImpl.prototype.toString = function () {
12753 return this._repo.toString() + pathToUrlEncodedString(this._path);
12754 };
12755 return QueryImpl;
12756}());
12757/**
12758 * Validates that no other order by call has been made
12759 */
12760function validateNoPreviousOrderByCall(query, fnName) {
12761 if (query._orderByCalled === true) {
12762 throw new Error(fnName + ": You can't combine multiple orderBy calls.");
12763 }
12764}
12765/**
12766 * Validates start/end values for queries.
12767 */
12768function validateQueryEndpoints(params) {
12769 var startNode = null;
12770 var endNode = null;
12771 if (params.hasStart()) {
12772 startNode = params.getIndexStartValue();
12773 }
12774 if (params.hasEnd()) {
12775 endNode = params.getIndexEndValue();
12776 }
12777 if (params.getIndex() === KEY_INDEX) {
12778 var tooManyArgsError = 'Query: When ordering by key, you may only pass one argument to ' +
12779 'startAt(), endAt(), or equalTo().';
12780 var wrongArgTypeError = 'Query: When ordering by key, the argument passed to startAt(), startAfter(), ' +
12781 'endAt(), endBefore(), or equalTo() must be a string.';
12782 if (params.hasStart()) {
12783 var startName = params.getIndexStartName();
12784 if (startName !== MIN_NAME) {
12785 throw new Error(tooManyArgsError);
12786 }
12787 else if (typeof startNode !== 'string') {
12788 throw new Error(wrongArgTypeError);
12789 }
12790 }
12791 if (params.hasEnd()) {
12792 var endName = params.getIndexEndName();
12793 if (endName !== MAX_NAME) {
12794 throw new Error(tooManyArgsError);
12795 }
12796 else if (typeof endNode !== 'string') {
12797 throw new Error(wrongArgTypeError);
12798 }
12799 }
12800 }
12801 else if (params.getIndex() === PRIORITY_INDEX) {
12802 if ((startNode != null && !isValidPriority(startNode)) ||
12803 (endNode != null && !isValidPriority(endNode))) {
12804 throw new Error('Query: When ordering by priority, the first argument passed to startAt(), ' +
12805 'startAfter() endAt(), endBefore(), or equalTo() must be a valid priority value ' +
12806 '(null, a number, or a string).');
12807 }
12808 }
12809 else {
12810 util.assert(params.getIndex() instanceof PathIndex ||
12811 params.getIndex() === VALUE_INDEX, 'unknown index type.');
12812 if ((startNode != null && typeof startNode === 'object') ||
12813 (endNode != null && typeof endNode === 'object')) {
12814 throw new Error('Query: First argument passed to startAt(), startAfter(), endAt(), endBefore(), or ' +
12815 'equalTo() cannot be an object.');
12816 }
12817 }
12818}
12819/**
12820 * Validates that limit* has been called with the correct combination of parameters
12821 */
12822function validateLimit(params) {
12823 if (params.hasStart() &&
12824 params.hasEnd() &&
12825 params.hasLimit() &&
12826 !params.hasAnchoredLimit()) {
12827 throw new Error("Query: Can't combine startAt(), startAfter(), endAt(), endBefore(), and limit(). Use " +
12828 'limitToFirst() or limitToLast() instead.');
12829 }
12830}
12831/**
12832 * @internal
12833 */
12834var ReferenceImpl = /** @class */ (function (_super) {
12835 tslib.__extends(ReferenceImpl, _super);
12836 /** @hideconstructor */
12837 function ReferenceImpl(repo, path) {
12838 return _super.call(this, repo, path, new QueryParams(), false) || this;
12839 }
12840 Object.defineProperty(ReferenceImpl.prototype, "parent", {
12841 get: function () {
12842 var parentPath = pathParent(this._path);
12843 return parentPath === null
12844 ? null
12845 : new ReferenceImpl(this._repo, parentPath);
12846 },
12847 enumerable: false,
12848 configurable: true
12849 });
12850 Object.defineProperty(ReferenceImpl.prototype, "root", {
12851 get: function () {
12852 var ref = this;
12853 while (ref.parent !== null) {
12854 ref = ref.parent;
12855 }
12856 return ref;
12857 },
12858 enumerable: false,
12859 configurable: true
12860 });
12861 return ReferenceImpl;
12862}(QueryImpl));
12863/**
12864 * A `DataSnapshot` contains data from a Database location.
12865 *
12866 * Any time you read data from the Database, you receive the data as a
12867 * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach
12868 * with `on()` or `once()`. You can extract the contents of the snapshot as a
12869 * JavaScript object by calling the `val()` method. Alternatively, you can
12870 * traverse into the snapshot by calling `child()` to return child snapshots
12871 * (which you could then call `val()` on).
12872 *
12873 * A `DataSnapshot` is an efficiently generated, immutable copy of the data at
12874 * a Database location. It cannot be modified and will never change (to modify
12875 * data, you always call the `set()` method on a `Reference` directly).
12876 */
12877var DataSnapshot = /** @class */ (function () {
12878 /**
12879 * @param _node - A SnapshotNode to wrap.
12880 * @param ref - The location this snapshot came from.
12881 * @param _index - The iteration order for this snapshot
12882 * @hideconstructor
12883 */
12884 function DataSnapshot(_node,
12885 /**
12886 * The location of this DataSnapshot.
12887 */
12888 ref, _index) {
12889 this._node = _node;
12890 this.ref = ref;
12891 this._index = _index;
12892 }
12893 Object.defineProperty(DataSnapshot.prototype, "priority", {
12894 /**
12895 * Gets the priority value of the data in this `DataSnapshot`.
12896 *
12897 * Applications need not use priority but can order collections by
12898 * ordinary properties (see
12899 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data |Sorting and filtering data}
12900 * ).
12901 */
12902 get: function () {
12903 // typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
12904 return this._node.getPriority().val();
12905 },
12906 enumerable: false,
12907 configurable: true
12908 });
12909 Object.defineProperty(DataSnapshot.prototype, "key", {
12910 /**
12911 * The key (last part of the path) of the location of this `DataSnapshot`.
12912 *
12913 * The last token in a Database location is considered its key. For example,
12914 * "ada" is the key for the /users/ada/ node. Accessing the key on any
12915 * `DataSnapshot` will return the key for the location that generated it.
12916 * However, accessing the key on the root URL of a Database will return
12917 * `null`.
12918 */
12919 get: function () {
12920 return this.ref.key;
12921 },
12922 enumerable: false,
12923 configurable: true
12924 });
12925 Object.defineProperty(DataSnapshot.prototype, "size", {
12926 /** Returns the number of child properties of this `DataSnapshot`. */
12927 get: function () {
12928 return this._node.numChildren();
12929 },
12930 enumerable: false,
12931 configurable: true
12932 });
12933 /**
12934 * Gets another `DataSnapshot` for the location at the specified relative path.
12935 *
12936 * Passing a relative path to the `child()` method of a DataSnapshot returns
12937 * another `DataSnapshot` for the location at the specified relative path. The
12938 * relative path can either be a simple child name (for example, "ada") or a
12939 * deeper, slash-separated path (for example, "ada/name/first"). If the child
12940 * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot`
12941 * whose value is `null`) is returned.
12942 *
12943 * @param path - A relative path to the location of child data.
12944 */
12945 DataSnapshot.prototype.child = function (path) {
12946 var childPath = new Path(path);
12947 var childRef = child(this.ref, path);
12948 return new DataSnapshot(this._node.getChild(childPath), childRef, PRIORITY_INDEX);
12949 };
12950 /**
12951 * Returns true if this `DataSnapshot` contains any data. It is slightly more
12952 * efficient than using `snapshot.val() !== null`.
12953 */
12954 DataSnapshot.prototype.exists = function () {
12955 return !this._node.isEmpty();
12956 };
12957 /**
12958 * Exports the entire contents of the DataSnapshot as a JavaScript object.
12959 *
12960 * The `exportVal()` method is similar to `val()`, except priority information
12961 * is included (if available), making it suitable for backing up your data.
12962 *
12963 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12964 * Array, string, number, boolean, or `null`).
12965 */
12966 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12967 DataSnapshot.prototype.exportVal = function () {
12968 return this._node.val(true);
12969 };
12970 /**
12971 * Enumerates the top-level children in the `DataSnapshot`.
12972 *
12973 * Because of the way JavaScript objects work, the ordering of data in the
12974 * JavaScript object returned by `val()` is not guaranteed to match the
12975 * ordering on the server nor the ordering of `onChildAdded()` events. That is
12976 * where `forEach()` comes in handy. It guarantees the children of a
12977 * `DataSnapshot` will be iterated in their query order.
12978 *
12979 * If no explicit `orderBy*()` method is used, results are returned
12980 * ordered by key (unless priorities are used, in which case, results are
12981 * returned by priority).
12982 *
12983 * @param action - A function that will be called for each child DataSnapshot.
12984 * The callback can return true to cancel further enumeration.
12985 * @returns true if enumeration was canceled due to your callback returning
12986 * true.
12987 */
12988 DataSnapshot.prototype.forEach = function (action) {
12989 var _this = this;
12990 if (this._node.isLeafNode()) {
12991 return false;
12992 }
12993 var childrenNode = this._node;
12994 // Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
12995 return !!childrenNode.forEachChild(this._index, function (key, node) {
12996 return action(new DataSnapshot(node, child(_this.ref, key), PRIORITY_INDEX));
12997 });
12998 };
12999 /**
13000 * Returns true if the specified child path has (non-null) data.
13001 *
13002 * @param path - A relative path to the location of a potential child.
13003 * @returns `true` if data exists at the specified child path; else
13004 * `false`.
13005 */
13006 DataSnapshot.prototype.hasChild = function (path) {
13007 var childPath = new Path(path);
13008 return !this._node.getChild(childPath).isEmpty();
13009 };
13010 /**
13011 * Returns whether or not the `DataSnapshot` has any non-`null` child
13012 * properties.
13013 *
13014 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
13015 * children. If it does, you can enumerate them using `forEach()`. If it
13016 * doesn't, then either this snapshot contains a primitive value (which can be
13017 * retrieved with `val()`) or it is empty (in which case, `val()` will return
13018 * `null`).
13019 *
13020 * @returns true if this snapshot has any children; else false.
13021 */
13022 DataSnapshot.prototype.hasChildren = function () {
13023 if (this._node.isLeafNode()) {
13024 return false;
13025 }
13026 else {
13027 return !this._node.isEmpty();
13028 }
13029 };
13030 /**
13031 * Returns a JSON-serializable representation of this object.
13032 */
13033 DataSnapshot.prototype.toJSON = function () {
13034 return this.exportVal();
13035 };
13036 /**
13037 * Extracts a JavaScript value from a `DataSnapshot`.
13038 *
13039 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
13040 * scalar type (string, number, or boolean), an array, or an object. It may
13041 * also return null, indicating that the `DataSnapshot` is empty (contains no
13042 * data).
13043 *
13044 * @returns The DataSnapshot's contents as a JavaScript value (Object,
13045 * Array, string, number, boolean, or `null`).
13046 */
13047 // eslint-disable-next-line @typescript-eslint/no-explicit-any
13048 DataSnapshot.prototype.val = function () {
13049 return this._node.val();
13050 };
13051 return DataSnapshot;
13052}());
13053/**
13054 *
13055 * Returns a `Reference` representing the location in the Database
13056 * corresponding to the provided path. If no path is provided, the `Reference`
13057 * will point to the root of the Database.
13058 *
13059 * @param db - The database instance to obtain a reference for.
13060 * @param path - Optional path representing the location the returned
13061 * `Reference` will point. If not provided, the returned `Reference` will
13062 * point to the root of the Database.
13063 * @returns If a path is provided, a `Reference`
13064 * pointing to the provided path. Otherwise, a `Reference` pointing to the
13065 * root of the Database.
13066 */
13067function ref(db, path) {
13068 db = util.getModularInstance(db);
13069 db._checkNotDeleted('ref');
13070 return path !== undefined ? child(db._root, path) : db._root;
13071}
13072/**
13073 * Returns a `Reference` representing the location in the Database
13074 * corresponding to the provided Firebase URL.
13075 *
13076 * An exception is thrown if the URL is not a valid Firebase Database URL or it
13077 * has a different domain than the current `Database` instance.
13078 *
13079 * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored
13080 * and are not applied to the returned `Reference`.
13081 *
13082 * @param db - The database instance to obtain a reference for.
13083 * @param url - The Firebase URL at which the returned `Reference` will
13084 * point.
13085 * @returns A `Reference` pointing to the provided
13086 * Firebase URL.
13087 */
13088function refFromURL(db, url) {
13089 db = util.getModularInstance(db);
13090 db._checkNotDeleted('refFromURL');
13091 var parsedURL = parseRepoInfo(url, db._repo.repoInfo_.nodeAdmin);
13092 validateUrl('refFromURL', parsedURL);
13093 var repoInfo = parsedURL.repoInfo;
13094 if (!db._repo.repoInfo_.isCustomHost() &&
13095 repoInfo.host !== db._repo.repoInfo_.host) {
13096 fatal('refFromURL' +
13097 ': Host name does not match the current database: ' +
13098 '(found ' +
13099 repoInfo.host +
13100 ' but expected ' +
13101 db._repo.repoInfo_.host +
13102 ')');
13103 }
13104 return ref(db, parsedURL.path.toString());
13105}
13106/**
13107 * Gets a `Reference` for the location at the specified relative path.
13108 *
13109 * The relative path can either be a simple child name (for example, "ada") or
13110 * a deeper slash-separated path (for example, "ada/name/first").
13111 *
13112 * @param parent - The parent location.
13113 * @param path - A relative path from this location to the desired child
13114 * location.
13115 * @returns The specified child location.
13116 */
13117function child(parent, path) {
13118 parent = util.getModularInstance(parent);
13119 if (pathGetFront(parent._path) === null) {
13120 validateRootPathString('child', 'path', path, false);
13121 }
13122 else {
13123 validatePathString('child', 'path', path, false);
13124 }
13125 return new ReferenceImpl(parent._repo, pathChild(parent._path, path));
13126}
13127/**
13128 * Returns an `OnDisconnect` object - see
13129 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
13130 * for more information on how to use it.
13131 *
13132 * @param ref - The reference to add OnDisconnect triggers for.
13133 */
13134function onDisconnect(ref) {
13135 ref = util.getModularInstance(ref);
13136 return new OnDisconnect(ref._repo, ref._path);
13137}
13138/**
13139 * Generates a new child location using a unique key and returns its
13140 * `Reference`.
13141 *
13142 * This is the most common pattern for adding data to a collection of items.
13143 *
13144 * If you provide a value to `push()`, the value is written to the
13145 * generated location. If you don't pass a value, nothing is written to the
13146 * database and the child remains empty (but you can use the `Reference`
13147 * elsewhere).
13148 *
13149 * The unique keys generated by `push()` are ordered by the current time, so the
13150 * resulting list of items is chronologically sorted. The keys are also
13151 * designed to be unguessable (they contain 72 random bits of entropy).
13152 *
13153 * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}
13154 * </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}
13155 *
13156 * @param parent - The parent location.
13157 * @param value - Optional value to be written at the generated location.
13158 * @returns Combined `Promise` and `Reference`; resolves when write is complete,
13159 * but can be used immediately as the `Reference` to the child location.
13160 */
13161function push(parent, value) {
13162 parent = util.getModularInstance(parent);
13163 validateWritablePath('push', parent._path);
13164 validateFirebaseDataArg('push', value, parent._path, true);
13165 var now = repoServerTime(parent._repo);
13166 var name = nextPushId(now);
13167 // push() returns a ThennableReference whose promise is fulfilled with a
13168 // regular Reference. We use child() to create handles to two different
13169 // references. The first is turned into a ThennableReference below by adding
13170 // then() and catch() methods and is used as the return value of push(). The
13171 // second remains a regular Reference and is used as the fulfilled value of
13172 // the first ThennableReference.
13173 var thennablePushRef = child(parent, name);
13174 var pushRef = child(parent, name);
13175 var promise;
13176 if (value != null) {
13177 promise = set(pushRef, value).then(function () { return pushRef; });
13178 }
13179 else {
13180 promise = Promise.resolve(pushRef);
13181 }
13182 thennablePushRef.then = promise.then.bind(promise);
13183 thennablePushRef.catch = promise.then.bind(promise, undefined);
13184 return thennablePushRef;
13185}
13186/**
13187 * Removes the data at this Database location.
13188 *
13189 * Any data at child locations will also be deleted.
13190 *
13191 * The effect of the remove will be visible immediately and the corresponding
13192 * event 'value' will be triggered. Synchronization of the remove to the
13193 * Firebase servers will also be started, and the returned Promise will resolve
13194 * when complete. If provided, the onComplete callback will be called
13195 * asynchronously after synchronization has finished.
13196 *
13197 * @param ref - The location to remove.
13198 * @returns Resolves when remove on server is complete.
13199 */
13200function remove(ref) {
13201 validateWritablePath('remove', ref._path);
13202 return set(ref, null);
13203}
13204/**
13205 * Writes data to this Database location.
13206 *
13207 * This will overwrite any data at this location and all child locations.
13208 *
13209 * The effect of the write will be visible immediately, and the corresponding
13210 * events ("value", "child_added", etc.) will be triggered. Synchronization of
13211 * the data to the Firebase servers will also be started, and the returned
13212 * Promise will resolve when complete. If provided, the `onComplete` callback
13213 * will be called asynchronously after synchronization has finished.
13214 *
13215 * Passing `null` for the new value is equivalent to calling `remove()`; namely,
13216 * all data at this location and all child locations will be deleted.
13217 *
13218 * `set()` will remove any priority stored at this location, so if priority is
13219 * meant to be preserved, you need to use `setWithPriority()` instead.
13220 *
13221 * Note that modifying data with `set()` will cancel any pending transactions
13222 * at that location, so extreme care should be taken if mixing `set()` and
13223 * `transaction()` to modify the same data.
13224 *
13225 * A single `set()` will generate a single "value" event at the location where
13226 * the `set()` was performed.
13227 *
13228 * @param ref - The location to write to.
13229 * @param value - The value to be written (string, number, boolean, object,
13230 * array, or null).
13231 * @returns Resolves when write to server is complete.
13232 */
13233function set(ref, value) {
13234 ref = util.getModularInstance(ref);
13235 validateWritablePath('set', ref._path);
13236 validateFirebaseDataArg('set', value, ref._path, false);
13237 var deferred = new util.Deferred();
13238 repoSetWithPriority(ref._repo, ref._path, value,
13239 /*priority=*/ null, deferred.wrapCallback(function () { }));
13240 return deferred.promise;
13241}
13242/**
13243 * Sets a priority for the data at this Database location.
13244 *
13245 * Applications need not use priority but can order collections by
13246 * ordinary properties (see
13247 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13248 * ).
13249 *
13250 * @param ref - The location to write to.
13251 * @param priority - The priority to be written (string, number, or null).
13252 * @returns Resolves when write to server is complete.
13253 */
13254function setPriority(ref, priority) {
13255 ref = util.getModularInstance(ref);
13256 validateWritablePath('setPriority', ref._path);
13257 validatePriority('setPriority', priority, false);
13258 var deferred = new util.Deferred();
13259 repoSetWithPriority(ref._repo, pathChild(ref._path, '.priority'), priority, null, deferred.wrapCallback(function () { }));
13260 return deferred.promise;
13261}
13262/**
13263 * Writes data the Database location. Like `set()` but also specifies the
13264 * priority for that data.
13265 *
13266 * Applications need not use priority but can order collections by
13267 * ordinary properties (see
13268 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13269 * ).
13270 *
13271 * @param ref - The location to write to.
13272 * @param value - The value to be written (string, number, boolean, object,
13273 * array, or null).
13274 * @param priority - The priority to be written (string, number, or null).
13275 * @returns Resolves when write to server is complete.
13276 */
13277function setWithPriority(ref, value, priority) {
13278 validateWritablePath('setWithPriority', ref._path);
13279 validateFirebaseDataArg('setWithPriority', value, ref._path, false);
13280 validatePriority('setWithPriority', priority, false);
13281 if (ref.key === '.length' || ref.key === '.keys') {
13282 throw 'setWithPriority failed: ' + ref.key + ' is a read-only object.';
13283 }
13284 var deferred = new util.Deferred();
13285 repoSetWithPriority(ref._repo, ref._path, value, priority, deferred.wrapCallback(function () { }));
13286 return deferred.promise;
13287}
13288/**
13289 * Writes multiple values to the Database at once.
13290 *
13291 * The `values` argument contains multiple property-value pairs that will be
13292 * written to the Database together. Each child property can either be a simple
13293 * property (for example, "name") or a relative path (for example,
13294 * "name/first") from the current location to the data to update.
13295 *
13296 * As opposed to the `set()` method, `update()` can be use to selectively update
13297 * only the referenced properties at the current location (instead of replacing
13298 * all the child properties at the current location).
13299 *
13300 * The effect of the write will be visible immediately, and the corresponding
13301 * events ('value', 'child_added', etc.) will be triggered. Synchronization of
13302 * the data to the Firebase servers will also be started, and the returned
13303 * Promise will resolve when complete. If provided, the `onComplete` callback
13304 * will be called asynchronously after synchronization has finished.
13305 *
13306 * A single `update()` will generate a single "value" event at the location
13307 * where the `update()` was performed, regardless of how many children were
13308 * modified.
13309 *
13310 * Note that modifying data with `update()` will cancel any pending
13311 * transactions at that location, so extreme care should be taken if mixing
13312 * `update()` and `transaction()` to modify the same data.
13313 *
13314 * Passing `null` to `update()` will remove the data at this location.
13315 *
13316 * See
13317 * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}.
13318 *
13319 * @param ref - The location to write to.
13320 * @param values - Object containing multiple values.
13321 * @returns Resolves when update on server is complete.
13322 */
13323function update(ref, values) {
13324 validateFirebaseMergeDataArg('update', values, ref._path, false);
13325 var deferred = new util.Deferred();
13326 repoUpdate(ref._repo, ref._path, values, deferred.wrapCallback(function () { }));
13327 return deferred.promise;
13328}
13329/**
13330 * Gets the most up-to-date result for this query.
13331 *
13332 * @param query - The query to run.
13333 * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is
13334 * available, or rejects if the client is unable to return a value (e.g., if the
13335 * server is unreachable and there is nothing cached).
13336 */
13337function get(query) {
13338 query = util.getModularInstance(query);
13339 return repoGetValue(query._repo, query).then(function (node) {
13340 return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
13341 });
13342}
13343/**
13344 * Represents registration for 'value' events.
13345 */
13346var ValueEventRegistration = /** @class */ (function () {
13347 function ValueEventRegistration(callbackContext) {
13348 this.callbackContext = callbackContext;
13349 }
13350 ValueEventRegistration.prototype.respondsTo = function (eventType) {
13351 return eventType === 'value';
13352 };
13353 ValueEventRegistration.prototype.createEvent = function (change, query) {
13354 var index = query._queryParams.getIndex();
13355 return new DataEvent('value', this, new DataSnapshot(change.snapshotNode, new ReferenceImpl(query._repo, query._path), index));
13356 };
13357 ValueEventRegistration.prototype.getEventRunner = function (eventData) {
13358 var _this = this;
13359 if (eventData.getEventType() === 'cancel') {
13360 return function () {
13361 return _this.callbackContext.onCancel(eventData.error);
13362 };
13363 }
13364 else {
13365 return function () {
13366 return _this.callbackContext.onValue(eventData.snapshot, null);
13367 };
13368 }
13369 };
13370 ValueEventRegistration.prototype.createCancelEvent = function (error, path) {
13371 if (this.callbackContext.hasCancelCallback) {
13372 return new CancelEvent(this, error, path);
13373 }
13374 else {
13375 return null;
13376 }
13377 };
13378 ValueEventRegistration.prototype.matches = function (other) {
13379 if (!(other instanceof ValueEventRegistration)) {
13380 return false;
13381 }
13382 else if (!other.callbackContext || !this.callbackContext) {
13383 // If no callback specified, we consider it to match any callback.
13384 return true;
13385 }
13386 else {
13387 return other.callbackContext.matches(this.callbackContext);
13388 }
13389 };
13390 ValueEventRegistration.prototype.hasAnyCallback = function () {
13391 return this.callbackContext !== null;
13392 };
13393 return ValueEventRegistration;
13394}());
13395/**
13396 * Represents the registration of a child_x event.
13397 */
13398var ChildEventRegistration = /** @class */ (function () {
13399 function ChildEventRegistration(eventType, callbackContext) {
13400 this.eventType = eventType;
13401 this.callbackContext = callbackContext;
13402 }
13403 ChildEventRegistration.prototype.respondsTo = function (eventType) {
13404 var eventToCheck = eventType === 'children_added' ? 'child_added' : eventType;
13405 eventToCheck =
13406 eventToCheck === 'children_removed' ? 'child_removed' : eventToCheck;
13407 return this.eventType === eventToCheck;
13408 };
13409 ChildEventRegistration.prototype.createCancelEvent = function (error, path) {
13410 if (this.callbackContext.hasCancelCallback) {
13411 return new CancelEvent(this, error, path);
13412 }
13413 else {
13414 return null;
13415 }
13416 };
13417 ChildEventRegistration.prototype.createEvent = function (change, query) {
13418 util.assert(change.childName != null, 'Child events should have a childName.');
13419 var childRef = child(new ReferenceImpl(query._repo, query._path), change.childName);
13420 var index = query._queryParams.getIndex();
13421 return new DataEvent(change.type, this, new DataSnapshot(change.snapshotNode, childRef, index), change.prevName);
13422 };
13423 ChildEventRegistration.prototype.getEventRunner = function (eventData) {
13424 var _this = this;
13425 if (eventData.getEventType() === 'cancel') {
13426 return function () {
13427 return _this.callbackContext.onCancel(eventData.error);
13428 };
13429 }
13430 else {
13431 return function () {
13432 return _this.callbackContext.onValue(eventData.snapshot, eventData.prevName);
13433 };
13434 }
13435 };
13436 ChildEventRegistration.prototype.matches = function (other) {
13437 if (other instanceof ChildEventRegistration) {
13438 return (this.eventType === other.eventType &&
13439 (!this.callbackContext ||
13440 !other.callbackContext ||
13441 this.callbackContext.matches(other.callbackContext)));
13442 }
13443 return false;
13444 };
13445 ChildEventRegistration.prototype.hasAnyCallback = function () {
13446 return !!this.callbackContext;
13447 };
13448 return ChildEventRegistration;
13449}());
13450function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) {
13451 var cancelCallback;
13452 if (typeof cancelCallbackOrListenOptions === 'object') {
13453 cancelCallback = undefined;
13454 options = cancelCallbackOrListenOptions;
13455 }
13456 if (typeof cancelCallbackOrListenOptions === 'function') {
13457 cancelCallback = cancelCallbackOrListenOptions;
13458 }
13459 if (options && options.onlyOnce) {
13460 var userCallback_1 = callback;
13461 var onceCallback = function (dataSnapshot, previousChildName) {
13462 repoRemoveEventCallbackForQuery(query._repo, query, container);
13463 userCallback_1(dataSnapshot, previousChildName);
13464 };
13465 onceCallback.userCallback = callback.userCallback;
13466 onceCallback.context = callback.context;
13467 callback = onceCallback;
13468 }
13469 var callbackContext = new CallbackContext(callback, cancelCallback || undefined);
13470 var container = eventType === 'value'
13471 ? new ValueEventRegistration(callbackContext)
13472 : new ChildEventRegistration(eventType, callbackContext);
13473 repoAddEventCallbackForQuery(query._repo, query, container);
13474 return function () { return repoRemoveEventCallbackForQuery(query._repo, query, container); };
13475}
13476function onValue(query, callback, cancelCallbackOrListenOptions, options) {
13477 return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options);
13478}
13479function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) {
13480 return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options);
13481}
13482function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) {
13483 return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options);
13484}
13485function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) {
13486 return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options);
13487}
13488function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) {
13489 return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options);
13490}
13491/**
13492 * Detaches a callback previously attached with `on()`.
13493 *
13494 * Detach a callback previously attached with `on()`. Note that if `on()` was
13495 * called multiple times with the same eventType and callback, the callback
13496 * will be called multiple times for each event, and `off()` must be called
13497 * multiple times to remove the callback. Calling `off()` on a parent listener
13498 * will not automatically remove listeners registered on child nodes, `off()`
13499 * must also be called on any child listeners to remove the callback.
13500 *
13501 * If a callback is not specified, all callbacks for the specified eventType
13502 * will be removed. Similarly, if no eventType is specified, all callbacks
13503 * for the `Reference` will be removed.
13504 *
13505 * Individual listeners can also be removed by invoking their unsubscribe
13506 * callbacks.
13507 *
13508 * @param query - The query that the listener was registered with.
13509 * @param eventType - One of the following strings: "value", "child_added",
13510 * "child_changed", "child_removed", or "child_moved." If omitted, all callbacks
13511 * for the `Reference` will be removed.
13512 * @param callback - The callback function that was passed to `on()` or
13513 * `undefined` to remove all callbacks.
13514 */
13515function off(query, eventType, callback) {
13516 var container = null;
13517 var expCallback = callback ? new CallbackContext(callback) : null;
13518 if (eventType === 'value') {
13519 container = new ValueEventRegistration(expCallback);
13520 }
13521 else if (eventType) {
13522 container = new ChildEventRegistration(eventType, expCallback);
13523 }
13524 repoRemoveEventCallbackForQuery(query._repo, query, container);
13525}
13526/**
13527 * A `QueryConstraint` is used to narrow the set of documents returned by a
13528 * Database query. `QueryConstraint`s are created by invoking {@link endAt},
13529 * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link
13530 * limitToFirst}, {@link limitToLast}, {@link orderByChild},
13531 * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} ,
13532 * {@link orderByValue} or {@link equalTo} and
13533 * can then be passed to {@link query} to create a new query instance that
13534 * also contains this `QueryConstraint`.
13535 */
13536var QueryConstraint = /** @class */ (function () {
13537 function QueryConstraint() {
13538 }
13539 return QueryConstraint;
13540}());
13541var QueryEndAtConstraint = /** @class */ (function (_super) {
13542 tslib.__extends(QueryEndAtConstraint, _super);
13543 function QueryEndAtConstraint(_value, _key) {
13544 var _this = _super.call(this) || this;
13545 _this._value = _value;
13546 _this._key = _key;
13547 return _this;
13548 }
13549 QueryEndAtConstraint.prototype._apply = function (query) {
13550 validateFirebaseDataArg('endAt', this._value, query._path, true);
13551 var newParams = queryParamsEndAt(query._queryParams, this._value, this._key);
13552 validateLimit(newParams);
13553 validateQueryEndpoints(newParams);
13554 if (query._queryParams.hasEnd()) {
13555 throw new Error('endAt: Starting point was already set (by another call to endAt, ' +
13556 'endBefore or equalTo).');
13557 }
13558 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13559 };
13560 return QueryEndAtConstraint;
13561}(QueryConstraint));
13562/**
13563 * Creates a `QueryConstraint` with the specified ending point.
13564 *
13565 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13566 * allows you to choose arbitrary starting and ending points for your queries.
13567 *
13568 * The ending point is inclusive, so children with exactly the specified value
13569 * will be included in the query. The optional key argument can be used to
13570 * further limit the range of the query. If it is specified, then children that
13571 * have exactly the specified value must also have a key name less than or equal
13572 * to the specified key.
13573 *
13574 * You can read more about `endAt()` in
13575 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13576 *
13577 * @param value - The value to end at. The argument type depends on which
13578 * `orderBy*()` function was used in this query. Specify a value that matches
13579 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13580 * value must be a string.
13581 * @param key - The child key to end at, among the children with the previously
13582 * specified priority. This argument is only allowed if ordering by child,
13583 * value, or priority.
13584 */
13585function endAt(value, key) {
13586 validateKey('endAt', 'key', key, true);
13587 return new QueryEndAtConstraint(value, key);
13588}
13589var QueryEndBeforeConstraint = /** @class */ (function (_super) {
13590 tslib.__extends(QueryEndBeforeConstraint, _super);
13591 function QueryEndBeforeConstraint(_value, _key) {
13592 var _this = _super.call(this) || this;
13593 _this._value = _value;
13594 _this._key = _key;
13595 return _this;
13596 }
13597 QueryEndBeforeConstraint.prototype._apply = function (query) {
13598 validateFirebaseDataArg('endBefore', this._value, query._path, false);
13599 var newParams = queryParamsEndBefore(query._queryParams, this._value, this._key);
13600 validateLimit(newParams);
13601 validateQueryEndpoints(newParams);
13602 if (query._queryParams.hasEnd()) {
13603 throw new Error('endBefore: Starting point was already set (by another call to endAt, ' +
13604 'endBefore or equalTo).');
13605 }
13606 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13607 };
13608 return QueryEndBeforeConstraint;
13609}(QueryConstraint));
13610/**
13611 * Creates a `QueryConstraint` with the specified ending point (exclusive).
13612 *
13613 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13614 * allows you to choose arbitrary starting and ending points for your queries.
13615 *
13616 * The ending point is exclusive. If only a value is provided, children
13617 * with a value less than the specified value will be included in the query.
13618 * If a key is specified, then children must have a value lesss than or equal
13619 * to the specified value and a a key name less than the specified key.
13620 *
13621 * @param value - The value to end before. The argument type depends on which
13622 * `orderBy*()` function was used in this query. Specify a value that matches
13623 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13624 * value must be a string.
13625 * @param key - The child key to end before, among the children with the
13626 * previously specified priority. This argument is only allowed if ordering by
13627 * child, value, or priority.
13628 */
13629function endBefore(value, key) {
13630 validateKey('endBefore', 'key', key, true);
13631 return new QueryEndBeforeConstraint(value, key);
13632}
13633var QueryStartAtConstraint = /** @class */ (function (_super) {
13634 tslib.__extends(QueryStartAtConstraint, _super);
13635 function QueryStartAtConstraint(_value, _key) {
13636 var _this = _super.call(this) || this;
13637 _this._value = _value;
13638 _this._key = _key;
13639 return _this;
13640 }
13641 QueryStartAtConstraint.prototype._apply = function (query) {
13642 validateFirebaseDataArg('startAt', this._value, query._path, true);
13643 var newParams = queryParamsStartAt(query._queryParams, this._value, this._key);
13644 validateLimit(newParams);
13645 validateQueryEndpoints(newParams);
13646 if (query._queryParams.hasStart()) {
13647 throw new Error('startAt: Starting point was already set (by another call to startAt, ' +
13648 'startBefore or equalTo).');
13649 }
13650 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13651 };
13652 return QueryStartAtConstraint;
13653}(QueryConstraint));
13654/**
13655 * Creates a `QueryConstraint` with the specified starting point.
13656 *
13657 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13658 * allows you to choose arbitrary starting and ending points for your queries.
13659 *
13660 * The starting point is inclusive, so children with exactly the specified value
13661 * will be included in the query. The optional key argument can be used to
13662 * further limit the range of the query. If it is specified, then children that
13663 * have exactly the specified value must also have a key name greater than or
13664 * equal to the specified key.
13665 *
13666 * You can read more about `startAt()` in
13667 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13668 *
13669 * @param value - The value to start at. The argument type depends on which
13670 * `orderBy*()` function was used in this query. Specify a value that matches
13671 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13672 * value must be a string.
13673 * @param key - The child key to start at. This argument is only allowed if
13674 * ordering by child, value, or priority.
13675 */
13676function startAt(value, key) {
13677 if (value === void 0) { value = null; }
13678 validateKey('startAt', 'key', key, true);
13679 return new QueryStartAtConstraint(value, key);
13680}
13681var QueryStartAfterConstraint = /** @class */ (function (_super) {
13682 tslib.__extends(QueryStartAfterConstraint, _super);
13683 function QueryStartAfterConstraint(_value, _key) {
13684 var _this = _super.call(this) || this;
13685 _this._value = _value;
13686 _this._key = _key;
13687 return _this;
13688 }
13689 QueryStartAfterConstraint.prototype._apply = function (query) {
13690 validateFirebaseDataArg('startAfter', this._value, query._path, false);
13691 var newParams = queryParamsStartAfter(query._queryParams, this._value, this._key);
13692 validateLimit(newParams);
13693 validateQueryEndpoints(newParams);
13694 if (query._queryParams.hasStart()) {
13695 throw new Error('startAfter: Starting point was already set (by another call to startAt, ' +
13696 'startAfter, or equalTo).');
13697 }
13698 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13699 };
13700 return QueryStartAfterConstraint;
13701}(QueryConstraint));
13702/**
13703 * Creates a `QueryConstraint` with the specified starting point (exclusive).
13704 *
13705 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13706 * allows you to choose arbitrary starting and ending points for your queries.
13707 *
13708 * The starting point is exclusive. If only a value is provided, children
13709 * with a value greater than the specified value will be included in the query.
13710 * If a key is specified, then children must have a value greater than or equal
13711 * to the specified value and a a key name greater than the specified key.
13712 *
13713 * @param value - The value to start after. The argument type depends on which
13714 * `orderBy*()` function was used in this query. Specify a value that matches
13715 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13716 * value must be a string.
13717 * @param key - The child key to start after. This argument is only allowed if
13718 * ordering by child, value, or priority.
13719 */
13720function startAfter(value, key) {
13721 validateKey('startAfter', 'key', key, true);
13722 return new QueryStartAfterConstraint(value, key);
13723}
13724var QueryLimitToFirstConstraint = /** @class */ (function (_super) {
13725 tslib.__extends(QueryLimitToFirstConstraint, _super);
13726 function QueryLimitToFirstConstraint(_limit) {
13727 var _this = _super.call(this) || this;
13728 _this._limit = _limit;
13729 return _this;
13730 }
13731 QueryLimitToFirstConstraint.prototype._apply = function (query) {
13732 if (query._queryParams.hasLimit()) {
13733 throw new Error('limitToFirst: Limit was already set (by another call to limitToFirst ' +
13734 'or limitToLast).');
13735 }
13736 return new QueryImpl(query._repo, query._path, queryParamsLimitToFirst(query._queryParams, this._limit), query._orderByCalled);
13737 };
13738 return QueryLimitToFirstConstraint;
13739}(QueryConstraint));
13740/**
13741 * Creates a new `QueryConstraint` that if limited to the first specific number
13742 * of children.
13743 *
13744 * The `limitToFirst()` method is used to set a maximum number of children to be
13745 * synced for a given callback. If we set a limit of 100, we will initially only
13746 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13747 * stored in our Database, a `child_added` event will fire for each message.
13748 * However, if we have over 100 messages, we will only receive a `child_added`
13749 * event for the first 100 ordered messages. As items change, we will receive
13750 * `child_removed` events for each item that drops out of the active list so
13751 * that the total number stays at 100.
13752 *
13753 * You can read more about `limitToFirst()` in
13754 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13755 *
13756 * @param limit - The maximum number of nodes to include in this query.
13757 */
13758function limitToFirst(limit) {
13759 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13760 throw new Error('limitToFirst: First argument must be a positive integer.');
13761 }
13762 return new QueryLimitToFirstConstraint(limit);
13763}
13764var QueryLimitToLastConstraint = /** @class */ (function (_super) {
13765 tslib.__extends(QueryLimitToLastConstraint, _super);
13766 function QueryLimitToLastConstraint(_limit) {
13767 var _this = _super.call(this) || this;
13768 _this._limit = _limit;
13769 return _this;
13770 }
13771 QueryLimitToLastConstraint.prototype._apply = function (query) {
13772 if (query._queryParams.hasLimit()) {
13773 throw new Error('limitToLast: Limit was already set (by another call to limitToFirst ' +
13774 'or limitToLast).');
13775 }
13776 return new QueryImpl(query._repo, query._path, queryParamsLimitToLast(query._queryParams, this._limit), query._orderByCalled);
13777 };
13778 return QueryLimitToLastConstraint;
13779}(QueryConstraint));
13780/**
13781 * Creates a new `QueryConstraint` that is limited to return only the last
13782 * specified number of children.
13783 *
13784 * The `limitToLast()` method is used to set a maximum number of children to be
13785 * synced for a given callback. If we set a limit of 100, we will initially only
13786 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13787 * stored in our Database, a `child_added` event will fire for each message.
13788 * However, if we have over 100 messages, we will only receive a `child_added`
13789 * event for the last 100 ordered messages. As items change, we will receive
13790 * `child_removed` events for each item that drops out of the active list so
13791 * that the total number stays at 100.
13792 *
13793 * You can read more about `limitToLast()` in
13794 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13795 *
13796 * @param limit - The maximum number of nodes to include in this query.
13797 */
13798function limitToLast(limit) {
13799 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13800 throw new Error('limitToLast: First argument must be a positive integer.');
13801 }
13802 return new QueryLimitToLastConstraint(limit);
13803}
13804var QueryOrderByChildConstraint = /** @class */ (function (_super) {
13805 tslib.__extends(QueryOrderByChildConstraint, _super);
13806 function QueryOrderByChildConstraint(_path) {
13807 var _this = _super.call(this) || this;
13808 _this._path = _path;
13809 return _this;
13810 }
13811 QueryOrderByChildConstraint.prototype._apply = function (query) {
13812 validateNoPreviousOrderByCall(query, 'orderByChild');
13813 var parsedPath = new Path(this._path);
13814 if (pathIsEmpty(parsedPath)) {
13815 throw new Error('orderByChild: cannot pass in empty path. Use orderByValue() instead.');
13816 }
13817 var index = new PathIndex(parsedPath);
13818 var newParams = queryParamsOrderBy(query._queryParams, index);
13819 validateQueryEndpoints(newParams);
13820 return new QueryImpl(query._repo, query._path, newParams,
13821 /*orderByCalled=*/ true);
13822 };
13823 return QueryOrderByChildConstraint;
13824}(QueryConstraint));
13825/**
13826 * Creates a new `QueryConstraint` that orders by the specified child key.
13827 *
13828 * Queries can only order by one key at a time. Calling `orderByChild()`
13829 * multiple times on the same query is an error.
13830 *
13831 * Firebase queries allow you to order your data by any child key on the fly.
13832 * However, if you know in advance what your indexes will be, you can define
13833 * them via the .indexOn rule in your Security Rules for better performance. See
13834 * the{@link https://firebase.google.com/docs/database/security/indexing-data}
13835 * rule for more information.
13836 *
13837 * You can read more about `orderByChild()` in
13838 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13839 *
13840 * @param path - The path to order by.
13841 */
13842function orderByChild(path) {
13843 if (path === '$key') {
13844 throw new Error('orderByChild: "$key" is invalid. Use orderByKey() instead.');
13845 }
13846 else if (path === '$priority') {
13847 throw new Error('orderByChild: "$priority" is invalid. Use orderByPriority() instead.');
13848 }
13849 else if (path === '$value') {
13850 throw new Error('orderByChild: "$value" is invalid. Use orderByValue() instead.');
13851 }
13852 validatePathString('orderByChild', 'path', path, false);
13853 return new QueryOrderByChildConstraint(path);
13854}
13855var QueryOrderByKeyConstraint = /** @class */ (function (_super) {
13856 tslib.__extends(QueryOrderByKeyConstraint, _super);
13857 function QueryOrderByKeyConstraint() {
13858 return _super !== null && _super.apply(this, arguments) || this;
13859 }
13860 QueryOrderByKeyConstraint.prototype._apply = function (query) {
13861 validateNoPreviousOrderByCall(query, 'orderByKey');
13862 var newParams = queryParamsOrderBy(query._queryParams, KEY_INDEX);
13863 validateQueryEndpoints(newParams);
13864 return new QueryImpl(query._repo, query._path, newParams,
13865 /*orderByCalled=*/ true);
13866 };
13867 return QueryOrderByKeyConstraint;
13868}(QueryConstraint));
13869/**
13870 * Creates a new `QueryConstraint` that orders by the key.
13871 *
13872 * Sorts the results of a query by their (ascending) key values.
13873 *
13874 * You can read more about `orderByKey()` in
13875 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13876 */
13877function orderByKey() {
13878 return new QueryOrderByKeyConstraint();
13879}
13880var QueryOrderByPriorityConstraint = /** @class */ (function (_super) {
13881 tslib.__extends(QueryOrderByPriorityConstraint, _super);
13882 function QueryOrderByPriorityConstraint() {
13883 return _super !== null && _super.apply(this, arguments) || this;
13884 }
13885 QueryOrderByPriorityConstraint.prototype._apply = function (query) {
13886 validateNoPreviousOrderByCall(query, 'orderByPriority');
13887 var newParams = queryParamsOrderBy(query._queryParams, PRIORITY_INDEX);
13888 validateQueryEndpoints(newParams);
13889 return new QueryImpl(query._repo, query._path, newParams,
13890 /*orderByCalled=*/ true);
13891 };
13892 return QueryOrderByPriorityConstraint;
13893}(QueryConstraint));
13894/**
13895 * Creates a new `QueryConstraint` that orders by priority.
13896 *
13897 * Applications need not use priority but can order collections by
13898 * ordinary properties (see
13899 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}
13900 * for alternatives to priority.
13901 */
13902function orderByPriority() {
13903 return new QueryOrderByPriorityConstraint();
13904}
13905var QueryOrderByValueConstraint = /** @class */ (function (_super) {
13906 tslib.__extends(QueryOrderByValueConstraint, _super);
13907 function QueryOrderByValueConstraint() {
13908 return _super !== null && _super.apply(this, arguments) || this;
13909 }
13910 QueryOrderByValueConstraint.prototype._apply = function (query) {
13911 validateNoPreviousOrderByCall(query, 'orderByValue');
13912 var newParams = queryParamsOrderBy(query._queryParams, VALUE_INDEX);
13913 validateQueryEndpoints(newParams);
13914 return new QueryImpl(query._repo, query._path, newParams,
13915 /*orderByCalled=*/ true);
13916 };
13917 return QueryOrderByValueConstraint;
13918}(QueryConstraint));
13919/**
13920 * Creates a new `QueryConstraint` that orders by value.
13921 *
13922 * If the children of a query are all scalar values (string, number, or
13923 * boolean), you can order the results by their (ascending) values.
13924 *
13925 * You can read more about `orderByValue()` in
13926 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13927 */
13928function orderByValue() {
13929 return new QueryOrderByValueConstraint();
13930}
13931var QueryEqualToValueConstraint = /** @class */ (function (_super) {
13932 tslib.__extends(QueryEqualToValueConstraint, _super);
13933 function QueryEqualToValueConstraint(_value, _key) {
13934 var _this = _super.call(this) || this;
13935 _this._value = _value;
13936 _this._key = _key;
13937 return _this;
13938 }
13939 QueryEqualToValueConstraint.prototype._apply = function (query) {
13940 validateFirebaseDataArg('equalTo', this._value, query._path, false);
13941 if (query._queryParams.hasStart()) {
13942 throw new Error('equalTo: Starting point was already set (by another call to startAt/startAfter or ' +
13943 'equalTo).');
13944 }
13945 if (query._queryParams.hasEnd()) {
13946 throw new Error('equalTo: Ending point was already set (by another call to endAt/endBefore or ' +
13947 'equalTo).');
13948 }
13949 return new QueryEndAtConstraint(this._value, this._key)._apply(new QueryStartAtConstraint(this._value, this._key)._apply(query));
13950 };
13951 return QueryEqualToValueConstraint;
13952}(QueryConstraint));
13953/**
13954 * Creates a `QueryConstraint` that includes children that match the specified
13955 * value.
13956 *
13957 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13958 * allows you to choose arbitrary starting and ending points for your queries.
13959 *
13960 * The optional key argument can be used to further limit the range of the
13961 * query. If it is specified, then children that have exactly the specified
13962 * value must also have exactly the specified key as their key name. This can be
13963 * used to filter result sets with many matches for the same value.
13964 *
13965 * You can read more about `equalTo()` in
13966 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13967 *
13968 * @param value - The value to match for. The argument type depends on which
13969 * `orderBy*()` function was used in this query. Specify a value that matches
13970 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13971 * value must be a string.
13972 * @param key - The child key to start at, among the children with the
13973 * previously specified priority. This argument is only allowed if ordering by
13974 * child, value, or priority.
13975 */
13976function equalTo(value, key) {
13977 validateKey('equalTo', 'key', key, true);
13978 return new QueryEqualToValueConstraint(value, key);
13979}
13980/**
13981 * Creates a new immutable instance of `Query` that is extended to also include
13982 * additional query constraints.
13983 *
13984 * @param query - The Query instance to use as a base for the new constraints.
13985 * @param queryConstraints - The list of `QueryConstraint`s to apply.
13986 * @throws if any of the provided query constraints cannot be combined with the
13987 * existing or new constraints.
13988 */
13989function query(query) {
13990 var e_1, _a;
13991 var queryConstraints = [];
13992 for (var _i = 1; _i < arguments.length; _i++) {
13993 queryConstraints[_i - 1] = arguments[_i];
13994 }
13995 var queryImpl = util.getModularInstance(query);
13996 try {
13997 for (var queryConstraints_1 = tslib.__values(queryConstraints), queryConstraints_1_1 = queryConstraints_1.next(); !queryConstraints_1_1.done; queryConstraints_1_1 = queryConstraints_1.next()) {
13998 var constraint = queryConstraints_1_1.value;
13999 queryImpl = constraint._apply(queryImpl);
14000 }
14001 }
14002 catch (e_1_1) { e_1 = { error: e_1_1 }; }
14003 finally {
14004 try {
14005 if (queryConstraints_1_1 && !queryConstraints_1_1.done && (_a = queryConstraints_1.return)) _a.call(queryConstraints_1);
14006 }
14007 finally { if (e_1) throw e_1.error; }
14008 }
14009 return queryImpl;
14010}
14011/**
14012 * Define reference constructor in various modules
14013 *
14014 * We are doing this here to avoid several circular
14015 * dependency issues
14016 */
14017syncPointSetReferenceConstructor(ReferenceImpl);
14018syncTreeSetReferenceConstructor(ReferenceImpl);
14019
14020/**
14021 * @license
14022 * Copyright 2020 Google LLC
14023 *
14024 * Licensed under the Apache License, Version 2.0 (the "License");
14025 * you may not use this file except in compliance with the License.
14026 * You may obtain a copy of the License at
14027 *
14028 * http://www.apache.org/licenses/LICENSE-2.0
14029 *
14030 * Unless required by applicable law or agreed to in writing, software
14031 * distributed under the License is distributed on an "AS IS" BASIS,
14032 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14033 * See the License for the specific language governing permissions and
14034 * limitations under the License.
14035 */
14036/**
14037 * This variable is also defined in the firebase Node.js Admin SDK. Before
14038 * modifying this definition, consult the definition in:
14039 *
14040 * https://github.com/firebase/firebase-admin-node
14041 *
14042 * and make sure the two are consistent.
14043 */
14044var FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST';
14045/**
14046 * Creates and caches `Repo` instances.
14047 */
14048var repos = {};
14049/**
14050 * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes).
14051 */
14052var useRestClient = false;
14053/**
14054 * Update an existing `Repo` in place to point to a new host/port.
14055 */
14056function repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider) {
14057 repo.repoInfo_ = new RepoInfo(host + ":" + port,
14058 /* secure= */ false, repo.repoInfo_.namespace, repo.repoInfo_.webSocketOnly, repo.repoInfo_.nodeAdmin, repo.repoInfo_.persistenceKey, repo.repoInfo_.includeNamespaceInQueryParams);
14059 if (tokenProvider) {
14060 repo.authTokenProvider_ = tokenProvider;
14061 }
14062}
14063/**
14064 * This function should only ever be called to CREATE a new database instance.
14065 * @internal
14066 */
14067function repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url, nodeAdmin) {
14068 var dbUrl = url || app.options.databaseURL;
14069 if (dbUrl === undefined) {
14070 if (!app.options.projectId) {
14071 fatal("Can't determine Firebase Database URL. Be sure to include " +
14072 ' a Project ID when calling firebase.initializeApp().');
14073 }
14074 log('Using default host for project ', app.options.projectId);
14075 dbUrl = app.options.projectId + "-default-rtdb.firebaseio.com";
14076 }
14077 var parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14078 var repoInfo = parsedUrl.repoInfo;
14079 var isEmulator;
14080 var dbEmulatorHost = undefined;
14081 if (typeof process !== 'undefined' && process.env) {
14082 dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR];
14083 }
14084 if (dbEmulatorHost) {
14085 isEmulator = true;
14086 dbUrl = "http://" + dbEmulatorHost + "?ns=" + repoInfo.namespace;
14087 parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14088 repoInfo = parsedUrl.repoInfo;
14089 }
14090 else {
14091 isEmulator = !parsedUrl.repoInfo.secure;
14092 }
14093 var authTokenProvider = nodeAdmin && isEmulator
14094 ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER)
14095 : new FirebaseAuthTokenProvider(app.name, app.options, authProvider);
14096 validateUrl('Invalid Firebase Database URL', parsedUrl);
14097 if (!pathIsEmpty(parsedUrl.path)) {
14098 fatal('Database URL must point to the root of a Firebase Database ' +
14099 '(not including a child path).');
14100 }
14101 var repo = repoManagerCreateRepo(repoInfo, app, authTokenProvider, new AppCheckTokenProvider(app.name, appCheckProvider));
14102 return new Database(repo, app);
14103}
14104/**
14105 * Remove the repo and make sure it is disconnected.
14106 *
14107 */
14108function repoManagerDeleteRepo(repo, appName) {
14109 var appRepos = repos[appName];
14110 // This should never happen...
14111 if (!appRepos || appRepos[repo.key] !== repo) {
14112 fatal("Database " + appName + "(" + repo.repoInfo_ + ") has already been deleted.");
14113 }
14114 repoInterrupt(repo);
14115 delete appRepos[repo.key];
14116}
14117/**
14118 * Ensures a repo doesn't already exist and then creates one using the
14119 * provided app.
14120 *
14121 * @param repoInfo - The metadata about the Repo
14122 * @returns The Repo object for the specified server / repoName.
14123 */
14124function repoManagerCreateRepo(repoInfo, app, authTokenProvider, appCheckProvider) {
14125 var appRepos = repos[app.name];
14126 if (!appRepos) {
14127 appRepos = {};
14128 repos[app.name] = appRepos;
14129 }
14130 var repo = appRepos[repoInfo.toURLString()];
14131 if (repo) {
14132 fatal('Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.');
14133 }
14134 repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider);
14135 appRepos[repoInfo.toURLString()] = repo;
14136 return repo;
14137}
14138/**
14139 * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos.
14140 */
14141function repoManagerForceRestClient(forceRestClient) {
14142 useRestClient = forceRestClient;
14143}
14144/**
14145 * Class representing a Firebase Realtime Database.
14146 */
14147var Database = /** @class */ (function () {
14148 /** @hideconstructor */
14149 function Database(_repoInternal,
14150 /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */
14151 app) {
14152 this._repoInternal = _repoInternal;
14153 this.app = app;
14154 /** Represents a `Database` instance. */
14155 this['type'] = 'database';
14156 /** Track if the instance has been used (root or repo accessed) */
14157 this._instanceStarted = false;
14158 }
14159 Object.defineProperty(Database.prototype, "_repo", {
14160 get: function () {
14161 if (!this._instanceStarted) {
14162 repoStart(this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride']);
14163 this._instanceStarted = true;
14164 }
14165 return this._repoInternal;
14166 },
14167 enumerable: false,
14168 configurable: true
14169 });
14170 Object.defineProperty(Database.prototype, "_root", {
14171 get: function () {
14172 if (!this._rootInternal) {
14173 this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath());
14174 }
14175 return this._rootInternal;
14176 },
14177 enumerable: false,
14178 configurable: true
14179 });
14180 Database.prototype._delete = function () {
14181 if (this._rootInternal !== null) {
14182 repoManagerDeleteRepo(this._repo, this.app.name);
14183 this._repoInternal = null;
14184 this._rootInternal = null;
14185 }
14186 return Promise.resolve();
14187 };
14188 Database.prototype._checkNotDeleted = function (apiName) {
14189 if (this._rootInternal === null) {
14190 fatal('Cannot call ' + apiName + ' on a deleted database.');
14191 }
14192 };
14193 return Database;
14194}());
14195function checkTransportInit() {
14196 if (TransportManager.IS_TRANSPORT_INITIALIZED) {
14197 warn('Transport has already been initialized. Please call this function before calling ref or setting up a listener');
14198 }
14199}
14200/**
14201 * Force the use of websockets instead of longPolling.
14202 */
14203function forceWebSockets() {
14204 checkTransportInit();
14205 BrowserPollConnection.forceDisallow();
14206}
14207/**
14208 * Force the use of longPolling instead of websockets. This will be ignored if websocket protocol is used in databaseURL.
14209 */
14210function forceLongPolling() {
14211 checkTransportInit();
14212 WebSocketConnection.forceDisallow();
14213 BrowserPollConnection.forceAllow();
14214}
14215/**
14216 * Returns the instance of the Realtime Database SDK that is associated
14217 * with the provided {@link @firebase/app#FirebaseApp}. Initializes a new instance with
14218 * with default settings if no instance exists or if the existing instance uses
14219 * a custom database URL.
14220 *
14221 * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned Realtime
14222 * Database instance is associated with.
14223 * @param url - The URL of the Realtime Database instance to connect to. If not
14224 * provided, the SDK connects to the default instance of the Firebase App.
14225 * @returns The `Database` instance of the provided app.
14226 */
14227function getDatabase(app$1, url) {
14228 if (app$1 === void 0) { app$1 = app.getApp(); }
14229 return app._getProvider(app$1, 'database').getImmediate({
14230 identifier: url
14231 });
14232}
14233/**
14234 * Modify the provided instance to communicate with the Realtime Database
14235 * emulator.
14236 *
14237 * <p>Note: This method must be called before performing any other operation.
14238 *
14239 * @param db - The instance to modify.
14240 * @param host - The emulator host (ex: localhost)
14241 * @param port - The emulator port (ex: 8080)
14242 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
14243 */
14244function connectDatabaseEmulator(db, host, port, options) {
14245 if (options === void 0) { options = {}; }
14246 db = util.getModularInstance(db);
14247 db._checkNotDeleted('useEmulator');
14248 if (db._instanceStarted) {
14249 fatal('Cannot call useEmulator() after instance has already been initialized.');
14250 }
14251 var repo = db._repoInternal;
14252 var tokenProvider = undefined;
14253 if (repo.repoInfo_.nodeAdmin) {
14254 if (options.mockUserToken) {
14255 fatal('mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".');
14256 }
14257 tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
14258 }
14259 else if (options.mockUserToken) {
14260 var token = typeof options.mockUserToken === 'string'
14261 ? options.mockUserToken
14262 : util.createMockUserToken(options.mockUserToken, db.app.options.projectId);
14263 tokenProvider = new EmulatorTokenProvider(token);
14264 }
14265 // Modify the repo to apply emulator settings
14266 repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider);
14267}
14268/**
14269 * Disconnects from the server (all Database operations will be completed
14270 * offline).
14271 *
14272 * The client automatically maintains a persistent connection to the Database
14273 * server, which will remain active indefinitely and reconnect when
14274 * disconnected. However, the `goOffline()` and `goOnline()` methods may be used
14275 * to control the client connection in cases where a persistent connection is
14276 * undesirable.
14277 *
14278 * While offline, the client will no longer receive data updates from the
14279 * Database. However, all Database operations performed locally will continue to
14280 * immediately fire events, allowing your application to continue behaving
14281 * normally. Additionally, each operation performed locally will automatically
14282 * be queued and retried upon reconnection to the Database server.
14283 *
14284 * To reconnect to the Database and begin receiving remote events, see
14285 * `goOnline()`.
14286 *
14287 * @param db - The instance to disconnect.
14288 */
14289function goOffline(db) {
14290 db = util.getModularInstance(db);
14291 db._checkNotDeleted('goOffline');
14292 repoInterrupt(db._repo);
14293}
14294/**
14295 * Reconnects to the server and synchronizes the offline Database state
14296 * with the server state.
14297 *
14298 * This method should be used after disabling the active connection with
14299 * `goOffline()`. Once reconnected, the client will transmit the proper data
14300 * and fire the appropriate events so that your client "catches up"
14301 * automatically.
14302 *
14303 * @param db - The instance to reconnect.
14304 */
14305function goOnline(db) {
14306 db = util.getModularInstance(db);
14307 db._checkNotDeleted('goOnline');
14308 repoResume(db._repo);
14309}
14310function enableLogging(logger, persistent) {
14311 enableLogging$1(logger, persistent);
14312}
14313
14314/**
14315 * @license
14316 * Copyright 2021 Google LLC
14317 *
14318 * Licensed under the Apache License, Version 2.0 (the "License");
14319 * you may not use this file except in compliance with the License.
14320 * You may obtain a copy of the License at
14321 *
14322 * http://www.apache.org/licenses/LICENSE-2.0
14323 *
14324 * Unless required by applicable law or agreed to in writing, software
14325 * distributed under the License is distributed on an "AS IS" BASIS,
14326 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14327 * See the License for the specific language governing permissions and
14328 * limitations under the License.
14329 */
14330function registerDatabase(variant) {
14331 setSDKVersion(app.SDK_VERSION);
14332 app._registerComponent(new component.Component('database', function (container, _a) {
14333 var url = _a.instanceIdentifier;
14334 var app = container.getProvider('app').getImmediate();
14335 var authProvider = container.getProvider('auth-internal');
14336 var appCheckProvider = container.getProvider('app-check-internal');
14337 return repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url);
14338 }, "PUBLIC" /* PUBLIC */).setMultipleInstances(true));
14339 app.registerVersion(name, version, variant);
14340 // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
14341 app.registerVersion(name, version, 'cjs5');
14342}
14343
14344/**
14345 * @license
14346 * Copyright 2020 Google LLC
14347 *
14348 * Licensed under the Apache License, Version 2.0 (the "License");
14349 * you may not use this file except in compliance with the License.
14350 * You may obtain a copy of the License at
14351 *
14352 * http://www.apache.org/licenses/LICENSE-2.0
14353 *
14354 * Unless required by applicable law or agreed to in writing, software
14355 * distributed under the License is distributed on an "AS IS" BASIS,
14356 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14357 * See the License for the specific language governing permissions and
14358 * limitations under the License.
14359 */
14360var SERVER_TIMESTAMP = {
14361 '.sv': 'timestamp'
14362};
14363/**
14364 * Returns a placeholder value for auto-populating the current timestamp (time
14365 * since the Unix epoch, in milliseconds) as determined by the Firebase
14366 * servers.
14367 */
14368function serverTimestamp() {
14369 return SERVER_TIMESTAMP;
14370}
14371/**
14372 * Returns a placeholder value that can be used to atomically increment the
14373 * current database value by the provided delta.
14374 *
14375 * @param delta - the amount to modify the current value atomically.
14376 * @returns A placeholder value for modifying data atomically server-side.
14377 */
14378function increment(delta) {
14379 return {
14380 '.sv': {
14381 'increment': delta
14382 }
14383 };
14384}
14385
14386/**
14387 * @license
14388 * Copyright 2020 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 */
14402/**
14403 * A type for the resolve value of {@link runTransaction}.
14404 */
14405var TransactionResult = /** @class */ (function () {
14406 /** @hideconstructor */
14407 function TransactionResult(
14408 /** Whether the transaction was successfully committed. */
14409 committed,
14410 /** The resulting data snapshot. */
14411 snapshot) {
14412 this.committed = committed;
14413 this.snapshot = snapshot;
14414 }
14415 /** Returns a JSON-serializable representation of this object. */
14416 TransactionResult.prototype.toJSON = function () {
14417 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14418 };
14419 return TransactionResult;
14420}());
14421/**
14422 * Atomically modifies the data at this location.
14423 *
14424 * Atomically modify the data at this location. Unlike a normal `set()`, which
14425 * just overwrites the data regardless of its previous value, `runTransaction()` is
14426 * used to modify the existing value to a new value, ensuring there are no
14427 * conflicts with other clients writing to the same location at the same time.
14428 *
14429 * To accomplish this, you pass `runTransaction()` an update function which is
14430 * used to transform the current value into a new value. If another client
14431 * writes to the location before your new value is successfully written, your
14432 * update function will be called again with the new current value, and the
14433 * write will be retried. This will happen repeatedly until your write succeeds
14434 * without conflict or you abort the transaction by not returning a value from
14435 * your update function.
14436 *
14437 * Note: Modifying data with `set()` will cancel any pending transactions at
14438 * that location, so extreme care should be taken if mixing `set()` and
14439 * `runTransaction()` to update the same data.
14440 *
14441 * Note: When using transactions with Security and Firebase Rules in place, be
14442 * aware that a client needs `.read` access in addition to `.write` access in
14443 * order to perform a transaction. This is because the client-side nature of
14444 * transactions requires the client to read the data in order to transactionally
14445 * update it.
14446 *
14447 * @param ref - The location to atomically modify.
14448 * @param transactionUpdate - A developer-supplied function which will be passed
14449 * the current data stored at this location (as a JavaScript object). The
14450 * function should return the new value it would like written (as a JavaScript
14451 * object). If `undefined` is returned (i.e. you return with no arguments) the
14452 * transaction will be aborted and the data at this location will not be
14453 * modified.
14454 * @param options - An options object to configure transactions.
14455 * @returns A `Promise` that can optionally be used instead of the `onComplete`
14456 * callback to handle success and failure.
14457 */
14458function runTransaction(ref,
14459// eslint-disable-next-line @typescript-eslint/no-explicit-any
14460transactionUpdate, options) {
14461 var _a;
14462 ref = util.getModularInstance(ref);
14463 validateWritablePath('Reference.transaction', ref._path);
14464 if (ref.key === '.length' || ref.key === '.keys') {
14465 throw ('Reference.transaction failed: ' + ref.key + ' is a read-only object.');
14466 }
14467 var applyLocally = (_a = options === null || options === void 0 ? void 0 : options.applyLocally) !== null && _a !== void 0 ? _a : true;
14468 var deferred = new util.Deferred();
14469 var promiseComplete = function (error, committed, node) {
14470 var dataSnapshot = null;
14471 if (error) {
14472 deferred.reject(error);
14473 }
14474 else {
14475 dataSnapshot = new DataSnapshot(node, new ReferenceImpl(ref._repo, ref._path), PRIORITY_INDEX);
14476 deferred.resolve(new TransactionResult(committed, dataSnapshot));
14477 }
14478 };
14479 // Add a watch to make sure we get server updates.
14480 var unwatcher = onValue(ref, function () { });
14481 repoStartTransaction(ref._repo, ref._path, transactionUpdate, promiseComplete, unwatcher, applyLocally);
14482 return deferred.promise;
14483}
14484
14485/**
14486 * @license
14487 * Copyright 2017 Google LLC
14488 *
14489 * Licensed under the Apache License, Version 2.0 (the "License");
14490 * you may not use this file except in compliance with the License.
14491 * You may obtain a copy of the License at
14492 *
14493 * http://www.apache.org/licenses/LICENSE-2.0
14494 *
14495 * Unless required by applicable law or agreed to in writing, software
14496 * distributed under the License is distributed on an "AS IS" BASIS,
14497 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14498 * See the License for the specific language governing permissions and
14499 * limitations under the License.
14500 */
14501// eslint-disable-next-line @typescript-eslint/no-explicit-any
14502PersistentConnection.prototype.simpleListen = function (pathString, onComplete) {
14503 this.sendRequest('q', { p: pathString }, onComplete);
14504};
14505// eslint-disable-next-line @typescript-eslint/no-explicit-any
14506PersistentConnection.prototype.echo = function (data, onEcho) {
14507 this.sendRequest('echo', { d: data }, onEcho);
14508};
14509/**
14510 * @internal
14511 */
14512var hijackHash = function (newHash) {
14513 var oldPut = PersistentConnection.prototype.put;
14514 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
14515 if (hash !== undefined) {
14516 hash = newHash();
14517 }
14518 oldPut.call(this, pathString, data, onComplete, hash);
14519 };
14520 return function () {
14521 PersistentConnection.prototype.put = oldPut;
14522 };
14523};
14524/**
14525 * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection.
14526 * @internal
14527 */
14528var forceRestClient = function (forceRestClient) {
14529 repoManagerForceRestClient(forceRestClient);
14530};
14531
14532/**
14533 * @license
14534 * Copyright 2021 Google LLC
14535 *
14536 * Licensed under the Apache License, Version 2.0 (the "License");
14537 * you may not use this file except in compliance with the License.
14538 * You may obtain a copy of the License at
14539 *
14540 * http://www.apache.org/licenses/LICENSE-2.0
14541 *
14542 * Unless required by applicable law or agreed to in writing, software
14543 * distributed under the License is distributed on an "AS IS" BASIS,
14544 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14545 * See the License for the specific language governing permissions and
14546 * limitations under the License.
14547 */
14548setWebSocketImpl(Websocket__default["default"].Client);
14549registerDatabase('node');
14550
14551exports.DataSnapshot = DataSnapshot;
14552exports.Database = Database;
14553exports.OnDisconnect = OnDisconnect;
14554exports.QueryConstraint = QueryConstraint;
14555exports.TransactionResult = TransactionResult;
14556exports._QueryImpl = QueryImpl;
14557exports._QueryParams = QueryParams;
14558exports._ReferenceImpl = ReferenceImpl;
14559exports._TEST_ACCESS_forceRestClient = forceRestClient;
14560exports._TEST_ACCESS_hijackHash = hijackHash;
14561exports._repoManagerDatabaseFromApp = repoManagerDatabaseFromApp;
14562exports._setSDKVersion = setSDKVersion;
14563exports._validatePathString = validatePathString;
14564exports._validateWritablePath = validateWritablePath;
14565exports.child = child;
14566exports.connectDatabaseEmulator = connectDatabaseEmulator;
14567exports.enableLogging = enableLogging;
14568exports.endAt = endAt;
14569exports.endBefore = endBefore;
14570exports.equalTo = equalTo;
14571exports.forceLongPolling = forceLongPolling;
14572exports.forceWebSockets = forceWebSockets;
14573exports.get = get;
14574exports.getDatabase = getDatabase;
14575exports.goOffline = goOffline;
14576exports.goOnline = goOnline;
14577exports.increment = increment;
14578exports.limitToFirst = limitToFirst;
14579exports.limitToLast = limitToLast;
14580exports.off = off;
14581exports.onChildAdded = onChildAdded;
14582exports.onChildChanged = onChildChanged;
14583exports.onChildMoved = onChildMoved;
14584exports.onChildRemoved = onChildRemoved;
14585exports.onDisconnect = onDisconnect;
14586exports.onValue = onValue;
14587exports.orderByChild = orderByChild;
14588exports.orderByKey = orderByKey;
14589exports.orderByPriority = orderByPriority;
14590exports.orderByValue = orderByValue;
14591exports.push = push;
14592exports.query = query;
14593exports.ref = ref;
14594exports.refFromURL = refFromURL;
14595exports.remove = remove;
14596exports.runTransaction = runTransaction;
14597exports.serverTimestamp = serverTimestamp;
14598exports.set = set;
14599exports.setPriority = setPriority;
14600exports.setWithPriority = setWithPriority;
14601exports.startAfter = startAfter;
14602exports.startAt = startAt;
14603exports.update = update;
14604//# sourceMappingURL=index.node.cjs.js.map