UNPKG

602 kBJavaScriptView Raw
1import { getApp, _getProvider, _registerComponent, registerVersion, SDK_VERSION as SDK_VERSION$1 } from '@firebase/app';
2import { Component } from '@firebase/component';
3import { stringify, jsonEval, contains, assert, isNodeSdk, base64, stringToByteArray, Sha1, deepCopy, base64Encode, isMobileCordova, stringLength, Deferred, safeGet, isAdmin, isValidFormat, isEmpty, isReactNative, assertionError, map, querystring, errorPrefix, getModularInstance, createMockUserToken } from '@firebase/util';
4import { __spreadArray, __read, __values, __extends, __awaiter, __generator, __assign } from 'tslib';
5import { Logger, LogLevel } from '@firebase/logger';
6
7var name = "@firebase/database";
8var version = "0.13.2";
9
10/**
11 * @license
12 * Copyright 2019 Google LLC
13 *
14 * Licensed under the Apache License, Version 2.0 (the "License");
15 * you may not use this file except in compliance with the License.
16 * You may obtain a copy of the License at
17 *
18 * http://www.apache.org/licenses/LICENSE-2.0
19 *
20 * Unless required by applicable law or agreed to in writing, software
21 * distributed under the License is distributed on an "AS IS" BASIS,
22 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 * See the License for the specific language governing permissions and
24 * limitations under the License.
25 */
26/** The semver (www.semver.org) version of the SDK. */
27var SDK_VERSION = '';
28/**
29 * SDK_VERSION should be set before any database instance is created
30 * @internal
31 */
32function setSDKVersion(version) {
33 SDK_VERSION = version;
34}
35
36/**
37 * @license
38 * Copyright 2017 Google LLC
39 *
40 * Licensed under the Apache License, Version 2.0 (the "License");
41 * you may not use this file except in compliance with the License.
42 * You may obtain a copy of the License at
43 *
44 * http://www.apache.org/licenses/LICENSE-2.0
45 *
46 * Unless required by applicable law or agreed to in writing, software
47 * distributed under the License is distributed on an "AS IS" BASIS,
48 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
49 * See the License for the specific language governing permissions and
50 * limitations under the License.
51 */
52/**
53 * Wraps a DOM Storage object and:
54 * - automatically encode objects as JSON strings before storing them to allow us to store arbitrary types.
55 * - prefixes names with "firebase:" to avoid collisions with app data.
56 *
57 * We automatically (see storage.js) create two such wrappers, one for sessionStorage,
58 * and one for localStorage.
59 *
60 */
61var DOMStorageWrapper = /** @class */ (function () {
62 /**
63 * @param domStorage_ - The underlying storage object (e.g. localStorage or sessionStorage)
64 */
65 function DOMStorageWrapper(domStorage_) {
66 this.domStorage_ = domStorage_;
67 // Use a prefix to avoid collisions with other stuff saved by the app.
68 this.prefix_ = 'firebase:';
69 }
70 /**
71 * @param key - The key to save the value under
72 * @param value - The value being stored, or null to remove the key.
73 */
74 DOMStorageWrapper.prototype.set = function (key, value) {
75 if (value == null) {
76 this.domStorage_.removeItem(this.prefixedName_(key));
77 }
78 else {
79 this.domStorage_.setItem(this.prefixedName_(key), stringify(value));
80 }
81 };
82 /**
83 * @returns The value that was stored under this key, or null
84 */
85 DOMStorageWrapper.prototype.get = function (key) {
86 var storedVal = this.domStorage_.getItem(this.prefixedName_(key));
87 if (storedVal == null) {
88 return null;
89 }
90 else {
91 return jsonEval(storedVal);
92 }
93 };
94 DOMStorageWrapper.prototype.remove = function (key) {
95 this.domStorage_.removeItem(this.prefixedName_(key));
96 };
97 DOMStorageWrapper.prototype.prefixedName_ = function (name) {
98 return this.prefix_ + name;
99 };
100 DOMStorageWrapper.prototype.toString = function () {
101 return this.domStorage_.toString();
102 };
103 return DOMStorageWrapper;
104}());
105
106/**
107 * @license
108 * Copyright 2017 Google LLC
109 *
110 * Licensed under the Apache License, Version 2.0 (the "License");
111 * you may not use this file except in compliance with the License.
112 * You may obtain a copy of the License at
113 *
114 * http://www.apache.org/licenses/LICENSE-2.0
115 *
116 * Unless required by applicable law or agreed to in writing, software
117 * distributed under the License is distributed on an "AS IS" BASIS,
118 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
119 * See the License for the specific language governing permissions and
120 * limitations under the License.
121 */
122/**
123 * An in-memory storage implementation that matches the API of DOMStorageWrapper
124 * (TODO: create interface for both to implement).
125 */
126var MemoryStorage = /** @class */ (function () {
127 function MemoryStorage() {
128 this.cache_ = {};
129 this.isInMemoryStorage = true;
130 }
131 MemoryStorage.prototype.set = function (key, value) {
132 if (value == null) {
133 delete this.cache_[key];
134 }
135 else {
136 this.cache_[key] = value;
137 }
138 };
139 MemoryStorage.prototype.get = function (key) {
140 if (contains(this.cache_, key)) {
141 return this.cache_[key];
142 }
143 return null;
144 };
145 MemoryStorage.prototype.remove = function (key) {
146 delete this.cache_[key];
147 };
148 return MemoryStorage;
149}());
150
151/**
152 * @license
153 * Copyright 2017 Google LLC
154 *
155 * Licensed under the Apache License, Version 2.0 (the "License");
156 * you may not use this file except in compliance with the License.
157 * You may obtain a copy of the License at
158 *
159 * http://www.apache.org/licenses/LICENSE-2.0
160 *
161 * Unless required by applicable law or agreed to in writing, software
162 * distributed under the License is distributed on an "AS IS" BASIS,
163 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
164 * See the License for the specific language governing permissions and
165 * limitations under the License.
166 */
167/**
168 * Helper to create a DOMStorageWrapper or else fall back to MemoryStorage.
169 * TODO: Once MemoryStorage and DOMStorageWrapper have a shared interface this method annotation should change
170 * to reflect this type
171 *
172 * @param domStorageName - Name of the underlying storage object
173 * (e.g. 'localStorage' or 'sessionStorage').
174 * @returns Turning off type information until a common interface is defined.
175 */
176var createStoragefor = function (domStorageName) {
177 try {
178 // NOTE: just accessing "localStorage" or "window['localStorage']" may throw a security exception,
179 // so it must be inside the try/catch.
180 if (typeof window !== 'undefined' &&
181 typeof window[domStorageName] !== 'undefined') {
182 // Need to test cache. Just because it's here doesn't mean it works
183 var domStorage = window[domStorageName];
184 domStorage.setItem('firebase:sentinel', 'cache');
185 domStorage.removeItem('firebase:sentinel');
186 return new DOMStorageWrapper(domStorage);
187 }
188 }
189 catch (e) { }
190 // Failed to create wrapper. Just return in-memory storage.
191 // TODO: log?
192 return new MemoryStorage();
193};
194/** A storage object that lasts across sessions */
195var PersistentStorage = createStoragefor('localStorage');
196/** A storage object that only lasts one session */
197var SessionStorage = createStoragefor('sessionStorage');
198
199/**
200 * @license
201 * Copyright 2017 Google LLC
202 *
203 * Licensed under the Apache License, Version 2.0 (the "License");
204 * you may not use this file except in compliance with the License.
205 * You may obtain a copy of the License at
206 *
207 * http://www.apache.org/licenses/LICENSE-2.0
208 *
209 * Unless required by applicable law or agreed to in writing, software
210 * distributed under the License is distributed on an "AS IS" BASIS,
211 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
212 * See the License for the specific language governing permissions and
213 * limitations under the License.
214 */
215var logClient = new Logger('@firebase/database');
216/**
217 * Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
218 */
219var LUIDGenerator = (function () {
220 var id = 1;
221 return function () {
222 return id++;
223 };
224})();
225/**
226 * Sha1 hash of the input string
227 * @param str - The string to hash
228 * @returns {!string} The resulting hash
229 */
230var sha1 = function (str) {
231 var utf8Bytes = stringToByteArray(str);
232 var sha1 = new Sha1();
233 sha1.update(utf8Bytes);
234 var sha1Bytes = sha1.digest();
235 return base64.encodeByteArray(sha1Bytes);
236};
237var buildLogMessage_ = function () {
238 var varArgs = [];
239 for (var _i = 0; _i < arguments.length; _i++) {
240 varArgs[_i] = arguments[_i];
241 }
242 var message = '';
243 for (var i = 0; i < varArgs.length; i++) {
244 var arg = varArgs[i];
245 if (Array.isArray(arg) ||
246 (arg &&
247 typeof arg === 'object' &&
248 // eslint-disable-next-line @typescript-eslint/no-explicit-any
249 typeof arg.length === 'number')) {
250 message += buildLogMessage_.apply(null, arg);
251 }
252 else if (typeof arg === 'object') {
253 message += stringify(arg);
254 }
255 else {
256 message += arg;
257 }
258 message += ' ';
259 }
260 return message;
261};
262/**
263 * Use this for all debug messages in Firebase.
264 */
265var logger = null;
266/**
267 * Flag to check for log availability on first log message
268 */
269var firstLog_ = true;
270/**
271 * The implementation of Firebase.enableLogging (defined here to break dependencies)
272 * @param logger_ - A flag to turn on logging, or a custom logger
273 * @param persistent - Whether or not to persist logging settings across refreshes
274 */
275var enableLogging$1 = function (logger_, persistent) {
276 assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
277 if (logger_ === true) {
278 logClient.logLevel = LogLevel.VERBOSE;
279 logger = logClient.log.bind(logClient);
280 if (persistent) {
281 SessionStorage.set('logging_enabled', true);
282 }
283 }
284 else if (typeof logger_ === 'function') {
285 logger = logger_;
286 }
287 else {
288 logger = null;
289 SessionStorage.remove('logging_enabled');
290 }
291};
292var log = function () {
293 var varArgs = [];
294 for (var _i = 0; _i < arguments.length; _i++) {
295 varArgs[_i] = arguments[_i];
296 }
297 if (firstLog_ === true) {
298 firstLog_ = false;
299 if (logger === null && SessionStorage.get('logging_enabled') === true) {
300 enableLogging$1(true);
301 }
302 }
303 if (logger) {
304 var message = buildLogMessage_.apply(null, varArgs);
305 logger(message);
306 }
307};
308var logWrapper = function (prefix) {
309 return function () {
310 var varArgs = [];
311 for (var _i = 0; _i < arguments.length; _i++) {
312 varArgs[_i] = arguments[_i];
313 }
314 log.apply(void 0, __spreadArray([prefix], __read(varArgs)));
315 };
316};
317var error = function () {
318 var varArgs = [];
319 for (var _i = 0; _i < arguments.length; _i++) {
320 varArgs[_i] = arguments[_i];
321 }
322 var message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_.apply(void 0, __spreadArray([], __read(varArgs)));
323 logClient.error(message);
324};
325var fatal = function () {
326 var varArgs = [];
327 for (var _i = 0; _i < arguments.length; _i++) {
328 varArgs[_i] = arguments[_i];
329 }
330 var message = "FIREBASE FATAL ERROR: " + buildLogMessage_.apply(void 0, __spreadArray([], __read(varArgs)));
331 logClient.error(message);
332 throw new Error(message);
333};
334var warn = function () {
335 var varArgs = [];
336 for (var _i = 0; _i < arguments.length; _i++) {
337 varArgs[_i] = arguments[_i];
338 }
339 var message = 'FIREBASE WARNING: ' + buildLogMessage_.apply(void 0, __spreadArray([], __read(varArgs)));
340 logClient.warn(message);
341};
342/**
343 * Logs a warning if the containing page uses https. Called when a call to new Firebase
344 * does not use https.
345 */
346var warnIfPageIsSecure = function () {
347 // Be very careful accessing browser globals. Who knows what may or may not exist.
348 if (typeof window !== 'undefined' &&
349 window.location &&
350 window.location.protocol &&
351 window.location.protocol.indexOf('https:') !== -1) {
352 warn('Insecure Firebase access from a secure page. ' +
353 'Please use https in calls to new Firebase().');
354 }
355};
356/**
357 * Returns true if data is NaN, or +/- Infinity.
358 */
359var isInvalidJSONNumber = function (data) {
360 return (typeof data === 'number' &&
361 (data !== data || // NaN
362 data === Number.POSITIVE_INFINITY ||
363 data === Number.NEGATIVE_INFINITY));
364};
365var executeWhenDOMReady = function (fn) {
366 if (isNodeSdk() || document.readyState === 'complete') {
367 fn();
368 }
369 else {
370 // Modeled after jQuery. Try DOMContentLoaded and onreadystatechange (which
371 // fire before onload), but fall back to onload.
372 var called_1 = false;
373 var wrappedFn_1 = function () {
374 if (!document.body) {
375 setTimeout(wrappedFn_1, Math.floor(10));
376 return;
377 }
378 if (!called_1) {
379 called_1 = true;
380 fn();
381 }
382 };
383 if (document.addEventListener) {
384 document.addEventListener('DOMContentLoaded', wrappedFn_1, false);
385 // fallback to onload.
386 window.addEventListener('load', wrappedFn_1, false);
387 // eslint-disable-next-line @typescript-eslint/no-explicit-any
388 }
389 else if (document.attachEvent) {
390 // IE.
391 // eslint-disable-next-line @typescript-eslint/no-explicit-any
392 document.attachEvent('onreadystatechange', function () {
393 if (document.readyState === 'complete') {
394 wrappedFn_1();
395 }
396 });
397 // fallback to onload.
398 // eslint-disable-next-line @typescript-eslint/no-explicit-any
399 window.attachEvent('onload', wrappedFn_1);
400 // jQuery has an extra hack for IE that we could employ (based on
401 // http://javascript.nwbox.com/IEContentLoaded/) But it looks really old.
402 // I'm hoping we don't need it.
403 }
404 }
405};
406/**
407 * Minimum key name. Invalid for actual data, used as a marker to sort before any valid names
408 */
409var MIN_NAME = '[MIN_NAME]';
410/**
411 * Maximum key name. Invalid for actual data, used as a marker to sort above any valid names
412 */
413var MAX_NAME = '[MAX_NAME]';
414/**
415 * Compares valid Firebase key names, plus min and max name
416 */
417var nameCompare = function (a, b) {
418 if (a === b) {
419 return 0;
420 }
421 else if (a === MIN_NAME || b === MAX_NAME) {
422 return -1;
423 }
424 else if (b === MIN_NAME || a === MAX_NAME) {
425 return 1;
426 }
427 else {
428 var aAsInt = tryParseInt(a), bAsInt = tryParseInt(b);
429 if (aAsInt !== null) {
430 if (bAsInt !== null) {
431 return aAsInt - bAsInt === 0 ? a.length - b.length : aAsInt - bAsInt;
432 }
433 else {
434 return -1;
435 }
436 }
437 else if (bAsInt !== null) {
438 return 1;
439 }
440 else {
441 return a < b ? -1 : 1;
442 }
443 }
444};
445/**
446 * @returns {!number} comparison result.
447 */
448var stringCompare = function (a, b) {
449 if (a === b) {
450 return 0;
451 }
452 else if (a < b) {
453 return -1;
454 }
455 else {
456 return 1;
457 }
458};
459var requireKey = function (key, obj) {
460 if (obj && key in obj) {
461 return obj[key];
462 }
463 else {
464 throw new Error('Missing required key (' + key + ') in object: ' + stringify(obj));
465 }
466};
467var ObjectToUniqueKey = function (obj) {
468 if (typeof obj !== 'object' || obj === null) {
469 return stringify(obj);
470 }
471 var keys = [];
472 // eslint-disable-next-line guard-for-in
473 for (var k in obj) {
474 keys.push(k);
475 }
476 // Export as json, but with the keys sorted.
477 keys.sort();
478 var key = '{';
479 for (var i = 0; i < keys.length; i++) {
480 if (i !== 0) {
481 key += ',';
482 }
483 key += stringify(keys[i]);
484 key += ':';
485 key += ObjectToUniqueKey(obj[keys[i]]);
486 }
487 key += '}';
488 return key;
489};
490/**
491 * Splits a string into a number of smaller segments of maximum size
492 * @param str - The string
493 * @param segsize - The maximum number of chars in the string.
494 * @returns The string, split into appropriately-sized chunks
495 */
496var splitStringBySize = function (str, segsize) {
497 var len = str.length;
498 if (len <= segsize) {
499 return [str];
500 }
501 var dataSegs = [];
502 for (var c = 0; c < len; c += segsize) {
503 if (c + segsize > len) {
504 dataSegs.push(str.substring(c, len));
505 }
506 else {
507 dataSegs.push(str.substring(c, c + segsize));
508 }
509 }
510 return dataSegs;
511};
512/**
513 * Apply a function to each (key, value) pair in an object or
514 * apply a function to each (index, value) pair in an array
515 * @param obj - The object or array to iterate over
516 * @param fn - The function to apply
517 */
518function each(obj, fn) {
519 for (var key in obj) {
520 if (obj.hasOwnProperty(key)) {
521 fn(key, obj[key]);
522 }
523 }
524}
525/**
526 * Borrowed from http://hg.secondlife.com/llsd/src/tip/js/typedarray.js (MIT License)
527 * I made one modification at the end and removed the NaN / Infinity
528 * handling (since it seemed broken [caused an overflow] and we don't need it). See MJL comments.
529 * @param v - A double
530 *
531 */
532var doubleToIEEE754String = function (v) {
533 assert(!isInvalidJSONNumber(v), 'Invalid JSON number'); // MJL
534 var ebits = 11, fbits = 52;
535 var bias = (1 << (ebits - 1)) - 1;
536 var s, e, f, ln, i;
537 // Compute sign, exponent, fraction
538 // Skip NaN / Infinity handling --MJL.
539 if (v === 0) {
540 e = 0;
541 f = 0;
542 s = 1 / v === -Infinity ? 1 : 0;
543 }
544 else {
545 s = v < 0;
546 v = Math.abs(v);
547 if (v >= Math.pow(2, 1 - bias)) {
548 // Normalized
549 ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
550 e = ln + bias;
551 f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
552 }
553 else {
554 // Denormalized
555 e = 0;
556 f = Math.round(v / Math.pow(2, 1 - bias - fbits));
557 }
558 }
559 // Pack sign, exponent, fraction
560 var bits = [];
561 for (i = fbits; i; i -= 1) {
562 bits.push(f % 2 ? 1 : 0);
563 f = Math.floor(f / 2);
564 }
565 for (i = ebits; i; i -= 1) {
566 bits.push(e % 2 ? 1 : 0);
567 e = Math.floor(e / 2);
568 }
569 bits.push(s ? 1 : 0);
570 bits.reverse();
571 var str = bits.join('');
572 // Return the data as a hex string. --MJL
573 var hexByteString = '';
574 for (i = 0; i < 64; i += 8) {
575 var hexByte = parseInt(str.substr(i, 8), 2).toString(16);
576 if (hexByte.length === 1) {
577 hexByte = '0' + hexByte;
578 }
579 hexByteString = hexByteString + hexByte;
580 }
581 return hexByteString.toLowerCase();
582};
583/**
584 * Used to detect if we're in a Chrome content script (which executes in an
585 * isolated environment where long-polling doesn't work).
586 */
587var isChromeExtensionContentScript = function () {
588 return !!(typeof window === 'object' &&
589 window['chrome'] &&
590 window['chrome']['extension'] &&
591 !/^chrome/.test(window.location.href));
592};
593/**
594 * Used to detect if we're in a Windows 8 Store app.
595 */
596var isWindowsStoreApp = function () {
597 // Check for the presence of a couple WinRT globals
598 return typeof Windows === 'object' && typeof Windows.UI === 'object';
599};
600/**
601 * Converts a server error code to a Javascript Error
602 */
603function errorForServerCode(code, query) {
604 var reason = 'Unknown Error';
605 if (code === 'too_big') {
606 reason =
607 'The data requested exceeds the maximum size ' +
608 'that can be accessed with a single request.';
609 }
610 else if (code === 'permission_denied') {
611 reason = "Client doesn't have permission to access the desired data.";
612 }
613 else if (code === 'unavailable') {
614 reason = 'The service is unavailable';
615 }
616 var error = new Error(code + ' at ' + query._path.toString() + ': ' + reason);
617 // eslint-disable-next-line @typescript-eslint/no-explicit-any
618 error.code = code.toUpperCase();
619 return error;
620}
621/**
622 * Used to test for integer-looking strings
623 */
624var INTEGER_REGEXP_ = new RegExp('^-?(0*)\\d{1,10}$');
625/**
626 * For use in keys, the minimum possible 32-bit integer.
627 */
628var INTEGER_32_MIN = -2147483648;
629/**
630 * For use in kyes, the maximum possible 32-bit integer.
631 */
632var INTEGER_32_MAX = 2147483647;
633/**
634 * If the string contains a 32-bit integer, return it. Else return null.
635 */
636var tryParseInt = function (str) {
637 if (INTEGER_REGEXP_.test(str)) {
638 var intVal = Number(str);
639 if (intVal >= INTEGER_32_MIN && intVal <= INTEGER_32_MAX) {
640 return intVal;
641 }
642 }
643 return null;
644};
645/**
646 * Helper to run some code but catch any exceptions and re-throw them later.
647 * Useful for preventing user callbacks from breaking internal code.
648 *
649 * Re-throwing the exception from a setTimeout is a little evil, but it's very
650 * convenient (we don't have to try to figure out when is a safe point to
651 * re-throw it), and the behavior seems reasonable:
652 *
653 * * If you aren't pausing on exceptions, you get an error in the console with
654 * the correct stack trace.
655 * * If you're pausing on all exceptions, the debugger will pause on your
656 * exception and then again when we rethrow it.
657 * * If you're only pausing on uncaught exceptions, the debugger will only pause
658 * on us re-throwing it.
659 *
660 * @param fn - The code to guard.
661 */
662var exceptionGuard = function (fn) {
663 try {
664 fn();
665 }
666 catch (e) {
667 // Re-throw exception when it's safe.
668 setTimeout(function () {
669 // It used to be that "throw e" would result in a good console error with
670 // relevant context, but as of Chrome 39, you just get the firebase.js
671 // file/line number where we re-throw it, which is useless. So we log
672 // e.stack explicitly.
673 var stack = e.stack || '';
674 warn('Exception was thrown by user callback.', stack);
675 throw e;
676 }, Math.floor(0));
677 }
678};
679/**
680 * @returns {boolean} true if we think we're currently being crawled.
681 */
682var beingCrawled = function () {
683 var userAgent = (typeof window === 'object' &&
684 window['navigator'] &&
685 window['navigator']['userAgent']) ||
686 '';
687 // For now we whitelist the most popular crawlers. We should refine this to be the set of crawlers we
688 // believe to support JavaScript/AJAX rendering.
689 // NOTE: Google Webmaster Tools doesn't really belong, but their "This is how a visitor to your website
690 // would have seen the page" is flaky if we don't treat it as a crawler.
691 return (userAgent.search(/googlebot|google webmaster tools|bingbot|yahoo! slurp|baiduspider|yandexbot|duckduckbot/i) >= 0);
692};
693/**
694 * Same as setTimeout() except on Node.JS it will /not/ prevent the process from exiting.
695 *
696 * It is removed with clearTimeout() as normal.
697 *
698 * @param fn - Function to run.
699 * @param time - Milliseconds to wait before running.
700 * @returns The setTimeout() return value.
701 */
702var setTimeoutNonBlocking = function (fn, time) {
703 var timeout = setTimeout(fn, time);
704 // eslint-disable-next-line @typescript-eslint/no-explicit-any
705 if (typeof timeout === 'object' && timeout['unref']) {
706 // eslint-disable-next-line @typescript-eslint/no-explicit-any
707 timeout['unref']();
708 }
709 return timeout;
710};
711
712/**
713 * @license
714 * Copyright 2021 Google LLC
715 *
716 * Licensed under the Apache License, Version 2.0 (the "License");
717 * you may not use this file except in compliance with the License.
718 * You may obtain a copy of the License at
719 *
720 * http://www.apache.org/licenses/LICENSE-2.0
721 *
722 * Unless required by applicable law or agreed to in writing, software
723 * distributed under the License is distributed on an "AS IS" BASIS,
724 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
725 * See the License for the specific language governing permissions and
726 * limitations under the License.
727 */
728/**
729 * Abstraction around AppCheck's token fetching capabilities.
730 */
731var AppCheckTokenProvider = /** @class */ (function () {
732 function AppCheckTokenProvider(appName_, appCheckProvider) {
733 var _this = this;
734 this.appName_ = appName_;
735 this.appCheckProvider = appCheckProvider;
736 this.appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
737 if (!this.appCheck) {
738 appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.get().then(function (appCheck) { return (_this.appCheck = appCheck); });
739 }
740 }
741 AppCheckTokenProvider.prototype.getToken = function (forceRefresh) {
742 var _this = this;
743 if (!this.appCheck) {
744 return new Promise(function (resolve, reject) {
745 // Support delayed initialization of FirebaseAppCheck. This allows our
746 // customers to initialize the RTDB SDK before initializing Firebase
747 // AppCheck and ensures that all requests are authenticated if a token
748 // becomes available before the timoeout below expires.
749 setTimeout(function () {
750 if (_this.appCheck) {
751 _this.getToken(forceRefresh).then(resolve, reject);
752 }
753 else {
754 resolve(null);
755 }
756 }, 0);
757 });
758 }
759 return this.appCheck.getToken(forceRefresh);
760 };
761 AppCheckTokenProvider.prototype.addTokenChangeListener = function (listener) {
762 var _a;
763 (_a = this.appCheckProvider) === null || _a === void 0 ? void 0 : _a.get().then(function (appCheck) { return appCheck.addTokenListener(listener); });
764 };
765 AppCheckTokenProvider.prototype.notifyForInvalidToken = function () {
766 warn("Provided AppCheck credentials for the app named \"" + this.appName_ + "\" " +
767 'are invalid. This usually indicates your app was not initialized correctly.');
768 };
769 return AppCheckTokenProvider;
770}());
771
772/**
773 * @license
774 * Copyright 2017 Google LLC
775 *
776 * Licensed under the Apache License, Version 2.0 (the "License");
777 * you may not use this file except in compliance with the License.
778 * You may obtain a copy of the License at
779 *
780 * http://www.apache.org/licenses/LICENSE-2.0
781 *
782 * Unless required by applicable law or agreed to in writing, software
783 * distributed under the License is distributed on an "AS IS" BASIS,
784 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
785 * See the License for the specific language governing permissions and
786 * limitations under the License.
787 */
788/**
789 * Abstraction around FirebaseApp's token fetching capabilities.
790 */
791var FirebaseAuthTokenProvider = /** @class */ (function () {
792 function FirebaseAuthTokenProvider(appName_, firebaseOptions_, authProvider_) {
793 var _this = this;
794 this.appName_ = appName_;
795 this.firebaseOptions_ = firebaseOptions_;
796 this.authProvider_ = authProvider_;
797 this.auth_ = null;
798 this.auth_ = authProvider_.getImmediate({ optional: true });
799 if (!this.auth_) {
800 authProvider_.onInit(function (auth) { return (_this.auth_ = auth); });
801 }
802 }
803 FirebaseAuthTokenProvider.prototype.getToken = function (forceRefresh) {
804 var _this = this;
805 if (!this.auth_) {
806 return new Promise(function (resolve, reject) {
807 // Support delayed initialization of FirebaseAuth. This allows our
808 // customers to initialize the RTDB SDK before initializing Firebase
809 // Auth and ensures that all requests are authenticated if a token
810 // becomes available before the timoeout below expires.
811 setTimeout(function () {
812 if (_this.auth_) {
813 _this.getToken(forceRefresh).then(resolve, reject);
814 }
815 else {
816 resolve(null);
817 }
818 }, 0);
819 });
820 }
821 return this.auth_.getToken(forceRefresh).catch(function (error) {
822 // TODO: Need to figure out all the cases this is raised and whether
823 // this makes sense.
824 if (error && error.code === 'auth/token-not-initialized') {
825 log('Got auth/token-not-initialized error. Treating as null token.');
826 return null;
827 }
828 else {
829 return Promise.reject(error);
830 }
831 });
832 };
833 FirebaseAuthTokenProvider.prototype.addTokenChangeListener = function (listener) {
834 // TODO: We might want to wrap the listener and call it with no args to
835 // avoid a leaky abstraction, but that makes removing the listener harder.
836 if (this.auth_) {
837 this.auth_.addAuthTokenListener(listener);
838 }
839 else {
840 this.authProvider_
841 .get()
842 .then(function (auth) { return auth.addAuthTokenListener(listener); });
843 }
844 };
845 FirebaseAuthTokenProvider.prototype.removeTokenChangeListener = function (listener) {
846 this.authProvider_
847 .get()
848 .then(function (auth) { return auth.removeAuthTokenListener(listener); });
849 };
850 FirebaseAuthTokenProvider.prototype.notifyForInvalidToken = function () {
851 var errorMessage = 'Provided authentication credentials for the app named "' +
852 this.appName_ +
853 '" are invalid. This usually indicates your app was not ' +
854 'initialized correctly. ';
855 if ('credential' in this.firebaseOptions_) {
856 errorMessage +=
857 'Make sure the "credential" property provided to initializeApp() ' +
858 'is authorized to access the specified "databaseURL" and is from the correct ' +
859 'project.';
860 }
861 else if ('serviceAccount' in this.firebaseOptions_) {
862 errorMessage +=
863 'Make sure the "serviceAccount" property provided to initializeApp() ' +
864 'is authorized to access the specified "databaseURL" and is from the correct ' +
865 'project.';
866 }
867 else {
868 errorMessage +=
869 'Make sure the "apiKey" and "databaseURL" properties provided to ' +
870 'initializeApp() match the values provided for your app at ' +
871 'https://console.firebase.google.com/.';
872 }
873 warn(errorMessage);
874 };
875 return FirebaseAuthTokenProvider;
876}());
877/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */
878var EmulatorTokenProvider = /** @class */ (function () {
879 function EmulatorTokenProvider(accessToken) {
880 this.accessToken = accessToken;
881 }
882 EmulatorTokenProvider.prototype.getToken = function (forceRefresh) {
883 return Promise.resolve({
884 accessToken: this.accessToken
885 });
886 };
887 EmulatorTokenProvider.prototype.addTokenChangeListener = function (listener) {
888 // Invoke the listener immediately to match the behavior in Firebase Auth
889 // (see packages/auth/src/auth.js#L1807)
890 listener(this.accessToken);
891 };
892 EmulatorTokenProvider.prototype.removeTokenChangeListener = function (listener) { };
893 EmulatorTokenProvider.prototype.notifyForInvalidToken = function () { };
894 /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */
895 EmulatorTokenProvider.OWNER = 'owner';
896 return EmulatorTokenProvider;
897}());
898
899/**
900 * @license
901 * Copyright 2017 Google LLC
902 *
903 * Licensed under the Apache License, Version 2.0 (the "License");
904 * you may not use this file except in compliance with the License.
905 * You may obtain a copy of the License at
906 *
907 * http://www.apache.org/licenses/LICENSE-2.0
908 *
909 * Unless required by applicable law or agreed to in writing, software
910 * distributed under the License is distributed on an "AS IS" BASIS,
911 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
912 * See the License for the specific language governing permissions and
913 * limitations under the License.
914 */
915var PROTOCOL_VERSION = '5';
916var VERSION_PARAM = 'v';
917var TRANSPORT_SESSION_PARAM = 's';
918var REFERER_PARAM = 'r';
919var FORGE_REF = 'f';
920// Matches console.firebase.google.com, firebase-console-*.corp.google.com and
921// firebase.corp.google.com
922var FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/;
923var LAST_SESSION_PARAM = 'ls';
924var APPLICATION_ID_PARAM = 'p';
925var APP_CHECK_TOKEN_PARAM = 'ac';
926var WEBSOCKET = 'websocket';
927var LONG_POLLING = 'long_polling';
928
929/**
930 * @license
931 * Copyright 2017 Google LLC
932 *
933 * Licensed under the Apache License, Version 2.0 (the "License");
934 * you may not use this file except in compliance with the License.
935 * You may obtain a copy of the License at
936 *
937 * http://www.apache.org/licenses/LICENSE-2.0
938 *
939 * Unless required by applicable law or agreed to in writing, software
940 * distributed under the License is distributed on an "AS IS" BASIS,
941 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
942 * See the License for the specific language governing permissions and
943 * limitations under the License.
944 */
945/**
946 * A class that holds metadata about a Repo object
947 */
948var RepoInfo = /** @class */ (function () {
949 /**
950 * @param host - Hostname portion of the url for the repo
951 * @param secure - Whether or not this repo is accessed over ssl
952 * @param namespace - The namespace represented by the repo
953 * @param webSocketOnly - Whether to prefer websockets over all other transports (used by Nest).
954 * @param nodeAdmin - Whether this instance uses Admin SDK credentials
955 * @param persistenceKey - Override the default session persistence storage key
956 */
957 function RepoInfo(host, secure, namespace, webSocketOnly, nodeAdmin, persistenceKey, includeNamespaceInQueryParams) {
958 if (nodeAdmin === void 0) { nodeAdmin = false; }
959 if (persistenceKey === void 0) { persistenceKey = ''; }
960 if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
961 this.secure = secure;
962 this.namespace = namespace;
963 this.webSocketOnly = webSocketOnly;
964 this.nodeAdmin = nodeAdmin;
965 this.persistenceKey = persistenceKey;
966 this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
967 this._host = host.toLowerCase();
968 this._domain = this._host.substr(this._host.indexOf('.') + 1);
969 this.internalHost =
970 PersistentStorage.get('host:' + host) || this._host;
971 }
972 RepoInfo.prototype.isCacheableHost = function () {
973 return this.internalHost.substr(0, 2) === 's-';
974 };
975 RepoInfo.prototype.isCustomHost = function () {
976 return (this._domain !== 'firebaseio.com' &&
977 this._domain !== 'firebaseio-demo.com');
978 };
979 Object.defineProperty(RepoInfo.prototype, "host", {
980 get: function () {
981 return this._host;
982 },
983 set: function (newHost) {
984 if (newHost !== this.internalHost) {
985 this.internalHost = newHost;
986 if (this.isCacheableHost()) {
987 PersistentStorage.set('host:' + this._host, this.internalHost);
988 }
989 }
990 },
991 enumerable: false,
992 configurable: true
993 });
994 RepoInfo.prototype.toString = function () {
995 var str = this.toURLString();
996 if (this.persistenceKey) {
997 str += '<' + this.persistenceKey + '>';
998 }
999 return str;
1000 };
1001 RepoInfo.prototype.toURLString = function () {
1002 var protocol = this.secure ? 'https://' : 'http://';
1003 var query = this.includeNamespaceInQueryParams
1004 ? "?ns=" + this.namespace
1005 : '';
1006 return "" + protocol + this.host + "/" + query;
1007 };
1008 return RepoInfo;
1009}());
1010function repoInfoNeedsQueryParam(repoInfo) {
1011 return (repoInfo.host !== repoInfo.internalHost ||
1012 repoInfo.isCustomHost() ||
1013 repoInfo.includeNamespaceInQueryParams);
1014}
1015/**
1016 * Returns the websocket URL for this repo
1017 * @param repoInfo - RepoInfo object
1018 * @param type - of connection
1019 * @param params - list
1020 * @returns The URL for this repo
1021 */
1022function repoInfoConnectionURL(repoInfo, type, params) {
1023 assert(typeof type === 'string', 'typeof type must == string');
1024 assert(typeof params === 'object', 'typeof params must == object');
1025 var connURL;
1026 if (type === WEBSOCKET) {
1027 connURL =
1028 (repoInfo.secure ? 'wss://' : 'ws://') + repoInfo.internalHost + '/.ws?';
1029 }
1030 else if (type === LONG_POLLING) {
1031 connURL =
1032 (repoInfo.secure ? 'https://' : 'http://') +
1033 repoInfo.internalHost +
1034 '/.lp?';
1035 }
1036 else {
1037 throw new Error('Unknown connection type: ' + type);
1038 }
1039 if (repoInfoNeedsQueryParam(repoInfo)) {
1040 params['ns'] = repoInfo.namespace;
1041 }
1042 var pairs = [];
1043 each(params, function (key, value) {
1044 pairs.push(key + '=' + value);
1045 });
1046 return connURL + pairs.join('&');
1047}
1048
1049/**
1050 * @license
1051 * Copyright 2017 Google LLC
1052 *
1053 * Licensed under the Apache License, Version 2.0 (the "License");
1054 * you may not use this file except in compliance with the License.
1055 * You may obtain a copy of the License at
1056 *
1057 * http://www.apache.org/licenses/LICENSE-2.0
1058 *
1059 * Unless required by applicable law or agreed to in writing, software
1060 * distributed under the License is distributed on an "AS IS" BASIS,
1061 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1062 * See the License for the specific language governing permissions and
1063 * limitations under the License.
1064 */
1065/**
1066 * Tracks a collection of stats.
1067 */
1068var StatsCollection = /** @class */ (function () {
1069 function StatsCollection() {
1070 this.counters_ = {};
1071 }
1072 StatsCollection.prototype.incrementCounter = function (name, amount) {
1073 if (amount === void 0) { amount = 1; }
1074 if (!contains(this.counters_, name)) {
1075 this.counters_[name] = 0;
1076 }
1077 this.counters_[name] += amount;
1078 };
1079 StatsCollection.prototype.get = function () {
1080 return deepCopy(this.counters_);
1081 };
1082 return StatsCollection;
1083}());
1084
1085/**
1086 * @license
1087 * Copyright 2017 Google LLC
1088 *
1089 * Licensed under the Apache License, Version 2.0 (the "License");
1090 * you may not use this file except in compliance with the License.
1091 * You may obtain a copy of the License at
1092 *
1093 * http://www.apache.org/licenses/LICENSE-2.0
1094 *
1095 * Unless required by applicable law or agreed to in writing, software
1096 * distributed under the License is distributed on an "AS IS" BASIS,
1097 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1098 * See the License for the specific language governing permissions and
1099 * limitations under the License.
1100 */
1101var collections = {};
1102var reporters = {};
1103function statsManagerGetCollection(repoInfo) {
1104 var hashString = repoInfo.toString();
1105 if (!collections[hashString]) {
1106 collections[hashString] = new StatsCollection();
1107 }
1108 return collections[hashString];
1109}
1110function statsManagerGetOrCreateReporter(repoInfo, creatorFunction) {
1111 var hashString = repoInfo.toString();
1112 if (!reporters[hashString]) {
1113 reporters[hashString] = creatorFunction();
1114 }
1115 return reporters[hashString];
1116}
1117
1118/**
1119 * @license
1120 * Copyright 2017 Google LLC
1121 *
1122 * Licensed under the Apache License, Version 2.0 (the "License");
1123 * you may not use this file except in compliance with the License.
1124 * You may obtain a copy of the License at
1125 *
1126 * http://www.apache.org/licenses/LICENSE-2.0
1127 *
1128 * Unless required by applicable law or agreed to in writing, software
1129 * distributed under the License is distributed on an "AS IS" BASIS,
1130 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1131 * See the License for the specific language governing permissions and
1132 * limitations under the License.
1133 */
1134/**
1135 * This class ensures the packets from the server arrive in order
1136 * This class takes data from the server and ensures it gets passed into the callbacks in order.
1137 */
1138var PacketReceiver = /** @class */ (function () {
1139 /**
1140 * @param onMessage_
1141 */
1142 function PacketReceiver(onMessage_) {
1143 this.onMessage_ = onMessage_;
1144 this.pendingResponses = [];
1145 this.currentResponseNum = 0;
1146 this.closeAfterResponse = -1;
1147 this.onClose = null;
1148 }
1149 PacketReceiver.prototype.closeAfter = function (responseNum, callback) {
1150 this.closeAfterResponse = responseNum;
1151 this.onClose = callback;
1152 if (this.closeAfterResponse < this.currentResponseNum) {
1153 this.onClose();
1154 this.onClose = null;
1155 }
1156 };
1157 /**
1158 * Each message from the server comes with a response number, and an array of data. The responseNumber
1159 * allows us to ensure that we process them in the right order, since we can't be guaranteed that all
1160 * browsers will respond in the same order as the requests we sent
1161 */
1162 PacketReceiver.prototype.handleResponse = function (requestNum, data) {
1163 var _this = this;
1164 this.pendingResponses[requestNum] = data;
1165 var _loop_1 = function () {
1166 var toProcess = this_1.pendingResponses[this_1.currentResponseNum];
1167 delete this_1.pendingResponses[this_1.currentResponseNum];
1168 var _loop_2 = function (i) {
1169 if (toProcess[i]) {
1170 exceptionGuard(function () {
1171 _this.onMessage_(toProcess[i]);
1172 });
1173 }
1174 };
1175 for (var i = 0; i < toProcess.length; ++i) {
1176 _loop_2(i);
1177 }
1178 if (this_1.currentResponseNum === this_1.closeAfterResponse) {
1179 if (this_1.onClose) {
1180 this_1.onClose();
1181 this_1.onClose = null;
1182 }
1183 return "break";
1184 }
1185 this_1.currentResponseNum++;
1186 };
1187 var this_1 = this;
1188 while (this.pendingResponses[this.currentResponseNum]) {
1189 var state_1 = _loop_1();
1190 if (state_1 === "break")
1191 break;
1192 }
1193 };
1194 return PacketReceiver;
1195}());
1196
1197/**
1198 * @license
1199 * Copyright 2017 Google LLC
1200 *
1201 * Licensed under the Apache License, Version 2.0 (the "License");
1202 * you may not use this file except in compliance with the License.
1203 * You may obtain a copy of the License at
1204 *
1205 * http://www.apache.org/licenses/LICENSE-2.0
1206 *
1207 * Unless required by applicable law or agreed to in writing, software
1208 * distributed under the License is distributed on an "AS IS" BASIS,
1209 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1210 * See the License for the specific language governing permissions and
1211 * limitations under the License.
1212 */
1213// URL query parameters associated with longpolling
1214var FIREBASE_LONGPOLL_START_PARAM = 'start';
1215var FIREBASE_LONGPOLL_CLOSE_COMMAND = 'close';
1216var FIREBASE_LONGPOLL_COMMAND_CB_NAME = 'pLPCommand';
1217var FIREBASE_LONGPOLL_DATA_CB_NAME = 'pRTLPCB';
1218var FIREBASE_LONGPOLL_ID_PARAM = 'id';
1219var FIREBASE_LONGPOLL_PW_PARAM = 'pw';
1220var FIREBASE_LONGPOLL_SERIAL_PARAM = 'ser';
1221var FIREBASE_LONGPOLL_CALLBACK_ID_PARAM = 'cb';
1222var FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM = 'seg';
1223var FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET = 'ts';
1224var FIREBASE_LONGPOLL_DATA_PARAM = 'd';
1225var FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM = 'dframe';
1226//Data size constants.
1227//TODO: Perf: the maximum length actually differs from browser to browser.
1228// We should check what browser we're on and set accordingly.
1229var MAX_URL_DATA_SIZE = 1870;
1230var SEG_HEADER_SIZE = 30; //ie: &seg=8299234&ts=982389123&d=
1231var MAX_PAYLOAD_SIZE = MAX_URL_DATA_SIZE - SEG_HEADER_SIZE;
1232/**
1233 * Keepalive period
1234 * send a fresh request at minimum every 25 seconds. Opera has a maximum request
1235 * length of 30 seconds that we can't exceed.
1236 */
1237var KEEPALIVE_REQUEST_INTERVAL = 25000;
1238/**
1239 * How long to wait before aborting a long-polling connection attempt.
1240 */
1241var LP_CONNECT_TIMEOUT = 30000;
1242/**
1243 * This class manages a single long-polling connection.
1244 */
1245var BrowserPollConnection = /** @class */ (function () {
1246 /**
1247 * @param connId An identifier for this connection, used for logging
1248 * @param repoInfo The info for the endpoint to send data to.
1249 * @param applicationId The Firebase App ID for this project.
1250 * @param appCheckToken The AppCheck token for this client.
1251 * @param authToken The AuthToken to use for this connection.
1252 * @param transportSessionId Optional transportSessionid if we are
1253 * reconnecting for an existing transport session
1254 * @param lastSessionId Optional lastSessionId if the PersistentConnection has
1255 * already created a connection previously
1256 */
1257 function BrowserPollConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1258 var _this = this;
1259 this.connId = connId;
1260 this.repoInfo = repoInfo;
1261 this.applicationId = applicationId;
1262 this.appCheckToken = appCheckToken;
1263 this.authToken = authToken;
1264 this.transportSessionId = transportSessionId;
1265 this.lastSessionId = lastSessionId;
1266 this.bytesSent = 0;
1267 this.bytesReceived = 0;
1268 this.everConnected_ = false;
1269 this.log_ = logWrapper(connId);
1270 this.stats_ = statsManagerGetCollection(repoInfo);
1271 this.urlFn = function (params) {
1272 // Always add the token if we have one.
1273 if (_this.appCheckToken) {
1274 params[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1275 }
1276 return repoInfoConnectionURL(repoInfo, LONG_POLLING, params);
1277 };
1278 }
1279 /**
1280 * @param onMessage - Callback when messages arrive
1281 * @param onDisconnect - Callback with connection lost.
1282 */
1283 BrowserPollConnection.prototype.open = function (onMessage, onDisconnect) {
1284 var _this = this;
1285 this.curSegmentNum = 0;
1286 this.onDisconnect_ = onDisconnect;
1287 this.myPacketOrderer = new PacketReceiver(onMessage);
1288 this.isClosed_ = false;
1289 this.connectTimeoutTimer_ = setTimeout(function () {
1290 _this.log_('Timed out trying to connect.');
1291 // Make sure we clear the host cache
1292 _this.onClosed_();
1293 _this.connectTimeoutTimer_ = null;
1294 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1295 }, Math.floor(LP_CONNECT_TIMEOUT));
1296 // Ensure we delay the creation of the iframe until the DOM is loaded.
1297 executeWhenDOMReady(function () {
1298 if (_this.isClosed_) {
1299 return;
1300 }
1301 //Set up a callback that gets triggered once a connection is set up.
1302 _this.scriptTagHolder = new FirebaseIFrameScriptHolder(function () {
1303 var args = [];
1304 for (var _i = 0; _i < arguments.length; _i++) {
1305 args[_i] = arguments[_i];
1306 }
1307 var _a = __read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2]; _a[3]; _a[4];
1308 _this.incrementIncomingBytes_(args);
1309 if (!_this.scriptTagHolder) {
1310 return; // we closed the connection.
1311 }
1312 if (_this.connectTimeoutTimer_) {
1313 clearTimeout(_this.connectTimeoutTimer_);
1314 _this.connectTimeoutTimer_ = null;
1315 }
1316 _this.everConnected_ = true;
1317 if (command === FIREBASE_LONGPOLL_START_PARAM) {
1318 _this.id = arg1;
1319 _this.password = arg2;
1320 }
1321 else if (command === FIREBASE_LONGPOLL_CLOSE_COMMAND) {
1322 // Don't clear the host cache. We got a response from the server, so we know it's reachable
1323 if (arg1) {
1324 // We aren't expecting any more data (other than what the server's already in the process of sending us
1325 // through our already open polls), so don't send any more.
1326 _this.scriptTagHolder.sendNewPolls = false;
1327 // arg1 in this case is the last response number sent by the server. We should try to receive
1328 // all of the responses up to this one before closing
1329 _this.myPacketOrderer.closeAfter(arg1, function () {
1330 _this.onClosed_();
1331 });
1332 }
1333 else {
1334 _this.onClosed_();
1335 }
1336 }
1337 else {
1338 throw new Error('Unrecognized command received: ' + command);
1339 }
1340 }, function () {
1341 var args = [];
1342 for (var _i = 0; _i < arguments.length; _i++) {
1343 args[_i] = arguments[_i];
1344 }
1345 var _a = __read(args, 2), pN = _a[0], data = _a[1];
1346 _this.incrementIncomingBytes_(args);
1347 _this.myPacketOrderer.handleResponse(pN, data);
1348 }, function () {
1349 _this.onClosed_();
1350 }, _this.urlFn);
1351 //Send the initial request to connect. The serial number is simply to keep the browser from pulling previous results
1352 //from cache.
1353 var urlParams = {};
1354 urlParams[FIREBASE_LONGPOLL_START_PARAM] = 't';
1355 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = Math.floor(Math.random() * 100000000);
1356 if (_this.scriptTagHolder.uniqueCallbackIdentifier) {
1357 urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] =
1358 _this.scriptTagHolder.uniqueCallbackIdentifier;
1359 }
1360 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1361 if (_this.transportSessionId) {
1362 urlParams[TRANSPORT_SESSION_PARAM] = _this.transportSessionId;
1363 }
1364 if (_this.lastSessionId) {
1365 urlParams[LAST_SESSION_PARAM] = _this.lastSessionId;
1366 }
1367 if (_this.applicationId) {
1368 urlParams[APPLICATION_ID_PARAM] = _this.applicationId;
1369 }
1370 if (_this.appCheckToken) {
1371 urlParams[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1372 }
1373 if (typeof location !== 'undefined' &&
1374 location.hostname &&
1375 FORGE_DOMAIN_RE.test(location.hostname)) {
1376 urlParams[REFERER_PARAM] = FORGE_REF;
1377 }
1378 var connectURL = _this.urlFn(urlParams);
1379 _this.log_('Connecting via long-poll to ' + connectURL);
1380 _this.scriptTagHolder.addTag(connectURL, function () {
1381 /* do nothing */
1382 });
1383 });
1384 };
1385 /**
1386 * Call this when a handshake has completed successfully and we want to consider the connection established
1387 */
1388 BrowserPollConnection.prototype.start = function () {
1389 this.scriptTagHolder.startLongPoll(this.id, this.password);
1390 this.addDisconnectPingFrame(this.id, this.password);
1391 };
1392 /**
1393 * Forces long polling to be considered as a potential transport
1394 */
1395 BrowserPollConnection.forceAllow = function () {
1396 BrowserPollConnection.forceAllow_ = true;
1397 };
1398 /**
1399 * Forces longpolling to not be considered as a potential transport
1400 */
1401 BrowserPollConnection.forceDisallow = function () {
1402 BrowserPollConnection.forceDisallow_ = true;
1403 };
1404 // Static method, use string literal so it can be accessed in a generic way
1405 BrowserPollConnection.isAvailable = function () {
1406 if (isNodeSdk()) {
1407 return false;
1408 }
1409 else if (BrowserPollConnection.forceAllow_) {
1410 return true;
1411 }
1412 else {
1413 // NOTE: In React-Native there's normally no 'document', but if you debug a React-Native app in
1414 // the Chrome debugger, 'document' is defined, but document.createElement is null (2015/06/08).
1415 return (!BrowserPollConnection.forceDisallow_ &&
1416 typeof document !== 'undefined' &&
1417 document.createElement != null &&
1418 !isChromeExtensionContentScript() &&
1419 !isWindowsStoreApp());
1420 }
1421 };
1422 /**
1423 * No-op for polling
1424 */
1425 BrowserPollConnection.prototype.markConnectionHealthy = function () { };
1426 /**
1427 * Stops polling and cleans up the iframe
1428 */
1429 BrowserPollConnection.prototype.shutdown_ = function () {
1430 this.isClosed_ = true;
1431 if (this.scriptTagHolder) {
1432 this.scriptTagHolder.close();
1433 this.scriptTagHolder = null;
1434 }
1435 //remove the disconnect frame, which will trigger an XHR call to the server to tell it we're leaving.
1436 if (this.myDisconnFrame) {
1437 document.body.removeChild(this.myDisconnFrame);
1438 this.myDisconnFrame = null;
1439 }
1440 if (this.connectTimeoutTimer_) {
1441 clearTimeout(this.connectTimeoutTimer_);
1442 this.connectTimeoutTimer_ = null;
1443 }
1444 };
1445 /**
1446 * Triggered when this transport is closed
1447 */
1448 BrowserPollConnection.prototype.onClosed_ = function () {
1449 if (!this.isClosed_) {
1450 this.log_('Longpoll is closing itself');
1451 this.shutdown_();
1452 if (this.onDisconnect_) {
1453 this.onDisconnect_(this.everConnected_);
1454 this.onDisconnect_ = null;
1455 }
1456 }
1457 };
1458 /**
1459 * External-facing close handler. RealTime has requested we shut down. Kill our connection and tell the server
1460 * that we've left.
1461 */
1462 BrowserPollConnection.prototype.close = function () {
1463 if (!this.isClosed_) {
1464 this.log_('Longpoll is being closed.');
1465 this.shutdown_();
1466 }
1467 };
1468 /**
1469 * Send the JSON object down to the server. It will need to be stringified, base64 encoded, and then
1470 * broken into chunks (since URLs have a small maximum length).
1471 * @param data - The JSON data to transmit.
1472 */
1473 BrowserPollConnection.prototype.send = function (data) {
1474 var dataStr = stringify(data);
1475 this.bytesSent += dataStr.length;
1476 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1477 //first, lets get the base64-encoded data
1478 var base64data = base64Encode(dataStr);
1479 //We can only fit a certain amount in each URL, so we need to split this request
1480 //up into multiple pieces if it doesn't fit in one request.
1481 var dataSegs = splitStringBySize(base64data, MAX_PAYLOAD_SIZE);
1482 //Enqueue each segment for transmission. We assign each chunk a sequential ID and a total number
1483 //of segments so that we can reassemble the packet on the server.
1484 for (var i = 0; i < dataSegs.length; i++) {
1485 this.scriptTagHolder.enqueueSegment(this.curSegmentNum, dataSegs.length, dataSegs[i]);
1486 this.curSegmentNum++;
1487 }
1488 };
1489 /**
1490 * This is how we notify the server that we're leaving.
1491 * We aren't able to send requests with DHTML on a window close event, but we can
1492 * trigger XHR requests in some browsers (everything but Opera basically).
1493 */
1494 BrowserPollConnection.prototype.addDisconnectPingFrame = function (id, pw) {
1495 if (isNodeSdk()) {
1496 return;
1497 }
1498 this.myDisconnFrame = document.createElement('iframe');
1499 var urlParams = {};
1500 urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
1501 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = id;
1502 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = pw;
1503 this.myDisconnFrame.src = this.urlFn(urlParams);
1504 this.myDisconnFrame.style.display = 'none';
1505 document.body.appendChild(this.myDisconnFrame);
1506 };
1507 /**
1508 * Used to track the bytes received by this client
1509 */
1510 BrowserPollConnection.prototype.incrementIncomingBytes_ = function (args) {
1511 // TODO: This is an annoying perf hit just to track the number of incoming bytes. Maybe it should be opt-in.
1512 var bytesReceived = stringify(args).length;
1513 this.bytesReceived += bytesReceived;
1514 this.stats_.incrementCounter('bytes_received', bytesReceived);
1515 };
1516 return BrowserPollConnection;
1517}());
1518/*********************************************************************************************
1519 * A wrapper around an iframe that is used as a long-polling script holder.
1520 *********************************************************************************************/
1521var FirebaseIFrameScriptHolder = /** @class */ (function () {
1522 /**
1523 * @param commandCB - The callback to be called when control commands are recevied from the server.
1524 * @param onMessageCB - The callback to be triggered when responses arrive from the server.
1525 * @param onDisconnect - The callback to be triggered when this tag holder is closed
1526 * @param urlFn - A function that provides the URL of the endpoint to send data to.
1527 */
1528 function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnect, urlFn) {
1529 this.onDisconnect = onDisconnect;
1530 this.urlFn = urlFn;
1531 //We maintain a count of all of the outstanding requests, because if we have too many active at once it can cause
1532 //problems in some browsers.
1533 this.outstandingRequests = new Set();
1534 //A queue of the pending segments waiting for transmission to the server.
1535 this.pendingSegs = [];
1536 //A serial number. We use this for two things:
1537 // 1) A way to ensure the browser doesn't cache responses to polls
1538 // 2) A way to make the server aware when long-polls arrive in a different order than we started them. The
1539 // server needs to release both polls in this case or it will cause problems in Opera since Opera can only execute
1540 // JSONP code in the order it was added to the iframe.
1541 this.currentSerial = Math.floor(Math.random() * 100000000);
1542 // This gets set to false when we're "closing down" the connection (e.g. we're switching transports but there's still
1543 // incoming data from the server that we're waiting for).
1544 this.sendNewPolls = true;
1545 if (!isNodeSdk()) {
1546 //Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
1547 //iframes where we put the long-polling script tags. We have two callbacks:
1548 // 1) Command Callback - Triggered for control issues, like starting a connection.
1549 // 2) Message Callback - Triggered when new data arrives.
1550 this.uniqueCallbackIdentifier = LUIDGenerator();
1551 window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
1552 window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] =
1553 onMessageCB;
1554 //Create an iframe for us to add script tags to.
1555 this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_();
1556 // Set the iframe's contents.
1557 var script = '';
1558 // if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
1559 // for ie9, but ie8 needs to do it again in the document itself.
1560 if (this.myIFrame.src &&
1561 this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
1562 var currentDomain = document.domain;
1563 script = '<script>document.domain="' + currentDomain + '";</script>';
1564 }
1565 var iframeContents = '<html><body>' + script + '</body></html>';
1566 try {
1567 this.myIFrame.doc.open();
1568 this.myIFrame.doc.write(iframeContents);
1569 this.myIFrame.doc.close();
1570 }
1571 catch (e) {
1572 log('frame writing exception');
1573 if (e.stack) {
1574 log(e.stack);
1575 }
1576 log(e);
1577 }
1578 }
1579 else {
1580 this.commandCB = commandCB;
1581 this.onMessageCB = onMessageCB;
1582 }
1583 }
1584 /**
1585 * Each browser has its own funny way to handle iframes. Here we mush them all together into one object that I can
1586 * actually use.
1587 */
1588 FirebaseIFrameScriptHolder.createIFrame_ = function () {
1589 var iframe = document.createElement('iframe');
1590 iframe.style.display = 'none';
1591 // This is necessary in order to initialize the document inside the iframe
1592 if (document.body) {
1593 document.body.appendChild(iframe);
1594 try {
1595 // If document.domain has been modified in IE, this will throw an error, and we need to set the
1596 // domain of the iframe's document manually. We can do this via a javascript: url as the src attribute
1597 // Also note that we must do this *after* the iframe has been appended to the page. Otherwise it doesn't work.
1598 var a = iframe.contentWindow.document;
1599 if (!a) {
1600 // Apologies for the log-spam, I need to do something to keep closure from optimizing out the assignment above.
1601 log('No IE domain setting required');
1602 }
1603 }
1604 catch (e) {
1605 var domain = document.domain;
1606 iframe.src =
1607 "javascript:void((function(){document.open();document.domain='" +
1608 domain +
1609 "';document.close();})())";
1610 }
1611 }
1612 else {
1613 // LongPollConnection attempts to delay initialization until the document is ready, so hopefully this
1614 // never gets hit.
1615 throw 'Document body has not initialized. Wait to initialize Firebase until after the document is ready.';
1616 }
1617 // Get the document of the iframe in a browser-specific way.
1618 if (iframe.contentDocument) {
1619 iframe.doc = iframe.contentDocument; // Firefox, Opera, Safari
1620 }
1621 else if (iframe.contentWindow) {
1622 iframe.doc = iframe.contentWindow.document; // Internet Explorer
1623 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1624 }
1625 else if (iframe.document) {
1626 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1627 iframe.doc = iframe.document; //others?
1628 }
1629 return iframe;
1630 };
1631 /**
1632 * Cancel all outstanding queries and remove the frame.
1633 */
1634 FirebaseIFrameScriptHolder.prototype.close = function () {
1635 var _this = this;
1636 //Mark this iframe as dead, so no new requests are sent.
1637 this.alive = false;
1638 if (this.myIFrame) {
1639 //We have to actually remove all of the html inside this iframe before removing it from the
1640 //window, or IE will continue loading and executing the script tags we've already added, which
1641 //can lead to some errors being thrown. Setting innerHTML seems to be the easiest way to do this.
1642 this.myIFrame.doc.body.innerHTML = '';
1643 setTimeout(function () {
1644 if (_this.myIFrame !== null) {
1645 document.body.removeChild(_this.myIFrame);
1646 _this.myIFrame = null;
1647 }
1648 }, Math.floor(0));
1649 }
1650 // Protect from being called recursively.
1651 var onDisconnect = this.onDisconnect;
1652 if (onDisconnect) {
1653 this.onDisconnect = null;
1654 onDisconnect();
1655 }
1656 };
1657 /**
1658 * Actually start the long-polling session by adding the first script tag(s) to the iframe.
1659 * @param id - The ID of this connection
1660 * @param pw - The password for this connection
1661 */
1662 FirebaseIFrameScriptHolder.prototype.startLongPoll = function (id, pw) {
1663 this.myID = id;
1664 this.myPW = pw;
1665 this.alive = true;
1666 //send the initial request. If there are requests queued, make sure that we transmit as many as we are currently able to.
1667 while (this.newRequest_()) { }
1668 };
1669 /**
1670 * This is called any time someone might want a script tag to be added. It adds a script tag when there aren't
1671 * too many outstanding requests and we are still alive.
1672 *
1673 * If there are outstanding packet segments to send, it sends one. If there aren't, it sends a long-poll anyways if
1674 * needed.
1675 */
1676 FirebaseIFrameScriptHolder.prototype.newRequest_ = function () {
1677 // We keep one outstanding request open all the time to receive data, but if we need to send data
1678 // (pendingSegs.length > 0) then we create a new request to send the data. The server will automatically
1679 // close the old request.
1680 if (this.alive &&
1681 this.sendNewPolls &&
1682 this.outstandingRequests.size < (this.pendingSegs.length > 0 ? 2 : 1)) {
1683 //construct our url
1684 this.currentSerial++;
1685 var urlParams = {};
1686 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
1687 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
1688 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = this.currentSerial;
1689 var theURL = this.urlFn(urlParams);
1690 //Now add as much data as we can.
1691 var curDataString = '';
1692 var i = 0;
1693 while (this.pendingSegs.length > 0) {
1694 //first, lets see if the next segment will fit.
1695 var nextSeg = this.pendingSegs[0];
1696 if (nextSeg.d.length +
1697 SEG_HEADER_SIZE +
1698 curDataString.length <=
1699 MAX_URL_DATA_SIZE) {
1700 //great, the segment will fit. Lets append it.
1701 var theSeg = this.pendingSegs.shift();
1702 curDataString =
1703 curDataString +
1704 '&' +
1705 FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM +
1706 i +
1707 '=' +
1708 theSeg.seg +
1709 '&' +
1710 FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET +
1711 i +
1712 '=' +
1713 theSeg.ts +
1714 '&' +
1715 FIREBASE_LONGPOLL_DATA_PARAM +
1716 i +
1717 '=' +
1718 theSeg.d;
1719 i++;
1720 }
1721 else {
1722 break;
1723 }
1724 }
1725 theURL = theURL + curDataString;
1726 this.addLongPollTag_(theURL, this.currentSerial);
1727 return true;
1728 }
1729 else {
1730 return false;
1731 }
1732 };
1733 /**
1734 * Queue a packet for transmission to the server.
1735 * @param segnum - A sequential id for this packet segment used for reassembly
1736 * @param totalsegs - The total number of segments in this packet
1737 * @param data - The data for this segment.
1738 */
1739 FirebaseIFrameScriptHolder.prototype.enqueueSegment = function (segnum, totalsegs, data) {
1740 //add this to the queue of segments to send.
1741 this.pendingSegs.push({ seg: segnum, ts: totalsegs, d: data });
1742 //send the data immediately if there isn't already data being transmitted, unless
1743 //startLongPoll hasn't been called yet.
1744 if (this.alive) {
1745 this.newRequest_();
1746 }
1747 };
1748 /**
1749 * Add a script tag for a regular long-poll request.
1750 * @param url - The URL of the script tag.
1751 * @param serial - The serial number of the request.
1752 */
1753 FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function (url, serial) {
1754 var _this = this;
1755 //remember that we sent this request.
1756 this.outstandingRequests.add(serial);
1757 var doNewRequest = function () {
1758 _this.outstandingRequests.delete(serial);
1759 _this.newRequest_();
1760 };
1761 // If this request doesn't return on its own accord (by the server sending us some data), we'll
1762 // create a new one after the KEEPALIVE interval to make sure we always keep a fresh request open.
1763 var keepaliveTimeout = setTimeout(doNewRequest, Math.floor(KEEPALIVE_REQUEST_INTERVAL));
1764 var readyStateCB = function () {
1765 // Request completed. Cancel the keepalive.
1766 clearTimeout(keepaliveTimeout);
1767 // Trigger a new request so we can continue receiving data.
1768 doNewRequest();
1769 };
1770 this.addTag(url, readyStateCB);
1771 };
1772 /**
1773 * Add an arbitrary script tag to the iframe.
1774 * @param url - The URL for the script tag source.
1775 * @param loadCB - A callback to be triggered once the script has loaded.
1776 */
1777 FirebaseIFrameScriptHolder.prototype.addTag = function (url, loadCB) {
1778 var _this = this;
1779 if (isNodeSdk()) {
1780 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1781 this.doNodeLongPoll(url, loadCB);
1782 }
1783 else {
1784 setTimeout(function () {
1785 try {
1786 // if we're already closed, don't add this poll
1787 if (!_this.sendNewPolls) {
1788 return;
1789 }
1790 var newScript_1 = _this.myIFrame.doc.createElement('script');
1791 newScript_1.type = 'text/javascript';
1792 newScript_1.async = true;
1793 newScript_1.src = url;
1794 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1795 newScript_1.onload = newScript_1.onreadystatechange =
1796 function () {
1797 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1798 var rstate = newScript_1.readyState;
1799 if (!rstate || rstate === 'loaded' || rstate === 'complete') {
1800 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1801 newScript_1.onload = newScript_1.onreadystatechange = null;
1802 if (newScript_1.parentNode) {
1803 newScript_1.parentNode.removeChild(newScript_1);
1804 }
1805 loadCB();
1806 }
1807 };
1808 newScript_1.onerror = function () {
1809 log('Long-poll script failed to load: ' + url);
1810 _this.sendNewPolls = false;
1811 _this.close();
1812 };
1813 _this.myIFrame.doc.body.appendChild(newScript_1);
1814 }
1815 catch (e) {
1816 // TODO: we should make this error visible somehow
1817 }
1818 }, Math.floor(1));
1819 }
1820 };
1821 return FirebaseIFrameScriptHolder;
1822}());
1823
1824/**
1825 * @license
1826 * Copyright 2017 Google LLC
1827 *
1828 * Licensed under the Apache License, Version 2.0 (the "License");
1829 * you may not use this file except in compliance with the License.
1830 * You may obtain a copy of the License at
1831 *
1832 * http://www.apache.org/licenses/LICENSE-2.0
1833 *
1834 * Unless required by applicable law or agreed to in writing, software
1835 * distributed under the License is distributed on an "AS IS" BASIS,
1836 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1837 * See the License for the specific language governing permissions and
1838 * limitations under the License.
1839 */
1840var WEBSOCKET_MAX_FRAME_SIZE = 16384;
1841var WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
1842var WebSocketImpl = null;
1843if (typeof MozWebSocket !== 'undefined') {
1844 WebSocketImpl = MozWebSocket;
1845}
1846else if (typeof WebSocket !== 'undefined') {
1847 WebSocketImpl = WebSocket;
1848}
1849/**
1850 * Create a new websocket connection with the given callbacks.
1851 */
1852var WebSocketConnection = /** @class */ (function () {
1853 /**
1854 * @param connId identifier for this transport
1855 * @param repoInfo The info for the websocket endpoint.
1856 * @param applicationId The Firebase App ID for this project.
1857 * @param appCheckToken The App Check Token for this client.
1858 * @param authToken The Auth Token for this client.
1859 * @param transportSessionId Optional transportSessionId if this is connecting
1860 * to an existing transport session
1861 * @param lastSessionId Optional lastSessionId if there was a previous
1862 * connection
1863 */
1864 function WebSocketConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1865 this.connId = connId;
1866 this.applicationId = applicationId;
1867 this.appCheckToken = appCheckToken;
1868 this.authToken = authToken;
1869 this.keepaliveTimer = null;
1870 this.frames = null;
1871 this.totalFrames = 0;
1872 this.bytesSent = 0;
1873 this.bytesReceived = 0;
1874 this.log_ = logWrapper(this.connId);
1875 this.stats_ = statsManagerGetCollection(repoInfo);
1876 this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId);
1877 this.nodeAdmin = repoInfo.nodeAdmin;
1878 }
1879 /**
1880 * @param repoInfo - The info for the websocket endpoint.
1881 * @param transportSessionId - Optional transportSessionId if this is connecting to an existing transport
1882 * session
1883 * @param lastSessionId - Optional lastSessionId if there was a previous connection
1884 * @returns connection url
1885 */
1886 WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId) {
1887 var urlParams = {};
1888 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1889 if (!isNodeSdk() &&
1890 typeof location !== 'undefined' &&
1891 location.hostname &&
1892 FORGE_DOMAIN_RE.test(location.hostname)) {
1893 urlParams[REFERER_PARAM] = FORGE_REF;
1894 }
1895 if (transportSessionId) {
1896 urlParams[TRANSPORT_SESSION_PARAM] = transportSessionId;
1897 }
1898 if (lastSessionId) {
1899 urlParams[LAST_SESSION_PARAM] = lastSessionId;
1900 }
1901 if (appCheckToken) {
1902 urlParams[APP_CHECK_TOKEN_PARAM] = appCheckToken;
1903 }
1904 if (applicationId) {
1905 urlParams[APPLICATION_ID_PARAM] = applicationId;
1906 }
1907 return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
1908 };
1909 /**
1910 * @param onMessage - Callback when messages arrive
1911 * @param onDisconnect - Callback with connection lost.
1912 */
1913 WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
1914 var _this = this;
1915 this.onDisconnect = onDisconnect;
1916 this.onMessage = onMessage;
1917 this.log_('Websocket connecting to ' + this.connURL);
1918 this.everConnected_ = false;
1919 // Assume failure until proven otherwise.
1920 PersistentStorage.set('previous_websocket_failure', true);
1921 try {
1922 var options = void 0;
1923 if (isNodeSdk()) {
1924 var device = this.nodeAdmin ? 'AdminNode' : 'Node';
1925 // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
1926 var options_1 = {
1927 headers: {
1928 'User-Agent': "Firebase/" + PROTOCOL_VERSION + "/" + SDK_VERSION + "/" + process.platform + "/" + device,
1929 'X-Firebase-GMPID': this.applicationId || ''
1930 }
1931 };
1932 // If using Node with admin creds, AppCheck-related checks are unnecessary.
1933 // Note that we send the credentials here even if they aren't admin credentials, which is
1934 // not a problem.
1935 // Note that this header is just used to bypass appcheck, and the token should still be sent
1936 // through the websocket connection once it is established.
1937 if (this.authToken) {
1938 options_1.headers['Authorization'] = "Bearer " + this.authToken;
1939 }
1940 if (this.appCheckToken) {
1941 options_1.headers['X-Firebase-AppCheck'] = this.appCheckToken;
1942 }
1943 // Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
1944 var env = process['env'];
1945 var proxy = this.connURL.indexOf('wss://') === 0
1946 ? env['HTTPS_PROXY'] || env['https_proxy']
1947 : env['HTTP_PROXY'] || env['http_proxy'];
1948 if (proxy) {
1949 options_1['proxy'] = { origin: proxy };
1950 }
1951 }
1952 this.mySock = new WebSocketImpl(this.connURL, [], options);
1953 }
1954 catch (e) {
1955 this.log_('Error instantiating WebSocket.');
1956 var error = e.message || e.data;
1957 if (error) {
1958 this.log_(error);
1959 }
1960 this.onClosed_();
1961 return;
1962 }
1963 this.mySock.onopen = function () {
1964 _this.log_('Websocket connected.');
1965 _this.everConnected_ = true;
1966 };
1967 this.mySock.onclose = function () {
1968 _this.log_('Websocket connection was disconnected.');
1969 _this.mySock = null;
1970 _this.onClosed_();
1971 };
1972 this.mySock.onmessage = function (m) {
1973 _this.handleIncomingFrame(m);
1974 };
1975 this.mySock.onerror = function (e) {
1976 _this.log_('WebSocket error. Closing connection.');
1977 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1978 var error = e.message || e.data;
1979 if (error) {
1980 _this.log_(error);
1981 }
1982 _this.onClosed_();
1983 };
1984 };
1985 /**
1986 * No-op for websockets, we don't need to do anything once the connection is confirmed as open
1987 */
1988 WebSocketConnection.prototype.start = function () { };
1989 WebSocketConnection.forceDisallow = function () {
1990 WebSocketConnection.forceDisallow_ = true;
1991 };
1992 WebSocketConnection.isAvailable = function () {
1993 var isOldAndroid = false;
1994 if (typeof navigator !== 'undefined' && navigator.userAgent) {
1995 var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
1996 var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
1997 if (oldAndroidMatch && oldAndroidMatch.length > 1) {
1998 if (parseFloat(oldAndroidMatch[1]) < 4.4) {
1999 isOldAndroid = true;
2000 }
2001 }
2002 }
2003 return (!isOldAndroid &&
2004 WebSocketImpl !== null &&
2005 !WebSocketConnection.forceDisallow_);
2006 };
2007 /**
2008 * Returns true if we previously failed to connect with this transport.
2009 */
2010 WebSocketConnection.previouslyFailed = function () {
2011 // If our persistent storage is actually only in-memory storage,
2012 // we default to assuming that it previously failed to be safe.
2013 return (PersistentStorage.isInMemoryStorage ||
2014 PersistentStorage.get('previous_websocket_failure') === true);
2015 };
2016 WebSocketConnection.prototype.markConnectionHealthy = function () {
2017 PersistentStorage.remove('previous_websocket_failure');
2018 };
2019 WebSocketConnection.prototype.appendFrame_ = function (data) {
2020 this.frames.push(data);
2021 if (this.frames.length === this.totalFrames) {
2022 var fullMess = this.frames.join('');
2023 this.frames = null;
2024 var jsonMess = jsonEval(fullMess);
2025 //handle the message
2026 this.onMessage(jsonMess);
2027 }
2028 };
2029 /**
2030 * @param frameCount - The number of frames we are expecting from the server
2031 */
2032 WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
2033 this.totalFrames = frameCount;
2034 this.frames = [];
2035 };
2036 /**
2037 * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
2038 * @returns Any remaining data to be process, or null if there is none
2039 */
2040 WebSocketConnection.prototype.extractFrameCount_ = function (data) {
2041 assert(this.frames === null, 'We already have a frame buffer');
2042 // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
2043 // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
2044 if (data.length <= 6) {
2045 var frameCount = Number(data);
2046 if (!isNaN(frameCount)) {
2047 this.handleNewFrameCount_(frameCount);
2048 return null;
2049 }
2050 }
2051 this.handleNewFrameCount_(1);
2052 return data;
2053 };
2054 /**
2055 * Process a websocket frame that has arrived from the server.
2056 * @param mess - The frame data
2057 */
2058 WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
2059 if (this.mySock === null) {
2060 return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
2061 }
2062 var data = mess['data'];
2063 this.bytesReceived += data.length;
2064 this.stats_.incrementCounter('bytes_received', data.length);
2065 this.resetKeepAlive();
2066 if (this.frames !== null) {
2067 // we're buffering
2068 this.appendFrame_(data);
2069 }
2070 else {
2071 // try to parse out a frame count, otherwise, assume 1 and process it
2072 var remainingData = this.extractFrameCount_(data);
2073 if (remainingData !== null) {
2074 this.appendFrame_(remainingData);
2075 }
2076 }
2077 };
2078 /**
2079 * Send a message to the server
2080 * @param data - The JSON object to transmit
2081 */
2082 WebSocketConnection.prototype.send = function (data) {
2083 this.resetKeepAlive();
2084 var dataStr = stringify(data);
2085 this.bytesSent += dataStr.length;
2086 this.stats_.incrementCounter('bytes_sent', dataStr.length);
2087 //We can only fit a certain amount in each websocket frame, so we need to split this request
2088 //up into multiple pieces if it doesn't fit in one request.
2089 var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
2090 //Send the length header
2091 if (dataSegs.length > 1) {
2092 this.sendString_(String(dataSegs.length));
2093 }
2094 //Send the actual data in segments.
2095 for (var i = 0; i < dataSegs.length; i++) {
2096 this.sendString_(dataSegs[i]);
2097 }
2098 };
2099 WebSocketConnection.prototype.shutdown_ = function () {
2100 this.isClosed_ = true;
2101 if (this.keepaliveTimer) {
2102 clearInterval(this.keepaliveTimer);
2103 this.keepaliveTimer = null;
2104 }
2105 if (this.mySock) {
2106 this.mySock.close();
2107 this.mySock = null;
2108 }
2109 };
2110 WebSocketConnection.prototype.onClosed_ = function () {
2111 if (!this.isClosed_) {
2112 this.log_('WebSocket is closing itself');
2113 this.shutdown_();
2114 // since this is an internal close, trigger the close listener
2115 if (this.onDisconnect) {
2116 this.onDisconnect(this.everConnected_);
2117 this.onDisconnect = null;
2118 }
2119 }
2120 };
2121 /**
2122 * External-facing close handler.
2123 * Close the websocket and kill the connection.
2124 */
2125 WebSocketConnection.prototype.close = function () {
2126 if (!this.isClosed_) {
2127 this.log_('WebSocket is being closed');
2128 this.shutdown_();
2129 }
2130 };
2131 /**
2132 * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
2133 * the last activity.
2134 */
2135 WebSocketConnection.prototype.resetKeepAlive = function () {
2136 var _this = this;
2137 clearInterval(this.keepaliveTimer);
2138 this.keepaliveTimer = setInterval(function () {
2139 //If there has been no websocket activity for a while, send a no-op
2140 if (_this.mySock) {
2141 _this.sendString_('0');
2142 }
2143 _this.resetKeepAlive();
2144 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2145 }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
2146 };
2147 /**
2148 * Send a string over the websocket.
2149 *
2150 * @param str - String to send.
2151 */
2152 WebSocketConnection.prototype.sendString_ = function (str) {
2153 // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
2154 // calls for some unknown reason. We treat these as an error and disconnect.
2155 // See https://app.asana.com/0/58926111402292/68021340250410
2156 try {
2157 this.mySock.send(str);
2158 }
2159 catch (e) {
2160 this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
2161 setTimeout(this.onClosed_.bind(this), 0);
2162 }
2163 };
2164 /**
2165 * Number of response before we consider the connection "healthy."
2166 */
2167 WebSocketConnection.responsesRequiredToBeHealthy = 2;
2168 /**
2169 * Time to wait for the connection te become healthy before giving up.
2170 */
2171 WebSocketConnection.healthyTimeout = 30000;
2172 return WebSocketConnection;
2173}());
2174
2175/**
2176 * @license
2177 * Copyright 2017 Google LLC
2178 *
2179 * Licensed under the Apache License, Version 2.0 (the "License");
2180 * you may not use this file except in compliance with the License.
2181 * You may obtain a copy of the License at
2182 *
2183 * http://www.apache.org/licenses/LICENSE-2.0
2184 *
2185 * Unless required by applicable law or agreed to in writing, software
2186 * distributed under the License is distributed on an "AS IS" BASIS,
2187 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2188 * See the License for the specific language governing permissions and
2189 * limitations under the License.
2190 */
2191/**
2192 * Currently simplistic, this class manages what transport a Connection should use at various stages of its
2193 * lifecycle.
2194 *
2195 * It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
2196 * they are available.
2197 */
2198var TransportManager = /** @class */ (function () {
2199 /**
2200 * @param repoInfo - Metadata around the namespace we're connecting to
2201 */
2202 function TransportManager(repoInfo) {
2203 this.initTransports_(repoInfo);
2204 }
2205 Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
2206 get: function () {
2207 return [BrowserPollConnection, WebSocketConnection];
2208 },
2209 enumerable: false,
2210 configurable: true
2211 });
2212 Object.defineProperty(TransportManager, "IS_TRANSPORT_INITIALIZED", {
2213 /**
2214 * Returns whether transport has been selected to ensure WebSocketConnection or BrowserPollConnection are not called after
2215 * TransportManager has already set up transports_
2216 */
2217 get: function () {
2218 return this.globalTransportInitialized_;
2219 },
2220 enumerable: false,
2221 configurable: true
2222 });
2223 TransportManager.prototype.initTransports_ = function (repoInfo) {
2224 var e_1, _a;
2225 var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
2226 var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
2227 if (repoInfo.webSocketOnly) {
2228 if (!isWebSocketsAvailable) {
2229 warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
2230 }
2231 isSkipPollConnection = true;
2232 }
2233 if (isSkipPollConnection) {
2234 this.transports_ = [WebSocketConnection];
2235 }
2236 else {
2237 var transports = (this.transports_ = []);
2238 try {
2239 for (var _b = __values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
2240 var transport = _c.value;
2241 if (transport && transport['isAvailable']()) {
2242 transports.push(transport);
2243 }
2244 }
2245 }
2246 catch (e_1_1) { e_1 = { error: e_1_1 }; }
2247 finally {
2248 try {
2249 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
2250 }
2251 finally { if (e_1) throw e_1.error; }
2252 }
2253 TransportManager.globalTransportInitialized_ = true;
2254 }
2255 };
2256 /**
2257 * @returns The constructor for the initial transport to use
2258 */
2259 TransportManager.prototype.initialTransport = function () {
2260 if (this.transports_.length > 0) {
2261 return this.transports_[0];
2262 }
2263 else {
2264 throw new Error('No transports available');
2265 }
2266 };
2267 /**
2268 * @returns The constructor for the next transport, or null
2269 */
2270 TransportManager.prototype.upgradeTransport = function () {
2271 if (this.transports_.length > 1) {
2272 return this.transports_[1];
2273 }
2274 else {
2275 return null;
2276 }
2277 };
2278 // Keeps track of whether the TransportManager has already chosen a transport to use
2279 TransportManager.globalTransportInitialized_ = false;
2280 return TransportManager;
2281}());
2282
2283/**
2284 * @license
2285 * Copyright 2017 Google LLC
2286 *
2287 * Licensed under the Apache License, Version 2.0 (the "License");
2288 * you may not use this file except in compliance with the License.
2289 * You may obtain a copy of the License at
2290 *
2291 * http://www.apache.org/licenses/LICENSE-2.0
2292 *
2293 * Unless required by applicable law or agreed to in writing, software
2294 * distributed under the License is distributed on an "AS IS" BASIS,
2295 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2296 * See the License for the specific language governing permissions and
2297 * limitations under the License.
2298 */
2299// Abort upgrade attempt if it takes longer than 60s.
2300var UPGRADE_TIMEOUT = 60000;
2301// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
2302// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
2303var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
2304// 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)
2305// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
2306// but we've sent/received enough bytes, we don't cancel the connection.
2307var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
2308var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
2309var MESSAGE_TYPE = 't';
2310var MESSAGE_DATA = 'd';
2311var CONTROL_SHUTDOWN = 's';
2312var CONTROL_RESET = 'r';
2313var CONTROL_ERROR = 'e';
2314var CONTROL_PONG = 'o';
2315var SWITCH_ACK = 'a';
2316var END_TRANSMISSION = 'n';
2317var PING = 'p';
2318var SERVER_HELLO = 'h';
2319/**
2320 * Creates a new real-time connection to the server using whichever method works
2321 * best in the current browser.
2322 */
2323var Connection = /** @class */ (function () {
2324 /**
2325 * @param id - an id for this connection
2326 * @param repoInfo_ - the info for the endpoint to connect to
2327 * @param applicationId_ - the Firebase App ID for this project
2328 * @param appCheckToken_ - The App Check Token for this device.
2329 * @param authToken_ - The auth token for this session.
2330 * @param onMessage_ - the callback to be triggered when a server-push message arrives
2331 * @param onReady_ - the callback to be triggered when this connection is ready to send messages.
2332 * @param onDisconnect_ - the callback to be triggered when a connection was lost
2333 * @param onKill_ - the callback to be triggered when this connection has permanently shut down.
2334 * @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
2335 */
2336 function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
2337 this.id = id;
2338 this.repoInfo_ = repoInfo_;
2339 this.applicationId_ = applicationId_;
2340 this.appCheckToken_ = appCheckToken_;
2341 this.authToken_ = authToken_;
2342 this.onMessage_ = onMessage_;
2343 this.onReady_ = onReady_;
2344 this.onDisconnect_ = onDisconnect_;
2345 this.onKill_ = onKill_;
2346 this.lastSessionId = lastSessionId;
2347 this.connectionCount = 0;
2348 this.pendingDataMessages = [];
2349 this.state_ = 0 /* CONNECTING */;
2350 this.log_ = logWrapper('c:' + this.id + ':');
2351 this.transportManager_ = new TransportManager(repoInfo_);
2352 this.log_('Connection created');
2353 this.start_();
2354 }
2355 /**
2356 * Starts a connection attempt
2357 */
2358 Connection.prototype.start_ = function () {
2359 var _this = this;
2360 var conn = this.transportManager_.initialTransport();
2361 this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, null, this.lastSessionId);
2362 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2363 // can consider the transport healthy.
2364 this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
2365 var onMessageReceived = this.connReceiver_(this.conn_);
2366 var onConnectionLost = this.disconnReceiver_(this.conn_);
2367 this.tx_ = this.conn_;
2368 this.rx_ = this.conn_;
2369 this.secondaryConn_ = null;
2370 this.isHealthy_ = false;
2371 /*
2372 * Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
2373 * This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
2374 * Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
2375 * still have the context of your originating frame.
2376 */
2377 setTimeout(function () {
2378 // this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
2379 _this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
2380 }, Math.floor(0));
2381 var healthyTimeoutMS = conn['healthyTimeout'] || 0;
2382 if (healthyTimeoutMS > 0) {
2383 this.healthyTimeout_ = setTimeoutNonBlocking(function () {
2384 _this.healthyTimeout_ = null;
2385 if (!_this.isHealthy_) {
2386 if (_this.conn_ &&
2387 _this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
2388 _this.log_('Connection exceeded healthy timeout but has received ' +
2389 _this.conn_.bytesReceived +
2390 ' bytes. Marking connection healthy.');
2391 _this.isHealthy_ = true;
2392 _this.conn_.markConnectionHealthy();
2393 }
2394 else if (_this.conn_ &&
2395 _this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
2396 _this.log_('Connection exceeded healthy timeout but has sent ' +
2397 _this.conn_.bytesSent +
2398 ' bytes. Leaving connection alive.');
2399 // NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
2400 // the server.
2401 }
2402 else {
2403 _this.log_('Closing unhealthy connection after timeout.');
2404 _this.close();
2405 }
2406 }
2407 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2408 }, Math.floor(healthyTimeoutMS));
2409 }
2410 };
2411 Connection.prototype.nextTransportId_ = function () {
2412 return 'c:' + this.id + ':' + this.connectionCount++;
2413 };
2414 Connection.prototype.disconnReceiver_ = function (conn) {
2415 var _this = this;
2416 return function (everConnected) {
2417 if (conn === _this.conn_) {
2418 _this.onConnectionLost_(everConnected);
2419 }
2420 else if (conn === _this.secondaryConn_) {
2421 _this.log_('Secondary connection lost.');
2422 _this.onSecondaryConnectionLost_();
2423 }
2424 else {
2425 _this.log_('closing an old connection');
2426 }
2427 };
2428 };
2429 Connection.prototype.connReceiver_ = function (conn) {
2430 var _this = this;
2431 return function (message) {
2432 if (_this.state_ !== 2 /* DISCONNECTED */) {
2433 if (conn === _this.rx_) {
2434 _this.onPrimaryMessageReceived_(message);
2435 }
2436 else if (conn === _this.secondaryConn_) {
2437 _this.onSecondaryMessageReceived_(message);
2438 }
2439 else {
2440 _this.log_('message on old connection');
2441 }
2442 }
2443 };
2444 };
2445 /**
2446 * @param dataMsg - An arbitrary data message to be sent to the server
2447 */
2448 Connection.prototype.sendRequest = function (dataMsg) {
2449 // wrap in a data message envelope and send it on
2450 var msg = { t: 'd', d: dataMsg };
2451 this.sendData_(msg);
2452 };
2453 Connection.prototype.tryCleanupConnection = function () {
2454 if (this.tx_ === this.secondaryConn_ && this.rx_ === this.secondaryConn_) {
2455 this.log_('cleaning up and promoting a connection: ' + this.secondaryConn_.connId);
2456 this.conn_ = this.secondaryConn_;
2457 this.secondaryConn_ = null;
2458 // the server will shutdown the old connection
2459 }
2460 };
2461 Connection.prototype.onSecondaryControl_ = function (controlData) {
2462 if (MESSAGE_TYPE in controlData) {
2463 var cmd = controlData[MESSAGE_TYPE];
2464 if (cmd === SWITCH_ACK) {
2465 this.upgradeIfSecondaryHealthy_();
2466 }
2467 else if (cmd === CONTROL_RESET) {
2468 // Most likely the session wasn't valid. Abandon the switch attempt
2469 this.log_('Got a reset on secondary, closing it');
2470 this.secondaryConn_.close();
2471 // If we were already using this connection for something, than we need to fully close
2472 if (this.tx_ === this.secondaryConn_ ||
2473 this.rx_ === this.secondaryConn_) {
2474 this.close();
2475 }
2476 }
2477 else if (cmd === CONTROL_PONG) {
2478 this.log_('got pong on secondary.');
2479 this.secondaryResponsesRequired_--;
2480 this.upgradeIfSecondaryHealthy_();
2481 }
2482 }
2483 };
2484 Connection.prototype.onSecondaryMessageReceived_ = function (parsedData) {
2485 var layer = requireKey('t', parsedData);
2486 var data = requireKey('d', parsedData);
2487 if (layer === 'c') {
2488 this.onSecondaryControl_(data);
2489 }
2490 else if (layer === 'd') {
2491 // got a data message, but we're still second connection. Need to buffer it up
2492 this.pendingDataMessages.push(data);
2493 }
2494 else {
2495 throw new Error('Unknown protocol layer: ' + layer);
2496 }
2497 };
2498 Connection.prototype.upgradeIfSecondaryHealthy_ = function () {
2499 if (this.secondaryResponsesRequired_ <= 0) {
2500 this.log_('Secondary connection is healthy.');
2501 this.isHealthy_ = true;
2502 this.secondaryConn_.markConnectionHealthy();
2503 this.proceedWithUpgrade_();
2504 }
2505 else {
2506 // Send a ping to make sure the connection is healthy.
2507 this.log_('sending ping on secondary.');
2508 this.secondaryConn_.send({ t: 'c', d: { t: PING, d: {} } });
2509 }
2510 };
2511 Connection.prototype.proceedWithUpgrade_ = function () {
2512 // tell this connection to consider itself open
2513 this.secondaryConn_.start();
2514 // send ack
2515 this.log_('sending client ack on secondary');
2516 this.secondaryConn_.send({ t: 'c', d: { t: SWITCH_ACK, d: {} } });
2517 // send end packet on primary transport, switch to sending on this one
2518 // can receive on this one, buffer responses until end received on primary transport
2519 this.log_('Ending transmission on primary');
2520 this.conn_.send({ t: 'c', d: { t: END_TRANSMISSION, d: {} } });
2521 this.tx_ = this.secondaryConn_;
2522 this.tryCleanupConnection();
2523 };
2524 Connection.prototype.onPrimaryMessageReceived_ = function (parsedData) {
2525 // Must refer to parsedData properties in quotes, so closure doesn't touch them.
2526 var layer = requireKey('t', parsedData);
2527 var data = requireKey('d', parsedData);
2528 if (layer === 'c') {
2529 this.onControl_(data);
2530 }
2531 else if (layer === 'd') {
2532 this.onDataMessage_(data);
2533 }
2534 };
2535 Connection.prototype.onDataMessage_ = function (message) {
2536 this.onPrimaryResponse_();
2537 // We don't do anything with data messages, just kick them up a level
2538 this.onMessage_(message);
2539 };
2540 Connection.prototype.onPrimaryResponse_ = function () {
2541 if (!this.isHealthy_) {
2542 this.primaryResponsesRequired_--;
2543 if (this.primaryResponsesRequired_ <= 0) {
2544 this.log_('Primary connection is healthy.');
2545 this.isHealthy_ = true;
2546 this.conn_.markConnectionHealthy();
2547 }
2548 }
2549 };
2550 Connection.prototype.onControl_ = function (controlData) {
2551 var cmd = requireKey(MESSAGE_TYPE, controlData);
2552 if (MESSAGE_DATA in controlData) {
2553 var payload = controlData[MESSAGE_DATA];
2554 if (cmd === SERVER_HELLO) {
2555 this.onHandshake_(payload);
2556 }
2557 else if (cmd === END_TRANSMISSION) {
2558 this.log_('recvd end transmission on primary');
2559 this.rx_ = this.secondaryConn_;
2560 for (var i = 0; i < this.pendingDataMessages.length; ++i) {
2561 this.onDataMessage_(this.pendingDataMessages[i]);
2562 }
2563 this.pendingDataMessages = [];
2564 this.tryCleanupConnection();
2565 }
2566 else if (cmd === CONTROL_SHUTDOWN) {
2567 // This was previously the 'onKill' callback passed to the lower-level connection
2568 // payload in this case is the reason for the shutdown. Generally a human-readable error
2569 this.onConnectionShutdown_(payload);
2570 }
2571 else if (cmd === CONTROL_RESET) {
2572 // payload in this case is the host we should contact
2573 this.onReset_(payload);
2574 }
2575 else if (cmd === CONTROL_ERROR) {
2576 error('Server Error: ' + payload);
2577 }
2578 else if (cmd === CONTROL_PONG) {
2579 this.log_('got pong on primary.');
2580 this.onPrimaryResponse_();
2581 this.sendPingOnPrimaryIfNecessary_();
2582 }
2583 else {
2584 error('Unknown control packet command: ' + cmd);
2585 }
2586 }
2587 };
2588 /**
2589 * @param handshake - The handshake data returned from the server
2590 */
2591 Connection.prototype.onHandshake_ = function (handshake) {
2592 var timestamp = handshake.ts;
2593 var version = handshake.v;
2594 var host = handshake.h;
2595 this.sessionId = handshake.s;
2596 this.repoInfo_.host = host;
2597 // if we've already closed the connection, then don't bother trying to progress further
2598 if (this.state_ === 0 /* CONNECTING */) {
2599 this.conn_.start();
2600 this.onConnectionEstablished_(this.conn_, timestamp);
2601 if (PROTOCOL_VERSION !== version) {
2602 warn('Protocol version mismatch detected');
2603 }
2604 // TODO: do we want to upgrade? when? maybe a delay?
2605 this.tryStartUpgrade_();
2606 }
2607 };
2608 Connection.prototype.tryStartUpgrade_ = function () {
2609 var conn = this.transportManager_.upgradeTransport();
2610 if (conn) {
2611 this.startUpgrade_(conn);
2612 }
2613 };
2614 Connection.prototype.startUpgrade_ = function (conn) {
2615 var _this = this;
2616 this.secondaryConn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, this.sessionId);
2617 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2618 // can consider the transport healthy.
2619 this.secondaryResponsesRequired_ =
2620 conn['responsesRequiredToBeHealthy'] || 0;
2621 var onMessage = this.connReceiver_(this.secondaryConn_);
2622 var onDisconnect = this.disconnReceiver_(this.secondaryConn_);
2623 this.secondaryConn_.open(onMessage, onDisconnect);
2624 // If we haven't successfully upgraded after UPGRADE_TIMEOUT, give up and kill the secondary.
2625 setTimeoutNonBlocking(function () {
2626 if (_this.secondaryConn_) {
2627 _this.log_('Timed out trying to upgrade.');
2628 _this.secondaryConn_.close();
2629 }
2630 }, Math.floor(UPGRADE_TIMEOUT));
2631 };
2632 Connection.prototype.onReset_ = function (host) {
2633 this.log_('Reset packet received. New host: ' + host);
2634 this.repoInfo_.host = host;
2635 // TODO: if we're already "connected", we need to trigger a disconnect at the next layer up.
2636 // We don't currently support resets after the connection has already been established
2637 if (this.state_ === 1 /* CONNECTED */) {
2638 this.close();
2639 }
2640 else {
2641 // Close whatever connections we have open and start again.
2642 this.closeConnections_();
2643 this.start_();
2644 }
2645 };
2646 Connection.prototype.onConnectionEstablished_ = function (conn, timestamp) {
2647 var _this = this;
2648 this.log_('Realtime connection established.');
2649 this.conn_ = conn;
2650 this.state_ = 1 /* CONNECTED */;
2651 if (this.onReady_) {
2652 this.onReady_(timestamp, this.sessionId);
2653 this.onReady_ = null;
2654 }
2655 // If after 5 seconds we haven't sent enough requests to the server to get the connection healthy,
2656 // send some pings.
2657 if (this.primaryResponsesRequired_ === 0) {
2658 this.log_('Primary connection is healthy.');
2659 this.isHealthy_ = true;
2660 }
2661 else {
2662 setTimeoutNonBlocking(function () {
2663 _this.sendPingOnPrimaryIfNecessary_();
2664 }, Math.floor(DELAY_BEFORE_SENDING_EXTRA_REQUESTS));
2665 }
2666 };
2667 Connection.prototype.sendPingOnPrimaryIfNecessary_ = function () {
2668 // If the connection isn't considered healthy yet, we'll send a noop ping packet request.
2669 if (!this.isHealthy_ && this.state_ === 1 /* CONNECTED */) {
2670 this.log_('sending ping on primary.');
2671 this.sendData_({ t: 'c', d: { t: PING, d: {} } });
2672 }
2673 };
2674 Connection.prototype.onSecondaryConnectionLost_ = function () {
2675 var conn = this.secondaryConn_;
2676 this.secondaryConn_ = null;
2677 if (this.tx_ === conn || this.rx_ === conn) {
2678 // we are relying on this connection already in some capacity. Therefore, a failure is real
2679 this.close();
2680 }
2681 };
2682 /**
2683 * @param everConnected - Whether or not the connection ever reached a server. Used to determine if
2684 * we should flush the host cache
2685 */
2686 Connection.prototype.onConnectionLost_ = function (everConnected) {
2687 this.conn_ = null;
2688 // NOTE: IF you're seeing a Firefox error for this line, I think it might be because it's getting
2689 // called on window close and RealtimeState.CONNECTING is no longer defined. Just a guess.
2690 if (!everConnected && this.state_ === 0 /* CONNECTING */) {
2691 this.log_('Realtime connection failed.');
2692 // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
2693 if (this.repoInfo_.isCacheableHost()) {
2694 PersistentStorage.remove('host:' + this.repoInfo_.host);
2695 // reset the internal host to what we would show the user, i.e. <ns>.firebaseio.com
2696 this.repoInfo_.internalHost = this.repoInfo_.host;
2697 }
2698 }
2699 else if (this.state_ === 1 /* CONNECTED */) {
2700 this.log_('Realtime connection lost.');
2701 }
2702 this.close();
2703 };
2704 Connection.prototype.onConnectionShutdown_ = function (reason) {
2705 this.log_('Connection shutdown command received. Shutting down...');
2706 if (this.onKill_) {
2707 this.onKill_(reason);
2708 this.onKill_ = null;
2709 }
2710 // We intentionally don't want to fire onDisconnect (kill is a different case),
2711 // so clear the callback.
2712 this.onDisconnect_ = null;
2713 this.close();
2714 };
2715 Connection.prototype.sendData_ = function (data) {
2716 if (this.state_ !== 1 /* CONNECTED */) {
2717 throw 'Connection is not connected';
2718 }
2719 else {
2720 this.tx_.send(data);
2721 }
2722 };
2723 /**
2724 * Cleans up this connection, calling the appropriate callbacks
2725 */
2726 Connection.prototype.close = function () {
2727 if (this.state_ !== 2 /* DISCONNECTED */) {
2728 this.log_('Closing realtime connection.');
2729 this.state_ = 2 /* DISCONNECTED */;
2730 this.closeConnections_();
2731 if (this.onDisconnect_) {
2732 this.onDisconnect_();
2733 this.onDisconnect_ = null;
2734 }
2735 }
2736 };
2737 Connection.prototype.closeConnections_ = function () {
2738 this.log_('Shutting down all connections');
2739 if (this.conn_) {
2740 this.conn_.close();
2741 this.conn_ = null;
2742 }
2743 if (this.secondaryConn_) {
2744 this.secondaryConn_.close();
2745 this.secondaryConn_ = null;
2746 }
2747 if (this.healthyTimeout_) {
2748 clearTimeout(this.healthyTimeout_);
2749 this.healthyTimeout_ = null;
2750 }
2751 };
2752 return Connection;
2753}());
2754
2755/**
2756 * @license
2757 * Copyright 2017 Google LLC
2758 *
2759 * Licensed under the Apache License, Version 2.0 (the "License");
2760 * you may not use this file except in compliance with the License.
2761 * You may obtain a copy of the License at
2762 *
2763 * http://www.apache.org/licenses/LICENSE-2.0
2764 *
2765 * Unless required by applicable law or agreed to in writing, software
2766 * distributed under the License is distributed on an "AS IS" BASIS,
2767 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2768 * See the License for the specific language governing permissions and
2769 * limitations under the License.
2770 */
2771/**
2772 * Interface defining the set of actions that can be performed against the Firebase server
2773 * (basically corresponds to our wire protocol).
2774 *
2775 * @interface
2776 */
2777var ServerActions = /** @class */ (function () {
2778 function ServerActions() {
2779 }
2780 ServerActions.prototype.put = function (pathString, data, onComplete, hash) { };
2781 ServerActions.prototype.merge = function (pathString, data, onComplete, hash) { };
2782 /**
2783 * Refreshes the auth token for the current connection.
2784 * @param token - The authentication token
2785 */
2786 ServerActions.prototype.refreshAuthToken = function (token) { };
2787 /**
2788 * Refreshes the app check token for the current connection.
2789 * @param token The app check token
2790 */
2791 ServerActions.prototype.refreshAppCheckToken = function (token) { };
2792 ServerActions.prototype.onDisconnectPut = function (pathString, data, onComplete) { };
2793 ServerActions.prototype.onDisconnectMerge = function (pathString, data, onComplete) { };
2794 ServerActions.prototype.onDisconnectCancel = function (pathString, onComplete) { };
2795 ServerActions.prototype.reportStats = function (stats) { };
2796 return ServerActions;
2797}());
2798
2799/**
2800 * @license
2801 * Copyright 2017 Google LLC
2802 *
2803 * Licensed under the Apache License, Version 2.0 (the "License");
2804 * you may not use this file except in compliance with the License.
2805 * You may obtain a copy of the License at
2806 *
2807 * http://www.apache.org/licenses/LICENSE-2.0
2808 *
2809 * Unless required by applicable law or agreed to in writing, software
2810 * distributed under the License is distributed on an "AS IS" BASIS,
2811 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2812 * See the License for the specific language governing permissions and
2813 * limitations under the License.
2814 */
2815/**
2816 * Base class to be used if you want to emit events. Call the constructor with
2817 * the set of allowed event names.
2818 */
2819var EventEmitter = /** @class */ (function () {
2820 function EventEmitter(allowedEvents_) {
2821 this.allowedEvents_ = allowedEvents_;
2822 this.listeners_ = {};
2823 assert(Array.isArray(allowedEvents_) && allowedEvents_.length > 0, 'Requires a non-empty array');
2824 }
2825 /**
2826 * To be called by derived classes to trigger events.
2827 */
2828 EventEmitter.prototype.trigger = function (eventType) {
2829 var varArgs = [];
2830 for (var _i = 1; _i < arguments.length; _i++) {
2831 varArgs[_i - 1] = arguments[_i];
2832 }
2833 if (Array.isArray(this.listeners_[eventType])) {
2834 // Clone the list, since callbacks could add/remove listeners.
2835 var listeners = __spreadArray([], __read(this.listeners_[eventType]));
2836 for (var i = 0; i < listeners.length; i++) {
2837 listeners[i].callback.apply(listeners[i].context, varArgs);
2838 }
2839 }
2840 };
2841 EventEmitter.prototype.on = function (eventType, callback, context) {
2842 this.validateEventType_(eventType);
2843 this.listeners_[eventType] = this.listeners_[eventType] || [];
2844 this.listeners_[eventType].push({ callback: callback, context: context });
2845 var eventData = this.getInitialEvent(eventType);
2846 if (eventData) {
2847 callback.apply(context, eventData);
2848 }
2849 };
2850 EventEmitter.prototype.off = function (eventType, callback, context) {
2851 this.validateEventType_(eventType);
2852 var listeners = this.listeners_[eventType] || [];
2853 for (var i = 0; i < listeners.length; i++) {
2854 if (listeners[i].callback === callback &&
2855 (!context || context === listeners[i].context)) {
2856 listeners.splice(i, 1);
2857 return;
2858 }
2859 }
2860 };
2861 EventEmitter.prototype.validateEventType_ = function (eventType) {
2862 assert(this.allowedEvents_.find(function (et) {
2863 return et === eventType;
2864 }), 'Unknown event: ' + eventType);
2865 };
2866 return EventEmitter;
2867}());
2868
2869/**
2870 * @license
2871 * Copyright 2017 Google LLC
2872 *
2873 * Licensed under the Apache License, Version 2.0 (the "License");
2874 * you may not use this file except in compliance with the License.
2875 * You may obtain a copy of the License at
2876 *
2877 * http://www.apache.org/licenses/LICENSE-2.0
2878 *
2879 * Unless required by applicable law or agreed to in writing, software
2880 * distributed under the License is distributed on an "AS IS" BASIS,
2881 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2882 * See the License for the specific language governing permissions and
2883 * limitations under the License.
2884 */
2885/**
2886 * Monitors online state (as reported by window.online/offline events).
2887 *
2888 * The expectation is that this could have many false positives (thinks we are online
2889 * when we're not), but no false negatives. So we can safely use it to determine when
2890 * we definitely cannot reach the internet.
2891 */
2892var OnlineMonitor = /** @class */ (function (_super) {
2893 __extends(OnlineMonitor, _super);
2894 function OnlineMonitor() {
2895 var _this = _super.call(this, ['online']) || this;
2896 _this.online_ = true;
2897 // We've had repeated complaints that Cordova apps can get stuck "offline", e.g.
2898 // https://forum.ionicframework.com/t/firebase-connection-is-lost-and-never-come-back/43810
2899 // It would seem that the 'online' event does not always fire consistently. So we disable it
2900 // for Cordova.
2901 if (typeof window !== 'undefined' &&
2902 typeof window.addEventListener !== 'undefined' &&
2903 !isMobileCordova()) {
2904 window.addEventListener('online', function () {
2905 if (!_this.online_) {
2906 _this.online_ = true;
2907 _this.trigger('online', true);
2908 }
2909 }, false);
2910 window.addEventListener('offline', function () {
2911 if (_this.online_) {
2912 _this.online_ = false;
2913 _this.trigger('online', false);
2914 }
2915 }, false);
2916 }
2917 return _this;
2918 }
2919 OnlineMonitor.getInstance = function () {
2920 return new OnlineMonitor();
2921 };
2922 OnlineMonitor.prototype.getInitialEvent = function (eventType) {
2923 assert(eventType === 'online', 'Unknown event type: ' + eventType);
2924 return [this.online_];
2925 };
2926 OnlineMonitor.prototype.currentlyOnline = function () {
2927 return this.online_;
2928 };
2929 return OnlineMonitor;
2930}(EventEmitter));
2931
2932/**
2933 * @license
2934 * Copyright 2017 Google LLC
2935 *
2936 * Licensed under the Apache License, Version 2.0 (the "License");
2937 * you may not use this file except in compliance with the License.
2938 * You may obtain a copy of the License at
2939 *
2940 * http://www.apache.org/licenses/LICENSE-2.0
2941 *
2942 * Unless required by applicable law or agreed to in writing, software
2943 * distributed under the License is distributed on an "AS IS" BASIS,
2944 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2945 * See the License for the specific language governing permissions and
2946 * limitations under the License.
2947 */
2948/** Maximum key depth. */
2949var MAX_PATH_DEPTH = 32;
2950/** Maximum number of (UTF8) bytes in a Firebase path. */
2951var MAX_PATH_LENGTH_BYTES = 768;
2952/**
2953 * An immutable object representing a parsed path. It's immutable so that you
2954 * can pass them around to other functions without worrying about them changing
2955 * it.
2956 */
2957var Path = /** @class */ (function () {
2958 /**
2959 * @param pathOrString - Path string to parse, or another path, or the raw
2960 * tokens array
2961 */
2962 function Path(pathOrString, pieceNum) {
2963 if (pieceNum === void 0) {
2964 this.pieces_ = pathOrString.split('/');
2965 // Remove empty pieces.
2966 var copyTo = 0;
2967 for (var i = 0; i < this.pieces_.length; i++) {
2968 if (this.pieces_[i].length > 0) {
2969 this.pieces_[copyTo] = this.pieces_[i];
2970 copyTo++;
2971 }
2972 }
2973 this.pieces_.length = copyTo;
2974 this.pieceNum_ = 0;
2975 }
2976 else {
2977 this.pieces_ = pathOrString;
2978 this.pieceNum_ = pieceNum;
2979 }
2980 }
2981 Path.prototype.toString = function () {
2982 var pathString = '';
2983 for (var i = this.pieceNum_; i < this.pieces_.length; i++) {
2984 if (this.pieces_[i] !== '') {
2985 pathString += '/' + this.pieces_[i];
2986 }
2987 }
2988 return pathString || '/';
2989 };
2990 return Path;
2991}());
2992function newEmptyPath() {
2993 return new Path('');
2994}
2995function pathGetFront(path) {
2996 if (path.pieceNum_ >= path.pieces_.length) {
2997 return null;
2998 }
2999 return path.pieces_[path.pieceNum_];
3000}
3001/**
3002 * @returns The number of segments in this path
3003 */
3004function pathGetLength(path) {
3005 return path.pieces_.length - path.pieceNum_;
3006}
3007function pathPopFront(path) {
3008 var pieceNum = path.pieceNum_;
3009 if (pieceNum < path.pieces_.length) {
3010 pieceNum++;
3011 }
3012 return new Path(path.pieces_, pieceNum);
3013}
3014function pathGetBack(path) {
3015 if (path.pieceNum_ < path.pieces_.length) {
3016 return path.pieces_[path.pieces_.length - 1];
3017 }
3018 return null;
3019}
3020function pathToUrlEncodedString(path) {
3021 var pathString = '';
3022 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3023 if (path.pieces_[i] !== '') {
3024 pathString += '/' + encodeURIComponent(String(path.pieces_[i]));
3025 }
3026 }
3027 return pathString || '/';
3028}
3029/**
3030 * Shallow copy of the parts of the path.
3031 *
3032 */
3033function pathSlice(path, begin) {
3034 if (begin === void 0) { begin = 0; }
3035 return path.pieces_.slice(path.pieceNum_ + begin);
3036}
3037function pathParent(path) {
3038 if (path.pieceNum_ >= path.pieces_.length) {
3039 return null;
3040 }
3041 var pieces = [];
3042 for (var i = path.pieceNum_; i < path.pieces_.length - 1; i++) {
3043 pieces.push(path.pieces_[i]);
3044 }
3045 return new Path(pieces, 0);
3046}
3047function pathChild(path, childPathObj) {
3048 var pieces = [];
3049 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3050 pieces.push(path.pieces_[i]);
3051 }
3052 if (childPathObj instanceof Path) {
3053 for (var i = childPathObj.pieceNum_; i < childPathObj.pieces_.length; i++) {
3054 pieces.push(childPathObj.pieces_[i]);
3055 }
3056 }
3057 else {
3058 var childPieces = childPathObj.split('/');
3059 for (var i = 0; i < childPieces.length; i++) {
3060 if (childPieces[i].length > 0) {
3061 pieces.push(childPieces[i]);
3062 }
3063 }
3064 }
3065 return new Path(pieces, 0);
3066}
3067/**
3068 * @returns True if there are no segments in this path
3069 */
3070function pathIsEmpty(path) {
3071 return path.pieceNum_ >= path.pieces_.length;
3072}
3073/**
3074 * @returns The path from outerPath to innerPath
3075 */
3076function newRelativePath(outerPath, innerPath) {
3077 var outer = pathGetFront(outerPath), inner = pathGetFront(innerPath);
3078 if (outer === null) {
3079 return innerPath;
3080 }
3081 else if (outer === inner) {
3082 return newRelativePath(pathPopFront(outerPath), pathPopFront(innerPath));
3083 }
3084 else {
3085 throw new Error('INTERNAL ERROR: innerPath (' +
3086 innerPath +
3087 ') is not within ' +
3088 'outerPath (' +
3089 outerPath +
3090 ')');
3091 }
3092}
3093/**
3094 * @returns -1, 0, 1 if left is less, equal, or greater than the right.
3095 */
3096function pathCompare(left, right) {
3097 var leftKeys = pathSlice(left, 0);
3098 var rightKeys = pathSlice(right, 0);
3099 for (var i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
3100 var cmp = nameCompare(leftKeys[i], rightKeys[i]);
3101 if (cmp !== 0) {
3102 return cmp;
3103 }
3104 }
3105 if (leftKeys.length === rightKeys.length) {
3106 return 0;
3107 }
3108 return leftKeys.length < rightKeys.length ? -1 : 1;
3109}
3110/**
3111 * @returns true if paths are the same.
3112 */
3113function pathEquals(path, other) {
3114 if (pathGetLength(path) !== pathGetLength(other)) {
3115 return false;
3116 }
3117 for (var i = path.pieceNum_, j = other.pieceNum_; i <= path.pieces_.length; i++, j++) {
3118 if (path.pieces_[i] !== other.pieces_[j]) {
3119 return false;
3120 }
3121 }
3122 return true;
3123}
3124/**
3125 * @returns True if this path is a parent of (or the same as) other
3126 */
3127function pathContains(path, other) {
3128 var i = path.pieceNum_;
3129 var j = other.pieceNum_;
3130 if (pathGetLength(path) > pathGetLength(other)) {
3131 return false;
3132 }
3133 while (i < path.pieces_.length) {
3134 if (path.pieces_[i] !== other.pieces_[j]) {
3135 return false;
3136 }
3137 ++i;
3138 ++j;
3139 }
3140 return true;
3141}
3142/**
3143 * Dynamic (mutable) path used to count path lengths.
3144 *
3145 * This class is used to efficiently check paths for valid
3146 * length (in UTF8 bytes) and depth (used in path validation).
3147 *
3148 * Throws Error exception if path is ever invalid.
3149 *
3150 * The definition of a path always begins with '/'.
3151 */
3152var ValidationPath = /** @class */ (function () {
3153 /**
3154 * @param path - Initial Path.
3155 * @param errorPrefix_ - Prefix for any error messages.
3156 */
3157 function ValidationPath(path, errorPrefix_) {
3158 this.errorPrefix_ = errorPrefix_;
3159 this.parts_ = pathSlice(path, 0);
3160 /** Initialize to number of '/' chars needed in path. */
3161 this.byteLength_ = Math.max(1, this.parts_.length);
3162 for (var i = 0; i < this.parts_.length; i++) {
3163 this.byteLength_ += stringLength(this.parts_[i]);
3164 }
3165 validationPathCheckValid(this);
3166 }
3167 return ValidationPath;
3168}());
3169function validationPathPush(validationPath, child) {
3170 // Count the needed '/'
3171 if (validationPath.parts_.length > 0) {
3172 validationPath.byteLength_ += 1;
3173 }
3174 validationPath.parts_.push(child);
3175 validationPath.byteLength_ += stringLength(child);
3176 validationPathCheckValid(validationPath);
3177}
3178function validationPathPop(validationPath) {
3179 var last = validationPath.parts_.pop();
3180 validationPath.byteLength_ -= stringLength(last);
3181 // Un-count the previous '/'
3182 if (validationPath.parts_.length > 0) {
3183 validationPath.byteLength_ -= 1;
3184 }
3185}
3186function validationPathCheckValid(validationPath) {
3187 if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
3188 throw new Error(validationPath.errorPrefix_ +
3189 'has a key path longer than ' +
3190 MAX_PATH_LENGTH_BYTES +
3191 ' bytes (' +
3192 validationPath.byteLength_ +
3193 ').');
3194 }
3195 if (validationPath.parts_.length > MAX_PATH_DEPTH) {
3196 throw new Error(validationPath.errorPrefix_ +
3197 'path specified exceeds the maximum depth that can be written (' +
3198 MAX_PATH_DEPTH +
3199 ') or object contains a cycle ' +
3200 validationPathToErrorString(validationPath));
3201 }
3202}
3203/**
3204 * String for use in error messages - uses '.' notation for path.
3205 */
3206function validationPathToErrorString(validationPath) {
3207 if (validationPath.parts_.length === 0) {
3208 return '';
3209 }
3210 return "in property '" + validationPath.parts_.join('.') + "'";
3211}
3212
3213/**
3214 * @license
3215 * Copyright 2017 Google LLC
3216 *
3217 * Licensed under the Apache License, Version 2.0 (the "License");
3218 * you may not use this file except in compliance with the License.
3219 * You may obtain a copy of the License at
3220 *
3221 * http://www.apache.org/licenses/LICENSE-2.0
3222 *
3223 * Unless required by applicable law or agreed to in writing, software
3224 * distributed under the License is distributed on an "AS IS" BASIS,
3225 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3226 * See the License for the specific language governing permissions and
3227 * limitations under the License.
3228 */
3229var VisibilityMonitor = /** @class */ (function (_super) {
3230 __extends(VisibilityMonitor, _super);
3231 function VisibilityMonitor() {
3232 var _this = _super.call(this, ['visible']) || this;
3233 var hidden;
3234 var visibilityChange;
3235 if (typeof document !== 'undefined' &&
3236 typeof document.addEventListener !== 'undefined') {
3237 if (typeof document['hidden'] !== 'undefined') {
3238 // Opera 12.10 and Firefox 18 and later support
3239 visibilityChange = 'visibilitychange';
3240 hidden = 'hidden';
3241 }
3242 else if (typeof document['mozHidden'] !== 'undefined') {
3243 visibilityChange = 'mozvisibilitychange';
3244 hidden = 'mozHidden';
3245 }
3246 else if (typeof document['msHidden'] !== 'undefined') {
3247 visibilityChange = 'msvisibilitychange';
3248 hidden = 'msHidden';
3249 }
3250 else if (typeof document['webkitHidden'] !== 'undefined') {
3251 visibilityChange = 'webkitvisibilitychange';
3252 hidden = 'webkitHidden';
3253 }
3254 }
3255 // Initially, we always assume we are visible. This ensures that in browsers
3256 // without page visibility support or in cases where we are never visible
3257 // (e.g. chrome extension), we act as if we are visible, i.e. don't delay
3258 // reconnects
3259 _this.visible_ = true;
3260 if (visibilityChange) {
3261 document.addEventListener(visibilityChange, function () {
3262 var visible = !document[hidden];
3263 if (visible !== _this.visible_) {
3264 _this.visible_ = visible;
3265 _this.trigger('visible', visible);
3266 }
3267 }, false);
3268 }
3269 return _this;
3270 }
3271 VisibilityMonitor.getInstance = function () {
3272 return new VisibilityMonitor();
3273 };
3274 VisibilityMonitor.prototype.getInitialEvent = function (eventType) {
3275 assert(eventType === 'visible', 'Unknown event type: ' + eventType);
3276 return [this.visible_];
3277 };
3278 return VisibilityMonitor;
3279}(EventEmitter));
3280
3281/**
3282 * @license
3283 * Copyright 2017 Google LLC
3284 *
3285 * Licensed under the Apache License, Version 2.0 (the "License");
3286 * you may not use this file except in compliance with the License.
3287 * You may obtain a copy of the License at
3288 *
3289 * http://www.apache.org/licenses/LICENSE-2.0
3290 *
3291 * Unless required by applicable law or agreed to in writing, software
3292 * distributed under the License is distributed on an "AS IS" BASIS,
3293 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3294 * See the License for the specific language governing permissions and
3295 * limitations under the License.
3296 */
3297var RECONNECT_MIN_DELAY = 1000;
3298var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
3299var GET_CONNECT_TIMEOUT = 3 * 1000;
3300var RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
3301var RECONNECT_DELAY_MULTIPLIER = 1.3;
3302var RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
3303var SERVER_KILL_INTERRUPT_REASON = 'server_kill';
3304// If auth fails repeatedly, we'll assume something is wrong and log a warning / back off.
3305var INVALID_TOKEN_THRESHOLD = 3;
3306/**
3307 * Firebase connection. Abstracts wire protocol and handles reconnecting.
3308 *
3309 * NOTE: All JSON objects sent to the realtime connection must have property names enclosed
3310 * in quotes to make sure the closure compiler does not minify them.
3311 */
3312var PersistentConnection = /** @class */ (function (_super) {
3313 __extends(PersistentConnection, _super);
3314 /**
3315 * @param repoInfo_ - Data about the namespace we are connecting to
3316 * @param applicationId_ - The Firebase App ID for this project
3317 * @param onDataUpdate_ - A callback for new data from the server
3318 */
3319 function PersistentConnection(repoInfo_, applicationId_, onDataUpdate_, onConnectStatus_, onServerInfoUpdate_, authTokenProvider_, appCheckTokenProvider_, authOverride_) {
3320 var _this = _super.call(this) || this;
3321 _this.repoInfo_ = repoInfo_;
3322 _this.applicationId_ = applicationId_;
3323 _this.onDataUpdate_ = onDataUpdate_;
3324 _this.onConnectStatus_ = onConnectStatus_;
3325 _this.onServerInfoUpdate_ = onServerInfoUpdate_;
3326 _this.authTokenProvider_ = authTokenProvider_;
3327 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
3328 _this.authOverride_ = authOverride_;
3329 // Used for diagnostic logging.
3330 _this.id = PersistentConnection.nextPersistentConnectionId_++;
3331 _this.log_ = logWrapper('p:' + _this.id + ':');
3332 _this.interruptReasons_ = {};
3333 _this.listens = new Map();
3334 _this.outstandingPuts_ = [];
3335 _this.outstandingGets_ = [];
3336 _this.outstandingPutCount_ = 0;
3337 _this.outstandingGetCount_ = 0;
3338 _this.onDisconnectRequestQueue_ = [];
3339 _this.connected_ = false;
3340 _this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3341 _this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_DEFAULT;
3342 _this.securityDebugCallback_ = null;
3343 _this.lastSessionId = null;
3344 _this.establishConnectionTimer_ = null;
3345 _this.visible_ = false;
3346 // Before we get connected, we keep a queue of pending messages to send.
3347 _this.requestCBHash_ = {};
3348 _this.requestNumber_ = 0;
3349 _this.realtime_ = null;
3350 _this.authToken_ = null;
3351 _this.appCheckToken_ = null;
3352 _this.forceTokenRefresh_ = false;
3353 _this.invalidAuthTokenCount_ = 0;
3354 _this.invalidAppCheckTokenCount_ = 0;
3355 _this.firstConnection_ = true;
3356 _this.lastConnectionAttemptTime_ = null;
3357 _this.lastConnectionEstablishedTime_ = null;
3358 if (authOverride_ && !isNodeSdk()) {
3359 throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
3360 }
3361 VisibilityMonitor.getInstance().on('visible', _this.onVisible_, _this);
3362 if (repoInfo_.host.indexOf('fblocal') === -1) {
3363 OnlineMonitor.getInstance().on('online', _this.onOnline_, _this);
3364 }
3365 return _this;
3366 }
3367 PersistentConnection.prototype.sendRequest = function (action, body, onResponse) {
3368 var curReqNum = ++this.requestNumber_;
3369 var msg = { r: curReqNum, a: action, b: body };
3370 this.log_(stringify(msg));
3371 assert(this.connected_, "sendRequest call when we're not connected not allowed.");
3372 this.realtime_.sendRequest(msg);
3373 if (onResponse) {
3374 this.requestCBHash_[curReqNum] = onResponse;
3375 }
3376 };
3377 PersistentConnection.prototype.get = function (query) {
3378 var _this = this;
3379 this.initConnection_();
3380 var deferred = new Deferred();
3381 var request = {
3382 p: query._path.toString(),
3383 q: query._queryObject
3384 };
3385 var outstandingGet = {
3386 action: 'g',
3387 request: request,
3388 onComplete: function (message) {
3389 var payload = message['d'];
3390 if (message['s'] === 'ok') {
3391 deferred.resolve(payload);
3392 }
3393 else {
3394 deferred.reject(payload);
3395 }
3396 }
3397 };
3398 this.outstandingGets_.push(outstandingGet);
3399 this.outstandingGetCount_++;
3400 var index = this.outstandingGets_.length - 1;
3401 if (!this.connected_) {
3402 setTimeout(function () {
3403 var get = _this.outstandingGets_[index];
3404 if (get === undefined || outstandingGet !== get) {
3405 return;
3406 }
3407 delete _this.outstandingGets_[index];
3408 _this.outstandingGetCount_--;
3409 if (_this.outstandingGetCount_ === 0) {
3410 _this.outstandingGets_ = [];
3411 }
3412 _this.log_('get ' + index + ' timed out on connection');
3413 deferred.reject(new Error('Client is offline.'));
3414 }, GET_CONNECT_TIMEOUT);
3415 }
3416 if (this.connected_) {
3417 this.sendGet_(index);
3418 }
3419 return deferred.promise;
3420 };
3421 PersistentConnection.prototype.listen = function (query, currentHashFn, tag, onComplete) {
3422 this.initConnection_();
3423 var queryId = query._queryIdentifier;
3424 var pathString = query._path.toString();
3425 this.log_('Listen called for ' + pathString + ' ' + queryId);
3426 if (!this.listens.has(pathString)) {
3427 this.listens.set(pathString, new Map());
3428 }
3429 assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'listen() called for non-default but complete query');
3430 assert(!this.listens.get(pathString).has(queryId), "listen() called twice for same path/queryId.");
3431 var listenSpec = {
3432 onComplete: onComplete,
3433 hashFn: currentHashFn,
3434 query: query,
3435 tag: tag
3436 };
3437 this.listens.get(pathString).set(queryId, listenSpec);
3438 if (this.connected_) {
3439 this.sendListen_(listenSpec);
3440 }
3441 };
3442 PersistentConnection.prototype.sendGet_ = function (index) {
3443 var _this = this;
3444 var get = this.outstandingGets_[index];
3445 this.sendRequest('g', get.request, function (message) {
3446 delete _this.outstandingGets_[index];
3447 _this.outstandingGetCount_--;
3448 if (_this.outstandingGetCount_ === 0) {
3449 _this.outstandingGets_ = [];
3450 }
3451 if (get.onComplete) {
3452 get.onComplete(message);
3453 }
3454 });
3455 };
3456 PersistentConnection.prototype.sendListen_ = function (listenSpec) {
3457 var _this = this;
3458 var query = listenSpec.query;
3459 var pathString = query._path.toString();
3460 var queryId = query._queryIdentifier;
3461 this.log_('Listen on ' + pathString + ' for ' + queryId);
3462 var req = { /*path*/ p: pathString };
3463 var action = 'q';
3464 // Only bother to send query if it's non-default.
3465 if (listenSpec.tag) {
3466 req['q'] = query._queryObject;
3467 req['t'] = listenSpec.tag;
3468 }
3469 req[ /*hash*/'h'] = listenSpec.hashFn();
3470 this.sendRequest(action, req, function (message) {
3471 var payload = message[ /*data*/'d'];
3472 var status = message[ /*status*/'s'];
3473 // print warnings in any case...
3474 PersistentConnection.warnOnListenWarnings_(payload, query);
3475 var currentListenSpec = _this.listens.get(pathString) &&
3476 _this.listens.get(pathString).get(queryId);
3477 // only trigger actions if the listen hasn't been removed and readded
3478 if (currentListenSpec === listenSpec) {
3479 _this.log_('listen response', message);
3480 if (status !== 'ok') {
3481 _this.removeListen_(pathString, queryId);
3482 }
3483 if (listenSpec.onComplete) {
3484 listenSpec.onComplete(status, payload);
3485 }
3486 }
3487 });
3488 };
3489 PersistentConnection.warnOnListenWarnings_ = function (payload, query) {
3490 if (payload && typeof payload === 'object' && contains(payload, 'w')) {
3491 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3492 var warnings = safeGet(payload, 'w');
3493 if (Array.isArray(warnings) && ~warnings.indexOf('no_index')) {
3494 var indexSpec = '".indexOn": "' + query._queryParams.getIndex().toString() + '"';
3495 var indexPath = query._path.toString();
3496 warn("Using an unspecified index. Your data will be downloaded and " +
3497 ("filtered on the client. Consider adding " + indexSpec + " at ") +
3498 (indexPath + " to your security rules for better performance."));
3499 }
3500 }
3501 };
3502 PersistentConnection.prototype.refreshAuthToken = function (token) {
3503 this.authToken_ = token;
3504 this.log_('Auth token refreshed');
3505 if (this.authToken_) {
3506 this.tryAuth();
3507 }
3508 else {
3509 //If we're connected we want to let the server know to unauthenticate us. If we're not connected, simply delete
3510 //the credential so we dont become authenticated next time we connect.
3511 if (this.connected_) {
3512 this.sendRequest('unauth', {}, function () { });
3513 }
3514 }
3515 this.reduceReconnectDelayIfAdminCredential_(token);
3516 };
3517 PersistentConnection.prototype.reduceReconnectDelayIfAdminCredential_ = function (credential) {
3518 // NOTE: This isn't intended to be bulletproof (a malicious developer can always just modify the client).
3519 // Additionally, we don't bother resetting the max delay back to the default if auth fails / expires.
3520 var isFirebaseSecret = credential && credential.length === 40;
3521 if (isFirebaseSecret || isAdmin(credential)) {
3522 this.log_('Admin auth credential detected. Reducing max reconnect time.');
3523 this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
3524 }
3525 };
3526 PersistentConnection.prototype.refreshAppCheckToken = function (token) {
3527 this.appCheckToken_ = token;
3528 this.log_('App check token refreshed');
3529 if (this.appCheckToken_) {
3530 this.tryAppCheck();
3531 }
3532 else {
3533 //If we're connected we want to let the server know to unauthenticate us.
3534 //If we're not connected, simply delete the credential so we dont become
3535 // authenticated next time we connect.
3536 if (this.connected_) {
3537 this.sendRequest('unappeck', {}, function () { });
3538 }
3539 }
3540 };
3541 /**
3542 * Attempts to authenticate with the given credentials. If the authentication attempt fails, it's triggered like
3543 * a auth revoked (the connection is closed).
3544 */
3545 PersistentConnection.prototype.tryAuth = function () {
3546 var _this = this;
3547 if (this.connected_ && this.authToken_) {
3548 var token_1 = this.authToken_;
3549 var authMethod = isValidFormat(token_1) ? 'auth' : 'gauth';
3550 var requestData = { cred: token_1 };
3551 if (this.authOverride_ === null) {
3552 requestData['noauth'] = true;
3553 }
3554 else if (typeof this.authOverride_ === 'object') {
3555 requestData['authvar'] = this.authOverride_;
3556 }
3557 this.sendRequest(authMethod, requestData, function (res) {
3558 var status = res[ /*status*/'s'];
3559 var data = res[ /*data*/'d'] || 'error';
3560 if (_this.authToken_ === token_1) {
3561 if (status === 'ok') {
3562 _this.invalidAuthTokenCount_ = 0;
3563 }
3564 else {
3565 // Triggers reconnect and force refresh for auth token
3566 _this.onAuthRevoked_(status, data);
3567 }
3568 }
3569 });
3570 }
3571 };
3572 /**
3573 * Attempts to authenticate with the given token. If the authentication
3574 * attempt fails, it's triggered like the token was revoked (the connection is
3575 * closed).
3576 */
3577 PersistentConnection.prototype.tryAppCheck = function () {
3578 var _this = this;
3579 if (this.connected_ && this.appCheckToken_) {
3580 this.sendRequest('appcheck', { 'token': this.appCheckToken_ }, function (res) {
3581 var status = res[ /*status*/'s'];
3582 var data = res[ /*data*/'d'] || 'error';
3583 if (status === 'ok') {
3584 _this.invalidAppCheckTokenCount_ = 0;
3585 }
3586 else {
3587 _this.onAppCheckRevoked_(status, data);
3588 }
3589 });
3590 }
3591 };
3592 /**
3593 * @inheritDoc
3594 */
3595 PersistentConnection.prototype.unlisten = function (query, tag) {
3596 var pathString = query._path.toString();
3597 var queryId = query._queryIdentifier;
3598 this.log_('Unlisten called for ' + pathString + ' ' + queryId);
3599 assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'unlisten() called for non-default but complete query');
3600 var listen = this.removeListen_(pathString, queryId);
3601 if (listen && this.connected_) {
3602 this.sendUnlisten_(pathString, queryId, query._queryObject, tag);
3603 }
3604 };
3605 PersistentConnection.prototype.sendUnlisten_ = function (pathString, queryId, queryObj, tag) {
3606 this.log_('Unlisten on ' + pathString + ' for ' + queryId);
3607 var req = { /*path*/ p: pathString };
3608 var action = 'n';
3609 // Only bother sending queryId if it's non-default.
3610 if (tag) {
3611 req['q'] = queryObj;
3612 req['t'] = tag;
3613 }
3614 this.sendRequest(action, req);
3615 };
3616 PersistentConnection.prototype.onDisconnectPut = function (pathString, data, onComplete) {
3617 this.initConnection_();
3618 if (this.connected_) {
3619 this.sendOnDisconnect_('o', pathString, data, onComplete);
3620 }
3621 else {
3622 this.onDisconnectRequestQueue_.push({
3623 pathString: pathString,
3624 action: 'o',
3625 data: data,
3626 onComplete: onComplete
3627 });
3628 }
3629 };
3630 PersistentConnection.prototype.onDisconnectMerge = function (pathString, data, onComplete) {
3631 this.initConnection_();
3632 if (this.connected_) {
3633 this.sendOnDisconnect_('om', pathString, data, onComplete);
3634 }
3635 else {
3636 this.onDisconnectRequestQueue_.push({
3637 pathString: pathString,
3638 action: 'om',
3639 data: data,
3640 onComplete: onComplete
3641 });
3642 }
3643 };
3644 PersistentConnection.prototype.onDisconnectCancel = function (pathString, onComplete) {
3645 this.initConnection_();
3646 if (this.connected_) {
3647 this.sendOnDisconnect_('oc', pathString, null, onComplete);
3648 }
3649 else {
3650 this.onDisconnectRequestQueue_.push({
3651 pathString: pathString,
3652 action: 'oc',
3653 data: null,
3654 onComplete: onComplete
3655 });
3656 }
3657 };
3658 PersistentConnection.prototype.sendOnDisconnect_ = function (action, pathString, data, onComplete) {
3659 var request = { /*path*/ p: pathString, /*data*/ d: data };
3660 this.log_('onDisconnect ' + action, request);
3661 this.sendRequest(action, request, function (response) {
3662 if (onComplete) {
3663 setTimeout(function () {
3664 onComplete(response[ /*status*/'s'], response[ /* data */'d']);
3665 }, Math.floor(0));
3666 }
3667 });
3668 };
3669 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
3670 this.putInternal('p', pathString, data, onComplete, hash);
3671 };
3672 PersistentConnection.prototype.merge = function (pathString, data, onComplete, hash) {
3673 this.putInternal('m', pathString, data, onComplete, hash);
3674 };
3675 PersistentConnection.prototype.putInternal = function (action, pathString, data, onComplete, hash) {
3676 this.initConnection_();
3677 var request = {
3678 /*path*/ p: pathString,
3679 /*data*/ d: data
3680 };
3681 if (hash !== undefined) {
3682 request[ /*hash*/'h'] = hash;
3683 }
3684 // TODO: Only keep track of the most recent put for a given path?
3685 this.outstandingPuts_.push({
3686 action: action,
3687 request: request,
3688 onComplete: onComplete
3689 });
3690 this.outstandingPutCount_++;
3691 var index = this.outstandingPuts_.length - 1;
3692 if (this.connected_) {
3693 this.sendPut_(index);
3694 }
3695 else {
3696 this.log_('Buffering put: ' + pathString);
3697 }
3698 };
3699 PersistentConnection.prototype.sendPut_ = function (index) {
3700 var _this = this;
3701 var action = this.outstandingPuts_[index].action;
3702 var request = this.outstandingPuts_[index].request;
3703 var onComplete = this.outstandingPuts_[index].onComplete;
3704 this.outstandingPuts_[index].queued = this.connected_;
3705 this.sendRequest(action, request, function (message) {
3706 _this.log_(action + ' response', message);
3707 delete _this.outstandingPuts_[index];
3708 _this.outstandingPutCount_--;
3709 // Clean up array occasionally.
3710 if (_this.outstandingPutCount_ === 0) {
3711 _this.outstandingPuts_ = [];
3712 }
3713 if (onComplete) {
3714 onComplete(message[ /*status*/'s'], message[ /* data */'d']);
3715 }
3716 });
3717 };
3718 PersistentConnection.prototype.reportStats = function (stats) {
3719 var _this = this;
3720 // If we're not connected, we just drop the stats.
3721 if (this.connected_) {
3722 var request = { /*counters*/ c: stats };
3723 this.log_('reportStats', request);
3724 this.sendRequest(/*stats*/ 's', request, function (result) {
3725 var status = result[ /*status*/'s'];
3726 if (status !== 'ok') {
3727 var errorReason = result[ /* data */'d'];
3728 _this.log_('reportStats', 'Error sending stats: ' + errorReason);
3729 }
3730 });
3731 }
3732 };
3733 PersistentConnection.prototype.onDataMessage_ = function (message) {
3734 if ('r' in message) {
3735 // this is a response
3736 this.log_('from server: ' + stringify(message));
3737 var reqNum = message['r'];
3738 var onResponse = this.requestCBHash_[reqNum];
3739 if (onResponse) {
3740 delete this.requestCBHash_[reqNum];
3741 onResponse(message[ /*body*/'b']);
3742 }
3743 }
3744 else if ('error' in message) {
3745 throw 'A server-side error has occurred: ' + message['error'];
3746 }
3747 else if ('a' in message) {
3748 // a and b are action and body, respectively
3749 this.onDataPush_(message['a'], message['b']);
3750 }
3751 };
3752 PersistentConnection.prototype.onDataPush_ = function (action, body) {
3753 this.log_('handleServerMessage', action, body);
3754 if (action === 'd') {
3755 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3756 /*isMerge*/ false, body['t']);
3757 }
3758 else if (action === 'm') {
3759 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3760 /*isMerge=*/ true, body['t']);
3761 }
3762 else if (action === 'c') {
3763 this.onListenRevoked_(body[ /*path*/'p'], body[ /*query*/'q']);
3764 }
3765 else if (action === 'ac') {
3766 this.onAuthRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3767 }
3768 else if (action === 'apc') {
3769 this.onAppCheckRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3770 }
3771 else if (action === 'sd') {
3772 this.onSecurityDebugPacket_(body);
3773 }
3774 else {
3775 error('Unrecognized action received from server: ' +
3776 stringify(action) +
3777 '\nAre you using the latest client?');
3778 }
3779 };
3780 PersistentConnection.prototype.onReady_ = function (timestamp, sessionId) {
3781 this.log_('connection ready');
3782 this.connected_ = true;
3783 this.lastConnectionEstablishedTime_ = new Date().getTime();
3784 this.handleTimestamp_(timestamp);
3785 this.lastSessionId = sessionId;
3786 if (this.firstConnection_) {
3787 this.sendConnectStats_();
3788 }
3789 this.restoreState_();
3790 this.firstConnection_ = false;
3791 this.onConnectStatus_(true);
3792 };
3793 PersistentConnection.prototype.scheduleConnect_ = function (timeout) {
3794 var _this = this;
3795 assert(!this.realtime_, "Scheduling a connect when we're already connected/ing?");
3796 if (this.establishConnectionTimer_) {
3797 clearTimeout(this.establishConnectionTimer_);
3798 }
3799 // NOTE: Even when timeout is 0, it's important to do a setTimeout to work around an infuriating "Security Error" in
3800 // Firefox when trying to write to our long-polling iframe in some scenarios (e.g. Forge or our unit tests).
3801 this.establishConnectionTimer_ = setTimeout(function () {
3802 _this.establishConnectionTimer_ = null;
3803 _this.establishConnection_();
3804 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3805 }, Math.floor(timeout));
3806 };
3807 PersistentConnection.prototype.initConnection_ = function () {
3808 if (!this.realtime_ && this.firstConnection_) {
3809 this.scheduleConnect_(0);
3810 }
3811 };
3812 PersistentConnection.prototype.onVisible_ = function (visible) {
3813 // NOTE: Tabbing away and back to a window will defeat our reconnect backoff, but I think that's fine.
3814 if (visible &&
3815 !this.visible_ &&
3816 this.reconnectDelay_ === this.maxReconnectDelay_) {
3817 this.log_('Window became visible. Reducing delay.');
3818 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3819 if (!this.realtime_) {
3820 this.scheduleConnect_(0);
3821 }
3822 }
3823 this.visible_ = visible;
3824 };
3825 PersistentConnection.prototype.onOnline_ = function (online) {
3826 if (online) {
3827 this.log_('Browser went online.');
3828 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3829 if (!this.realtime_) {
3830 this.scheduleConnect_(0);
3831 }
3832 }
3833 else {
3834 this.log_('Browser went offline. Killing connection.');
3835 if (this.realtime_) {
3836 this.realtime_.close();
3837 }
3838 }
3839 };
3840 PersistentConnection.prototype.onRealtimeDisconnect_ = function () {
3841 this.log_('data client disconnected');
3842 this.connected_ = false;
3843 this.realtime_ = null;
3844 // Since we don't know if our sent transactions succeeded or not, we need to cancel them.
3845 this.cancelSentTransactions_();
3846 // Clear out the pending requests.
3847 this.requestCBHash_ = {};
3848 if (this.shouldReconnect_()) {
3849 if (!this.visible_) {
3850 this.log_("Window isn't visible. Delaying reconnect.");
3851 this.reconnectDelay_ = this.maxReconnectDelay_;
3852 this.lastConnectionAttemptTime_ = new Date().getTime();
3853 }
3854 else if (this.lastConnectionEstablishedTime_) {
3855 // If we've been connected long enough, reset reconnect delay to minimum.
3856 var timeSinceLastConnectSucceeded = new Date().getTime() - this.lastConnectionEstablishedTime_;
3857 if (timeSinceLastConnectSucceeded > RECONNECT_DELAY_RESET_TIMEOUT) {
3858 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3859 }
3860 this.lastConnectionEstablishedTime_ = null;
3861 }
3862 var timeSinceLastConnectAttempt = new Date().getTime() - this.lastConnectionAttemptTime_;
3863 var reconnectDelay = Math.max(0, this.reconnectDelay_ - timeSinceLastConnectAttempt);
3864 reconnectDelay = Math.random() * reconnectDelay;
3865 this.log_('Trying to reconnect in ' + reconnectDelay + 'ms');
3866 this.scheduleConnect_(reconnectDelay);
3867 // Adjust reconnect delay for next time.
3868 this.reconnectDelay_ = Math.min(this.maxReconnectDelay_, this.reconnectDelay_ * RECONNECT_DELAY_MULTIPLIER);
3869 }
3870 this.onConnectStatus_(false);
3871 };
3872 PersistentConnection.prototype.establishConnection_ = function () {
3873 return __awaiter(this, void 0, void 0, function () {
3874 var onDataMessage, onReady, onDisconnect_1, connId, lastSessionId, canceled_1, connection_1, closeFn, sendRequestFn, forceRefresh, _a, authToken, appCheckToken, error_1;
3875 var _this = this;
3876 return __generator(this, function (_b) {
3877 switch (_b.label) {
3878 case 0:
3879 if (!this.shouldReconnect_()) return [3 /*break*/, 4];
3880 this.log_('Making a connection attempt');
3881 this.lastConnectionAttemptTime_ = new Date().getTime();
3882 this.lastConnectionEstablishedTime_ = null;
3883 onDataMessage = this.onDataMessage_.bind(this);
3884 onReady = this.onReady_.bind(this);
3885 onDisconnect_1 = this.onRealtimeDisconnect_.bind(this);
3886 connId = this.id + ':' + PersistentConnection.nextConnectionId_++;
3887 lastSessionId = this.lastSessionId;
3888 canceled_1 = false;
3889 connection_1 = null;
3890 closeFn = function () {
3891 if (connection_1) {
3892 connection_1.close();
3893 }
3894 else {
3895 canceled_1 = true;
3896 onDisconnect_1();
3897 }
3898 };
3899 sendRequestFn = function (msg) {
3900 assert(connection_1, "sendRequest call when we're not connected not allowed.");
3901 connection_1.sendRequest(msg);
3902 };
3903 this.realtime_ = {
3904 close: closeFn,
3905 sendRequest: sendRequestFn
3906 };
3907 forceRefresh = this.forceTokenRefresh_;
3908 this.forceTokenRefresh_ = false;
3909 _b.label = 1;
3910 case 1:
3911 _b.trys.push([1, 3, , 4]);
3912 return [4 /*yield*/, Promise.all([
3913 this.authTokenProvider_.getToken(forceRefresh),
3914 this.appCheckTokenProvider_.getToken(forceRefresh)
3915 ])];
3916 case 2:
3917 _a = __read.apply(void 0, [_b.sent(), 2]), authToken = _a[0], appCheckToken = _a[1];
3918 if (!canceled_1) {
3919 log('getToken() completed. Creating connection.');
3920 this.authToken_ = authToken && authToken.accessToken;
3921 this.appCheckToken_ = appCheckToken && appCheckToken.token;
3922 connection_1 = new Connection(connId, this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, onDataMessage, onReady, onDisconnect_1,
3923 /* onKill= */ function (reason) {
3924 warn(reason + ' (' + _this.repoInfo_.toString() + ')');
3925 _this.interrupt(SERVER_KILL_INTERRUPT_REASON);
3926 }, lastSessionId);
3927 }
3928 else {
3929 log('getToken() completed but was canceled');
3930 }
3931 return [3 /*break*/, 4];
3932 case 3:
3933 error_1 = _b.sent();
3934 this.log_('Failed to get token: ' + error_1);
3935 if (!canceled_1) {
3936 if (this.repoInfo_.nodeAdmin) {
3937 // This may be a critical error for the Admin Node.js SDK, so log a warning.
3938 // But getToken() may also just have temporarily failed, so we still want to
3939 // continue retrying.
3940 warn(error_1);
3941 }
3942 closeFn();
3943 }
3944 return [3 /*break*/, 4];
3945 case 4: return [2 /*return*/];
3946 }
3947 });
3948 });
3949 };
3950 PersistentConnection.prototype.interrupt = function (reason) {
3951 log('Interrupting connection for reason: ' + reason);
3952 this.interruptReasons_[reason] = true;
3953 if (this.realtime_) {
3954 this.realtime_.close();
3955 }
3956 else {
3957 if (this.establishConnectionTimer_) {
3958 clearTimeout(this.establishConnectionTimer_);
3959 this.establishConnectionTimer_ = null;
3960 }
3961 if (this.connected_) {
3962 this.onRealtimeDisconnect_();
3963 }
3964 }
3965 };
3966 PersistentConnection.prototype.resume = function (reason) {
3967 log('Resuming connection for reason: ' + reason);
3968 delete this.interruptReasons_[reason];
3969 if (isEmpty(this.interruptReasons_)) {
3970 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3971 if (!this.realtime_) {
3972 this.scheduleConnect_(0);
3973 }
3974 }
3975 };
3976 PersistentConnection.prototype.handleTimestamp_ = function (timestamp) {
3977 var delta = timestamp - new Date().getTime();
3978 this.onServerInfoUpdate_({ serverTimeOffset: delta });
3979 };
3980 PersistentConnection.prototype.cancelSentTransactions_ = function () {
3981 for (var i = 0; i < this.outstandingPuts_.length; i++) {
3982 var put = this.outstandingPuts_[i];
3983 if (put && /*hash*/ 'h' in put.request && put.queued) {
3984 if (put.onComplete) {
3985 put.onComplete('disconnect');
3986 }
3987 delete this.outstandingPuts_[i];
3988 this.outstandingPutCount_--;
3989 }
3990 }
3991 // Clean up array occasionally.
3992 if (this.outstandingPutCount_ === 0) {
3993 this.outstandingPuts_ = [];
3994 }
3995 };
3996 PersistentConnection.prototype.onListenRevoked_ = function (pathString, query) {
3997 // Remove the listen and manufacture a "permission_denied" error for the failed listen.
3998 var queryId;
3999 if (!query) {
4000 queryId = 'default';
4001 }
4002 else {
4003 queryId = query.map(function (q) { return ObjectToUniqueKey(q); }).join('$');
4004 }
4005 var listen = this.removeListen_(pathString, queryId);
4006 if (listen && listen.onComplete) {
4007 listen.onComplete('permission_denied');
4008 }
4009 };
4010 PersistentConnection.prototype.removeListen_ = function (pathString, queryId) {
4011 var normalizedPathString = new Path(pathString).toString(); // normalize path.
4012 var listen;
4013 if (this.listens.has(normalizedPathString)) {
4014 var map = this.listens.get(normalizedPathString);
4015 listen = map.get(queryId);
4016 map.delete(queryId);
4017 if (map.size === 0) {
4018 this.listens.delete(normalizedPathString);
4019 }
4020 }
4021 else {
4022 // all listens for this path has already been removed
4023 listen = undefined;
4024 }
4025 return listen;
4026 };
4027 PersistentConnection.prototype.onAuthRevoked_ = function (statusCode, explanation) {
4028 log('Auth token revoked: ' + statusCode + '/' + explanation);
4029 this.authToken_ = null;
4030 this.forceTokenRefresh_ = true;
4031 this.realtime_.close();
4032 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4033 // We'll wait a couple times before logging the warning / increasing the
4034 // retry period since oauth tokens will report as "invalid" if they're
4035 // just expired. Plus there may be transient issues that resolve themselves.
4036 this.invalidAuthTokenCount_++;
4037 if (this.invalidAuthTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4038 // Set a long reconnect delay because recovery is unlikely
4039 this.reconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
4040 // Notify the auth token provider that the token is invalid, which will log
4041 // a warning
4042 this.authTokenProvider_.notifyForInvalidToken();
4043 }
4044 }
4045 };
4046 PersistentConnection.prototype.onAppCheckRevoked_ = function (statusCode, explanation) {
4047 log('App check token revoked: ' + statusCode + '/' + explanation);
4048 this.appCheckToken_ = null;
4049 this.forceTokenRefresh_ = true;
4050 // Note: We don't close the connection as the developer may not have
4051 // enforcement enabled. The backend closes connections with enforcements.
4052 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4053 // We'll wait a couple times before logging the warning / increasing the
4054 // retry period since oauth tokens will report as "invalid" if they're
4055 // just expired. Plus there may be transient issues that resolve themselves.
4056 this.invalidAppCheckTokenCount_++;
4057 if (this.invalidAppCheckTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4058 this.appCheckTokenProvider_.notifyForInvalidToken();
4059 }
4060 }
4061 };
4062 PersistentConnection.prototype.onSecurityDebugPacket_ = function (body) {
4063 if (this.securityDebugCallback_) {
4064 this.securityDebugCallback_(body);
4065 }
4066 else {
4067 if ('msg' in body) {
4068 console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
4069 }
4070 }
4071 };
4072 PersistentConnection.prototype.restoreState_ = function () {
4073 var e_1, _a, e_2, _b;
4074 //Re-authenticate ourselves if we have a credential stored.
4075 this.tryAuth();
4076 this.tryAppCheck();
4077 try {
4078 // Puts depend on having received the corresponding data update from the server before they complete, so we must
4079 // make sure to send listens before puts.
4080 for (var _c = __values(this.listens.values()), _d = _c.next(); !_d.done; _d = _c.next()) {
4081 var queries = _d.value;
4082 try {
4083 for (var _e = (e_2 = void 0, __values(queries.values())), _f = _e.next(); !_f.done; _f = _e.next()) {
4084 var listenSpec = _f.value;
4085 this.sendListen_(listenSpec);
4086 }
4087 }
4088 catch (e_2_1) { e_2 = { error: e_2_1 }; }
4089 finally {
4090 try {
4091 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
4092 }
4093 finally { if (e_2) throw e_2.error; }
4094 }
4095 }
4096 }
4097 catch (e_1_1) { e_1 = { error: e_1_1 }; }
4098 finally {
4099 try {
4100 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
4101 }
4102 finally { if (e_1) throw e_1.error; }
4103 }
4104 for (var i = 0; i < this.outstandingPuts_.length; i++) {
4105 if (this.outstandingPuts_[i]) {
4106 this.sendPut_(i);
4107 }
4108 }
4109 while (this.onDisconnectRequestQueue_.length) {
4110 var request = this.onDisconnectRequestQueue_.shift();
4111 this.sendOnDisconnect_(request.action, request.pathString, request.data, request.onComplete);
4112 }
4113 for (var i = 0; i < this.outstandingGets_.length; i++) {
4114 if (this.outstandingGets_[i]) {
4115 this.sendGet_(i);
4116 }
4117 }
4118 };
4119 /**
4120 * Sends client stats for first connection
4121 */
4122 PersistentConnection.prototype.sendConnectStats_ = function () {
4123 var stats = {};
4124 var clientName = 'js';
4125 if (isNodeSdk()) {
4126 if (this.repoInfo_.nodeAdmin) {
4127 clientName = 'admin_node';
4128 }
4129 else {
4130 clientName = 'node';
4131 }
4132 }
4133 stats['sdk.' + clientName + '.' + SDK_VERSION.replace(/\./g, '-')] = 1;
4134 if (isMobileCordova()) {
4135 stats['framework.cordova'] = 1;
4136 }
4137 else if (isReactNative()) {
4138 stats['framework.reactnative'] = 1;
4139 }
4140 this.reportStats(stats);
4141 };
4142 PersistentConnection.prototype.shouldReconnect_ = function () {
4143 var online = OnlineMonitor.getInstance().currentlyOnline();
4144 return isEmpty(this.interruptReasons_) && online;
4145 };
4146 PersistentConnection.nextPersistentConnectionId_ = 0;
4147 /**
4148 * Counter for number of connections created. Mainly used for tagging in the logs
4149 */
4150 PersistentConnection.nextConnectionId_ = 0;
4151 return PersistentConnection;
4152}(ServerActions));
4153
4154/**
4155 * @license
4156 * Copyright 2017 Google LLC
4157 *
4158 * Licensed under the Apache License, Version 2.0 (the "License");
4159 * you may not use this file except in compliance with the License.
4160 * You may obtain a copy of the License at
4161 *
4162 * http://www.apache.org/licenses/LICENSE-2.0
4163 *
4164 * Unless required by applicable law or agreed to in writing, software
4165 * distributed under the License is distributed on an "AS IS" BASIS,
4166 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4167 * See the License for the specific language governing permissions and
4168 * limitations under the License.
4169 */
4170var NamedNode = /** @class */ (function () {
4171 function NamedNode(name, node) {
4172 this.name = name;
4173 this.node = node;
4174 }
4175 NamedNode.Wrap = function (name, node) {
4176 return new NamedNode(name, node);
4177 };
4178 return NamedNode;
4179}());
4180
4181/**
4182 * @license
4183 * Copyright 2017 Google LLC
4184 *
4185 * Licensed under the Apache License, Version 2.0 (the "License");
4186 * you may not use this file except in compliance with the License.
4187 * You may obtain a copy of the License at
4188 *
4189 * http://www.apache.org/licenses/LICENSE-2.0
4190 *
4191 * Unless required by applicable law or agreed to in writing, software
4192 * distributed under the License is distributed on an "AS IS" BASIS,
4193 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4194 * See the License for the specific language governing permissions and
4195 * limitations under the License.
4196 */
4197var Index = /** @class */ (function () {
4198 function Index() {
4199 }
4200 /**
4201 * @returns A standalone comparison function for
4202 * this index
4203 */
4204 Index.prototype.getCompare = function () {
4205 return this.compare.bind(this);
4206 };
4207 /**
4208 * Given a before and after value for a node, determine if the indexed value has changed. Even if they are different,
4209 * it's possible that the changes are isolated to parts of the snapshot that are not indexed.
4210 *
4211 *
4212 * @returns True if the portion of the snapshot being indexed changed between oldNode and newNode
4213 */
4214 Index.prototype.indexedValueChanged = function (oldNode, newNode) {
4215 var oldWrapped = new NamedNode(MIN_NAME, oldNode);
4216 var newWrapped = new NamedNode(MIN_NAME, newNode);
4217 return this.compare(oldWrapped, newWrapped) !== 0;
4218 };
4219 /**
4220 * @returns a node wrapper that will sort equal to or less than
4221 * any other node wrapper, using this index
4222 */
4223 Index.prototype.minPost = function () {
4224 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4225 return NamedNode.MIN;
4226 };
4227 return Index;
4228}());
4229
4230/**
4231 * @license
4232 * Copyright 2017 Google LLC
4233 *
4234 * Licensed under the Apache License, Version 2.0 (the "License");
4235 * you may not use this file except in compliance with the License.
4236 * You may obtain a copy of the License at
4237 *
4238 * http://www.apache.org/licenses/LICENSE-2.0
4239 *
4240 * Unless required by applicable law or agreed to in writing, software
4241 * distributed under the License is distributed on an "AS IS" BASIS,
4242 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4243 * See the License for the specific language governing permissions and
4244 * limitations under the License.
4245 */
4246var __EMPTY_NODE;
4247var KeyIndex = /** @class */ (function (_super) {
4248 __extends(KeyIndex, _super);
4249 function KeyIndex() {
4250 return _super !== null && _super.apply(this, arguments) || this;
4251 }
4252 Object.defineProperty(KeyIndex, "__EMPTY_NODE", {
4253 get: function () {
4254 return __EMPTY_NODE;
4255 },
4256 set: function (val) {
4257 __EMPTY_NODE = val;
4258 },
4259 enumerable: false,
4260 configurable: true
4261 });
4262 KeyIndex.prototype.compare = function (a, b) {
4263 return nameCompare(a.name, b.name);
4264 };
4265 KeyIndex.prototype.isDefinedOn = function (node) {
4266 // We could probably return true here (since every node has a key), but it's never called
4267 // so just leaving unimplemented for now.
4268 throw assertionError('KeyIndex.isDefinedOn not expected to be called.');
4269 };
4270 KeyIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
4271 return false; // The key for a node never changes.
4272 };
4273 KeyIndex.prototype.minPost = function () {
4274 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4275 return NamedNode.MIN;
4276 };
4277 KeyIndex.prototype.maxPost = function () {
4278 // TODO: This should really be created once and cached in a static property, but
4279 // NamedNode isn't defined yet, so I can't use it in a static. Bleh.
4280 return new NamedNode(MAX_NAME, __EMPTY_NODE);
4281 };
4282 KeyIndex.prototype.makePost = function (indexValue, name) {
4283 assert(typeof indexValue === 'string', 'KeyIndex indexValue must always be a string.');
4284 // We just use empty node, but it'll never be compared, since our comparator only looks at name.
4285 return new NamedNode(indexValue, __EMPTY_NODE);
4286 };
4287 /**
4288 * @returns String representation for inclusion in a query spec
4289 */
4290 KeyIndex.prototype.toString = function () {
4291 return '.key';
4292 };
4293 return KeyIndex;
4294}(Index));
4295var KEY_INDEX = new KeyIndex();
4296
4297/**
4298 * @license
4299 * Copyright 2017 Google LLC
4300 *
4301 * Licensed under the Apache License, Version 2.0 (the "License");
4302 * you may not use this file except in compliance with the License.
4303 * You may obtain a copy of the License at
4304 *
4305 * http://www.apache.org/licenses/LICENSE-2.0
4306 *
4307 * Unless required by applicable law or agreed to in writing, software
4308 * distributed under the License is distributed on an "AS IS" BASIS,
4309 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4310 * See the License for the specific language governing permissions and
4311 * limitations under the License.
4312 */
4313/**
4314 * An iterator over an LLRBNode.
4315 */
4316var SortedMapIterator = /** @class */ (function () {
4317 /**
4318 * @param node - Node to iterate.
4319 * @param isReverse_ - Whether or not to iterate in reverse
4320 */
4321 function SortedMapIterator(node, startKey, comparator, isReverse_, resultGenerator_) {
4322 if (resultGenerator_ === void 0) { resultGenerator_ = null; }
4323 this.isReverse_ = isReverse_;
4324 this.resultGenerator_ = resultGenerator_;
4325 this.nodeStack_ = [];
4326 var cmp = 1;
4327 while (!node.isEmpty()) {
4328 node = node;
4329 cmp = startKey ? comparator(node.key, startKey) : 1;
4330 // flip the comparison if we're going in reverse
4331 if (isReverse_) {
4332 cmp *= -1;
4333 }
4334 if (cmp < 0) {
4335 // This node is less than our start key. ignore it
4336 if (this.isReverse_) {
4337 node = node.left;
4338 }
4339 else {
4340 node = node.right;
4341 }
4342 }
4343 else if (cmp === 0) {
4344 // This node is exactly equal to our start key. Push it on the stack, but stop iterating;
4345 this.nodeStack_.push(node);
4346 break;
4347 }
4348 else {
4349 // This node is greater than our start key, add it to the stack and move to the next one
4350 this.nodeStack_.push(node);
4351 if (this.isReverse_) {
4352 node = node.right;
4353 }
4354 else {
4355 node = node.left;
4356 }
4357 }
4358 }
4359 }
4360 SortedMapIterator.prototype.getNext = function () {
4361 if (this.nodeStack_.length === 0) {
4362 return null;
4363 }
4364 var node = this.nodeStack_.pop();
4365 var result;
4366 if (this.resultGenerator_) {
4367 result = this.resultGenerator_(node.key, node.value);
4368 }
4369 else {
4370 result = { key: node.key, value: node.value };
4371 }
4372 if (this.isReverse_) {
4373 node = node.left;
4374 while (!node.isEmpty()) {
4375 this.nodeStack_.push(node);
4376 node = node.right;
4377 }
4378 }
4379 else {
4380 node = node.right;
4381 while (!node.isEmpty()) {
4382 this.nodeStack_.push(node);
4383 node = node.left;
4384 }
4385 }
4386 return result;
4387 };
4388 SortedMapIterator.prototype.hasNext = function () {
4389 return this.nodeStack_.length > 0;
4390 };
4391 SortedMapIterator.prototype.peek = function () {
4392 if (this.nodeStack_.length === 0) {
4393 return null;
4394 }
4395 var node = this.nodeStack_[this.nodeStack_.length - 1];
4396 if (this.resultGenerator_) {
4397 return this.resultGenerator_(node.key, node.value);
4398 }
4399 else {
4400 return { key: node.key, value: node.value };
4401 }
4402 };
4403 return SortedMapIterator;
4404}());
4405/**
4406 * Represents a node in a Left-leaning Red-Black tree.
4407 */
4408var LLRBNode = /** @class */ (function () {
4409 /**
4410 * @param key - Key associated with this node.
4411 * @param value - Value associated with this node.
4412 * @param color - Whether this node is red.
4413 * @param left - Left child.
4414 * @param right - Right child.
4415 */
4416 function LLRBNode(key, value, color, left, right) {
4417 this.key = key;
4418 this.value = value;
4419 this.color = color != null ? color : LLRBNode.RED;
4420 this.left =
4421 left != null ? left : SortedMap.EMPTY_NODE;
4422 this.right =
4423 right != null ? right : SortedMap.EMPTY_NODE;
4424 }
4425 /**
4426 * Returns a copy of the current node, optionally replacing pieces of it.
4427 *
4428 * @param key - New key for the node, or null.
4429 * @param value - New value for the node, or null.
4430 * @param color - New color for the node, or null.
4431 * @param left - New left child for the node, or null.
4432 * @param right - New right child for the node, or null.
4433 * @returns The node copy.
4434 */
4435 LLRBNode.prototype.copy = function (key, value, color, left, right) {
4436 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);
4437 };
4438 /**
4439 * @returns The total number of nodes in the tree.
4440 */
4441 LLRBNode.prototype.count = function () {
4442 return this.left.count() + 1 + this.right.count();
4443 };
4444 /**
4445 * @returns True if the tree is empty.
4446 */
4447 LLRBNode.prototype.isEmpty = function () {
4448 return false;
4449 };
4450 /**
4451 * Traverses the tree in key order and calls the specified action function
4452 * for each node.
4453 *
4454 * @param action - Callback function to be called for each
4455 * node. If it returns true, traversal is aborted.
4456 * @returns The first truthy value returned by action, or the last falsey
4457 * value returned by action
4458 */
4459 LLRBNode.prototype.inorderTraversal = function (action) {
4460 return (this.left.inorderTraversal(action) ||
4461 !!action(this.key, this.value) ||
4462 this.right.inorderTraversal(action));
4463 };
4464 /**
4465 * Traverses the tree in reverse key order and calls the specified action function
4466 * for each node.
4467 *
4468 * @param action - Callback function to be called for each
4469 * node. If it returns true, traversal is aborted.
4470 * @returns True if traversal was aborted.
4471 */
4472 LLRBNode.prototype.reverseTraversal = function (action) {
4473 return (this.right.reverseTraversal(action) ||
4474 action(this.key, this.value) ||
4475 this.left.reverseTraversal(action));
4476 };
4477 /**
4478 * @returns The minimum node in the tree.
4479 */
4480 LLRBNode.prototype.min_ = function () {
4481 if (this.left.isEmpty()) {
4482 return this;
4483 }
4484 else {
4485 return this.left.min_();
4486 }
4487 };
4488 /**
4489 * @returns The maximum key in the tree.
4490 */
4491 LLRBNode.prototype.minKey = function () {
4492 return this.min_().key;
4493 };
4494 /**
4495 * @returns The maximum key in the tree.
4496 */
4497 LLRBNode.prototype.maxKey = function () {
4498 if (this.right.isEmpty()) {
4499 return this.key;
4500 }
4501 else {
4502 return this.right.maxKey();
4503 }
4504 };
4505 /**
4506 * @param key - Key to insert.
4507 * @param value - Value to insert.
4508 * @param comparator - Comparator.
4509 * @returns New tree, with the key/value added.
4510 */
4511 LLRBNode.prototype.insert = function (key, value, comparator) {
4512 var n = this;
4513 var cmp = comparator(key, n.key);
4514 if (cmp < 0) {
4515 n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
4516 }
4517 else if (cmp === 0) {
4518 n = n.copy(null, value, null, null, null);
4519 }
4520 else {
4521 n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
4522 }
4523 return n.fixUp_();
4524 };
4525 /**
4526 * @returns New tree, with the minimum key removed.
4527 */
4528 LLRBNode.prototype.removeMin_ = function () {
4529 if (this.left.isEmpty()) {
4530 return SortedMap.EMPTY_NODE;
4531 }
4532 var n = this;
4533 if (!n.left.isRed_() && !n.left.left.isRed_()) {
4534 n = n.moveRedLeft_();
4535 }
4536 n = n.copy(null, null, null, n.left.removeMin_(), null);
4537 return n.fixUp_();
4538 };
4539 /**
4540 * @param key - The key of the item to remove.
4541 * @param comparator - Comparator.
4542 * @returns New tree, with the specified item removed.
4543 */
4544 LLRBNode.prototype.remove = function (key, comparator) {
4545 var n, smallest;
4546 n = this;
4547 if (comparator(key, n.key) < 0) {
4548 if (!n.left.isEmpty() && !n.left.isRed_() && !n.left.left.isRed_()) {
4549 n = n.moveRedLeft_();
4550 }
4551 n = n.copy(null, null, null, n.left.remove(key, comparator), null);
4552 }
4553 else {
4554 if (n.left.isRed_()) {
4555 n = n.rotateRight_();
4556 }
4557 if (!n.right.isEmpty() && !n.right.isRed_() && !n.right.left.isRed_()) {
4558 n = n.moveRedRight_();
4559 }
4560 if (comparator(key, n.key) === 0) {
4561 if (n.right.isEmpty()) {
4562 return SortedMap.EMPTY_NODE;
4563 }
4564 else {
4565 smallest = n.right.min_();
4566 n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin_());
4567 }
4568 }
4569 n = n.copy(null, null, null, null, n.right.remove(key, comparator));
4570 }
4571 return n.fixUp_();
4572 };
4573 /**
4574 * @returns Whether this is a RED node.
4575 */
4576 LLRBNode.prototype.isRed_ = function () {
4577 return this.color;
4578 };
4579 /**
4580 * @returns New tree after performing any needed rotations.
4581 */
4582 LLRBNode.prototype.fixUp_ = function () {
4583 var n = this;
4584 if (n.right.isRed_() && !n.left.isRed_()) {
4585 n = n.rotateLeft_();
4586 }
4587 if (n.left.isRed_() && n.left.left.isRed_()) {
4588 n = n.rotateRight_();
4589 }
4590 if (n.left.isRed_() && n.right.isRed_()) {
4591 n = n.colorFlip_();
4592 }
4593 return n;
4594 };
4595 /**
4596 * @returns New tree, after moveRedLeft.
4597 */
4598 LLRBNode.prototype.moveRedLeft_ = function () {
4599 var n = this.colorFlip_();
4600 if (n.right.left.isRed_()) {
4601 n = n.copy(null, null, null, null, n.right.rotateRight_());
4602 n = n.rotateLeft_();
4603 n = n.colorFlip_();
4604 }
4605 return n;
4606 };
4607 /**
4608 * @returns New tree, after moveRedRight.
4609 */
4610 LLRBNode.prototype.moveRedRight_ = function () {
4611 var n = this.colorFlip_();
4612 if (n.left.left.isRed_()) {
4613 n = n.rotateRight_();
4614 n = n.colorFlip_();
4615 }
4616 return n;
4617 };
4618 /**
4619 * @returns New tree, after rotateLeft.
4620 */
4621 LLRBNode.prototype.rotateLeft_ = function () {
4622 var nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
4623 return this.right.copy(null, null, this.color, nl, null);
4624 };
4625 /**
4626 * @returns New tree, after rotateRight.
4627 */
4628 LLRBNode.prototype.rotateRight_ = function () {
4629 var nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
4630 return this.left.copy(null, null, this.color, null, nr);
4631 };
4632 /**
4633 * @returns Newt ree, after colorFlip.
4634 */
4635 LLRBNode.prototype.colorFlip_ = function () {
4636 var left = this.left.copy(null, null, !this.left.color, null, null);
4637 var right = this.right.copy(null, null, !this.right.color, null, null);
4638 return this.copy(null, null, !this.color, left, right);
4639 };
4640 /**
4641 * For testing.
4642 *
4643 * @returns True if all is well.
4644 */
4645 LLRBNode.prototype.checkMaxDepth_ = function () {
4646 var blackDepth = this.check_();
4647 return Math.pow(2.0, blackDepth) <= this.count() + 1;
4648 };
4649 LLRBNode.prototype.check_ = function () {
4650 if (this.isRed_() && this.left.isRed_()) {
4651 throw new Error('Red node has red child(' + this.key + ',' + this.value + ')');
4652 }
4653 if (this.right.isRed_()) {
4654 throw new Error('Right child of (' + this.key + ',' + this.value + ') is red');
4655 }
4656 var blackDepth = this.left.check_();
4657 if (blackDepth !== this.right.check_()) {
4658 throw new Error('Black depths differ');
4659 }
4660 else {
4661 return blackDepth + (this.isRed_() ? 0 : 1);
4662 }
4663 };
4664 LLRBNode.RED = true;
4665 LLRBNode.BLACK = false;
4666 return LLRBNode;
4667}());
4668/**
4669 * Represents an empty node (a leaf node in the Red-Black Tree).
4670 */
4671var LLRBEmptyNode = /** @class */ (function () {
4672 function LLRBEmptyNode() {
4673 }
4674 /**
4675 * Returns a copy of the current node.
4676 *
4677 * @returns The node copy.
4678 */
4679 LLRBEmptyNode.prototype.copy = function (key, value, color, left, right) {
4680 return this;
4681 };
4682 /**
4683 * Returns a copy of the tree, with the specified key/value added.
4684 *
4685 * @param key - Key to be added.
4686 * @param value - Value to be added.
4687 * @param comparator - Comparator.
4688 * @returns New tree, with item added.
4689 */
4690 LLRBEmptyNode.prototype.insert = function (key, value, comparator) {
4691 return new LLRBNode(key, value, null);
4692 };
4693 /**
4694 * Returns a copy of the tree, with the specified key removed.
4695 *
4696 * @param key - The key to remove.
4697 * @param comparator - Comparator.
4698 * @returns New tree, with item removed.
4699 */
4700 LLRBEmptyNode.prototype.remove = function (key, comparator) {
4701 return this;
4702 };
4703 /**
4704 * @returns The total number of nodes in the tree.
4705 */
4706 LLRBEmptyNode.prototype.count = function () {
4707 return 0;
4708 };
4709 /**
4710 * @returns True if the tree is empty.
4711 */
4712 LLRBEmptyNode.prototype.isEmpty = function () {
4713 return true;
4714 };
4715 /**
4716 * Traverses the tree in key order and calls the specified action function
4717 * for each node.
4718 *
4719 * @param action - Callback function to be called for each
4720 * node. If it returns true, traversal is aborted.
4721 * @returns True if traversal was aborted.
4722 */
4723 LLRBEmptyNode.prototype.inorderTraversal = function (action) {
4724 return false;
4725 };
4726 /**
4727 * Traverses the tree in reverse key order and calls the specified action function
4728 * for each node.
4729 *
4730 * @param action - Callback function to be called for each
4731 * node. If it returns true, traversal is aborted.
4732 * @returns True if traversal was aborted.
4733 */
4734 LLRBEmptyNode.prototype.reverseTraversal = function (action) {
4735 return false;
4736 };
4737 LLRBEmptyNode.prototype.minKey = function () {
4738 return null;
4739 };
4740 LLRBEmptyNode.prototype.maxKey = function () {
4741 return null;
4742 };
4743 LLRBEmptyNode.prototype.check_ = function () {
4744 return 0;
4745 };
4746 /**
4747 * @returns Whether this node is red.
4748 */
4749 LLRBEmptyNode.prototype.isRed_ = function () {
4750 return false;
4751 };
4752 return LLRBEmptyNode;
4753}());
4754/**
4755 * An immutable sorted map implementation, based on a Left-leaning Red-Black
4756 * tree.
4757 */
4758var SortedMap = /** @class */ (function () {
4759 /**
4760 * @param comparator_ - Key comparator.
4761 * @param root_ - Optional root node for the map.
4762 */
4763 function SortedMap(comparator_, root_) {
4764 if (root_ === void 0) { root_ = SortedMap.EMPTY_NODE; }
4765 this.comparator_ = comparator_;
4766 this.root_ = root_;
4767 }
4768 /**
4769 * Returns a copy of the map, with the specified key/value added or replaced.
4770 * (TODO: We should perhaps rename this method to 'put')
4771 *
4772 * @param key - Key to be added.
4773 * @param value - Value to be added.
4774 * @returns New map, with item added.
4775 */
4776 SortedMap.prototype.insert = function (key, value) {
4777 return new SortedMap(this.comparator_, this.root_
4778 .insert(key, value, this.comparator_)
4779 .copy(null, null, LLRBNode.BLACK, null, null));
4780 };
4781 /**
4782 * Returns a copy of the map, with the specified key removed.
4783 *
4784 * @param key - The key to remove.
4785 * @returns New map, with item removed.
4786 */
4787 SortedMap.prototype.remove = function (key) {
4788 return new SortedMap(this.comparator_, this.root_
4789 .remove(key, this.comparator_)
4790 .copy(null, null, LLRBNode.BLACK, null, null));
4791 };
4792 /**
4793 * Returns the value of the node with the given key, or null.
4794 *
4795 * @param key - The key to look up.
4796 * @returns The value of the node with the given key, or null if the
4797 * key doesn't exist.
4798 */
4799 SortedMap.prototype.get = function (key) {
4800 var cmp;
4801 var node = this.root_;
4802 while (!node.isEmpty()) {
4803 cmp = this.comparator_(key, node.key);
4804 if (cmp === 0) {
4805 return node.value;
4806 }
4807 else if (cmp < 0) {
4808 node = node.left;
4809 }
4810 else if (cmp > 0) {
4811 node = node.right;
4812 }
4813 }
4814 return null;
4815 };
4816 /**
4817 * Returns the key of the item *before* the specified key, or null if key is the first item.
4818 * @param key - The key to find the predecessor of
4819 * @returns The predecessor key.
4820 */
4821 SortedMap.prototype.getPredecessorKey = function (key) {
4822 var cmp, node = this.root_, rightParent = null;
4823 while (!node.isEmpty()) {
4824 cmp = this.comparator_(key, node.key);
4825 if (cmp === 0) {
4826 if (!node.left.isEmpty()) {
4827 node = node.left;
4828 while (!node.right.isEmpty()) {
4829 node = node.right;
4830 }
4831 return node.key;
4832 }
4833 else if (rightParent) {
4834 return rightParent.key;
4835 }
4836 else {
4837 return null; // first item.
4838 }
4839 }
4840 else if (cmp < 0) {
4841 node = node.left;
4842 }
4843 else if (cmp > 0) {
4844 rightParent = node;
4845 node = node.right;
4846 }
4847 }
4848 throw new Error('Attempted to find predecessor key for a nonexistent key. What gives?');
4849 };
4850 /**
4851 * @returns True if the map is empty.
4852 */
4853 SortedMap.prototype.isEmpty = function () {
4854 return this.root_.isEmpty();
4855 };
4856 /**
4857 * @returns The total number of nodes in the map.
4858 */
4859 SortedMap.prototype.count = function () {
4860 return this.root_.count();
4861 };
4862 /**
4863 * @returns The minimum key in the map.
4864 */
4865 SortedMap.prototype.minKey = function () {
4866 return this.root_.minKey();
4867 };
4868 /**
4869 * @returns The maximum key in the map.
4870 */
4871 SortedMap.prototype.maxKey = function () {
4872 return this.root_.maxKey();
4873 };
4874 /**
4875 * Traverses the map in key order and calls the specified action function
4876 * for each key/value pair.
4877 *
4878 * @param action - Callback function to be called
4879 * for each key/value pair. If action returns true, traversal is aborted.
4880 * @returns The first truthy value returned by action, or the last falsey
4881 * value returned by action
4882 */
4883 SortedMap.prototype.inorderTraversal = function (action) {
4884 return this.root_.inorderTraversal(action);
4885 };
4886 /**
4887 * Traverses the map in reverse 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 True if the traversal was aborted.
4893 */
4894 SortedMap.prototype.reverseTraversal = function (action) {
4895 return this.root_.reverseTraversal(action);
4896 };
4897 /**
4898 * Returns an iterator over the SortedMap.
4899 * @returns The iterator.
4900 */
4901 SortedMap.prototype.getIterator = function (resultGenerator) {
4902 return new SortedMapIterator(this.root_, null, this.comparator_, false, resultGenerator);
4903 };
4904 SortedMap.prototype.getIteratorFrom = function (key, resultGenerator) {
4905 return new SortedMapIterator(this.root_, key, this.comparator_, false, resultGenerator);
4906 };
4907 SortedMap.prototype.getReverseIteratorFrom = function (key, resultGenerator) {
4908 return new SortedMapIterator(this.root_, key, this.comparator_, true, resultGenerator);
4909 };
4910 SortedMap.prototype.getReverseIterator = function (resultGenerator) {
4911 return new SortedMapIterator(this.root_, null, this.comparator_, true, resultGenerator);
4912 };
4913 /**
4914 * Always use the same empty node, to reduce memory.
4915 */
4916 SortedMap.EMPTY_NODE = new LLRBEmptyNode();
4917 return SortedMap;
4918}());
4919
4920/**
4921 * @license
4922 * Copyright 2017 Google LLC
4923 *
4924 * Licensed under the Apache License, Version 2.0 (the "License");
4925 * you may not use this file except in compliance with the License.
4926 * You may obtain a copy of the License at
4927 *
4928 * http://www.apache.org/licenses/LICENSE-2.0
4929 *
4930 * Unless required by applicable law or agreed to in writing, software
4931 * distributed under the License is distributed on an "AS IS" BASIS,
4932 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4933 * See the License for the specific language governing permissions and
4934 * limitations under the License.
4935 */
4936function NAME_ONLY_COMPARATOR(left, right) {
4937 return nameCompare(left.name, right.name);
4938}
4939function NAME_COMPARATOR(left, right) {
4940 return nameCompare(left, right);
4941}
4942
4943/**
4944 * @license
4945 * Copyright 2017 Google LLC
4946 *
4947 * Licensed under the Apache License, Version 2.0 (the "License");
4948 * you may not use this file except in compliance with the License.
4949 * You may obtain a copy of the License at
4950 *
4951 * http://www.apache.org/licenses/LICENSE-2.0
4952 *
4953 * Unless required by applicable law or agreed to in writing, software
4954 * distributed under the License is distributed on an "AS IS" BASIS,
4955 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4956 * See the License for the specific language governing permissions and
4957 * limitations under the License.
4958 */
4959var MAX_NODE$2;
4960function setMaxNode$1(val) {
4961 MAX_NODE$2 = val;
4962}
4963var priorityHashText = function (priority) {
4964 if (typeof priority === 'number') {
4965 return 'number:' + doubleToIEEE754String(priority);
4966 }
4967 else {
4968 return 'string:' + priority;
4969 }
4970};
4971/**
4972 * Validates that a priority snapshot Node is valid.
4973 */
4974var validatePriorityNode = function (priorityNode) {
4975 if (priorityNode.isLeafNode()) {
4976 var val = priorityNode.val();
4977 assert(typeof val === 'string' ||
4978 typeof val === 'number' ||
4979 (typeof val === 'object' && contains(val, '.sv')), 'Priority must be a string or number.');
4980 }
4981 else {
4982 assert(priorityNode === MAX_NODE$2 || priorityNode.isEmpty(), 'priority of unexpected type.');
4983 }
4984 // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
4985 assert(priorityNode === MAX_NODE$2 || priorityNode.getPriority().isEmpty(), "Priority nodes can't have a priority of their own.");
4986};
4987
4988/**
4989 * @license
4990 * Copyright 2017 Google LLC
4991 *
4992 * Licensed under the Apache License, Version 2.0 (the "License");
4993 * you may not use this file except in compliance with the License.
4994 * You may obtain a copy of the License at
4995 *
4996 * http://www.apache.org/licenses/LICENSE-2.0
4997 *
4998 * Unless required by applicable law or agreed to in writing, software
4999 * distributed under the License is distributed on an "AS IS" BASIS,
5000 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5001 * See the License for the specific language governing permissions and
5002 * limitations under the License.
5003 */
5004var __childrenNodeConstructor;
5005/**
5006 * LeafNode is a class for storing leaf nodes in a DataSnapshot. It
5007 * implements Node and stores the value of the node (a string,
5008 * number, or boolean) accessible via getValue().
5009 */
5010var LeafNode = /** @class */ (function () {
5011 /**
5012 * @param value_ - The value to store in this leaf node. The object type is
5013 * possible in the event of a deferred value
5014 * @param priorityNode_ - The priority of this node.
5015 */
5016 function LeafNode(value_, priorityNode_) {
5017 if (priorityNode_ === void 0) { priorityNode_ = LeafNode.__childrenNodeConstructor.EMPTY_NODE; }
5018 this.value_ = value_;
5019 this.priorityNode_ = priorityNode_;
5020 this.lazyHash_ = null;
5021 assert(this.value_ !== undefined && this.value_ !== null, "LeafNode shouldn't be created with null/undefined value.");
5022 validatePriorityNode(this.priorityNode_);
5023 }
5024 Object.defineProperty(LeafNode, "__childrenNodeConstructor", {
5025 get: function () {
5026 return __childrenNodeConstructor;
5027 },
5028 set: function (val) {
5029 __childrenNodeConstructor = val;
5030 },
5031 enumerable: false,
5032 configurable: true
5033 });
5034 /** @inheritDoc */
5035 LeafNode.prototype.isLeafNode = function () {
5036 return true;
5037 };
5038 /** @inheritDoc */
5039 LeafNode.prototype.getPriority = function () {
5040 return this.priorityNode_;
5041 };
5042 /** @inheritDoc */
5043 LeafNode.prototype.updatePriority = function (newPriorityNode) {
5044 return new LeafNode(this.value_, newPriorityNode);
5045 };
5046 /** @inheritDoc */
5047 LeafNode.prototype.getImmediateChild = function (childName) {
5048 // Hack to treat priority as a regular child
5049 if (childName === '.priority') {
5050 return this.priorityNode_;
5051 }
5052 else {
5053 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5054 }
5055 };
5056 /** @inheritDoc */
5057 LeafNode.prototype.getChild = function (path) {
5058 if (pathIsEmpty(path)) {
5059 return this;
5060 }
5061 else if (pathGetFront(path) === '.priority') {
5062 return this.priorityNode_;
5063 }
5064 else {
5065 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5066 }
5067 };
5068 LeafNode.prototype.hasChild = function () {
5069 return false;
5070 };
5071 /** @inheritDoc */
5072 LeafNode.prototype.getPredecessorChildName = function (childName, childNode) {
5073 return null;
5074 };
5075 /** @inheritDoc */
5076 LeafNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5077 if (childName === '.priority') {
5078 return this.updatePriority(newChildNode);
5079 }
5080 else if (newChildNode.isEmpty() && childName !== '.priority') {
5081 return this;
5082 }
5083 else {
5084 return LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateImmediateChild(childName, newChildNode).updatePriority(this.priorityNode_);
5085 }
5086 };
5087 /** @inheritDoc */
5088 LeafNode.prototype.updateChild = function (path, newChildNode) {
5089 var front = pathGetFront(path);
5090 if (front === null) {
5091 return newChildNode;
5092 }
5093 else if (newChildNode.isEmpty() && front !== '.priority') {
5094 return this;
5095 }
5096 else {
5097 assert(front !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5098 return this.updateImmediateChild(front, LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateChild(pathPopFront(path), newChildNode));
5099 }
5100 };
5101 /** @inheritDoc */
5102 LeafNode.prototype.isEmpty = function () {
5103 return false;
5104 };
5105 /** @inheritDoc */
5106 LeafNode.prototype.numChildren = function () {
5107 return 0;
5108 };
5109 /** @inheritDoc */
5110 LeafNode.prototype.forEachChild = function (index, action) {
5111 return false;
5112 };
5113 LeafNode.prototype.val = function (exportFormat) {
5114 if (exportFormat && !this.getPriority().isEmpty()) {
5115 return {
5116 '.value': this.getValue(),
5117 '.priority': this.getPriority().val()
5118 };
5119 }
5120 else {
5121 return this.getValue();
5122 }
5123 };
5124 /** @inheritDoc */
5125 LeafNode.prototype.hash = function () {
5126 if (this.lazyHash_ === null) {
5127 var toHash = '';
5128 if (!this.priorityNode_.isEmpty()) {
5129 toHash +=
5130 'priority:' +
5131 priorityHashText(this.priorityNode_.val()) +
5132 ':';
5133 }
5134 var type = typeof this.value_;
5135 toHash += type + ':';
5136 if (type === 'number') {
5137 toHash += doubleToIEEE754String(this.value_);
5138 }
5139 else {
5140 toHash += this.value_;
5141 }
5142 this.lazyHash_ = sha1(toHash);
5143 }
5144 return this.lazyHash_;
5145 };
5146 /**
5147 * Returns the value of the leaf node.
5148 * @returns The value of the node.
5149 */
5150 LeafNode.prototype.getValue = function () {
5151 return this.value_;
5152 };
5153 LeafNode.prototype.compareTo = function (other) {
5154 if (other === LeafNode.__childrenNodeConstructor.EMPTY_NODE) {
5155 return 1;
5156 }
5157 else if (other instanceof LeafNode.__childrenNodeConstructor) {
5158 return -1;
5159 }
5160 else {
5161 assert(other.isLeafNode(), 'Unknown node type');
5162 return this.compareToLeafNode_(other);
5163 }
5164 };
5165 /**
5166 * Comparison specifically for two leaf nodes
5167 */
5168 LeafNode.prototype.compareToLeafNode_ = function (otherLeaf) {
5169 var otherLeafType = typeof otherLeaf.value_;
5170 var thisLeafType = typeof this.value_;
5171 var otherIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(otherLeafType);
5172 var thisIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(thisLeafType);
5173 assert(otherIndex >= 0, 'Unknown leaf type: ' + otherLeafType);
5174 assert(thisIndex >= 0, 'Unknown leaf type: ' + thisLeafType);
5175 if (otherIndex === thisIndex) {
5176 // Same type, compare values
5177 if (thisLeafType === 'object') {
5178 // Deferred value nodes are all equal, but we should also never get to this point...
5179 return 0;
5180 }
5181 else {
5182 // Note that this works because true > false, all others are number or string comparisons
5183 if (this.value_ < otherLeaf.value_) {
5184 return -1;
5185 }
5186 else if (this.value_ === otherLeaf.value_) {
5187 return 0;
5188 }
5189 else {
5190 return 1;
5191 }
5192 }
5193 }
5194 else {
5195 return thisIndex - otherIndex;
5196 }
5197 };
5198 LeafNode.prototype.withIndex = function () {
5199 return this;
5200 };
5201 LeafNode.prototype.isIndexed = function () {
5202 return true;
5203 };
5204 LeafNode.prototype.equals = function (other) {
5205 if (other === this) {
5206 return true;
5207 }
5208 else if (other.isLeafNode()) {
5209 var otherLeaf = other;
5210 return (this.value_ === otherLeaf.value_ &&
5211 this.priorityNode_.equals(otherLeaf.priorityNode_));
5212 }
5213 else {
5214 return false;
5215 }
5216 };
5217 /**
5218 * The sort order for comparing leaf nodes of different types. If two leaf nodes have
5219 * the same type, the comparison falls back to their value
5220 */
5221 LeafNode.VALUE_TYPE_ORDER = ['object', 'boolean', 'number', 'string'];
5222 return LeafNode;
5223}());
5224
5225/**
5226 * @license
5227 * Copyright 2017 Google LLC
5228 *
5229 * Licensed under the Apache License, Version 2.0 (the "License");
5230 * you may not use this file except in compliance with the License.
5231 * You may obtain a copy of the License at
5232 *
5233 * http://www.apache.org/licenses/LICENSE-2.0
5234 *
5235 * Unless required by applicable law or agreed to in writing, software
5236 * distributed under the License is distributed on an "AS IS" BASIS,
5237 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5238 * See the License for the specific language governing permissions and
5239 * limitations under the License.
5240 */
5241var nodeFromJSON$1;
5242var MAX_NODE$1;
5243function setNodeFromJSON(val) {
5244 nodeFromJSON$1 = val;
5245}
5246function setMaxNode(val) {
5247 MAX_NODE$1 = val;
5248}
5249var PriorityIndex = /** @class */ (function (_super) {
5250 __extends(PriorityIndex, _super);
5251 function PriorityIndex() {
5252 return _super !== null && _super.apply(this, arguments) || this;
5253 }
5254 PriorityIndex.prototype.compare = function (a, b) {
5255 var aPriority = a.node.getPriority();
5256 var bPriority = b.node.getPriority();
5257 var indexCmp = aPriority.compareTo(bPriority);
5258 if (indexCmp === 0) {
5259 return nameCompare(a.name, b.name);
5260 }
5261 else {
5262 return indexCmp;
5263 }
5264 };
5265 PriorityIndex.prototype.isDefinedOn = function (node) {
5266 return !node.getPriority().isEmpty();
5267 };
5268 PriorityIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
5269 return !oldNode.getPriority().equals(newNode.getPriority());
5270 };
5271 PriorityIndex.prototype.minPost = function () {
5272 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5273 return NamedNode.MIN;
5274 };
5275 PriorityIndex.prototype.maxPost = function () {
5276 return new NamedNode(MAX_NAME, new LeafNode('[PRIORITY-POST]', MAX_NODE$1));
5277 };
5278 PriorityIndex.prototype.makePost = function (indexValue, name) {
5279 var priorityNode = nodeFromJSON$1(indexValue);
5280 return new NamedNode(name, new LeafNode('[PRIORITY-POST]', priorityNode));
5281 };
5282 /**
5283 * @returns String representation for inclusion in a query spec
5284 */
5285 PriorityIndex.prototype.toString = function () {
5286 return '.priority';
5287 };
5288 return PriorityIndex;
5289}(Index));
5290var PRIORITY_INDEX = new PriorityIndex();
5291
5292/**
5293 * @license
5294 * Copyright 2017 Google LLC
5295 *
5296 * Licensed under the Apache License, Version 2.0 (the "License");
5297 * you may not use this file except in compliance with the License.
5298 * You may obtain a copy of the License at
5299 *
5300 * http://www.apache.org/licenses/LICENSE-2.0
5301 *
5302 * Unless required by applicable law or agreed to in writing, software
5303 * distributed under the License is distributed on an "AS IS" BASIS,
5304 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5305 * See the License for the specific language governing permissions and
5306 * limitations under the License.
5307 */
5308var LOG_2 = Math.log(2);
5309var Base12Num = /** @class */ (function () {
5310 function Base12Num(length) {
5311 var logBase2 = function (num) {
5312 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5313 return parseInt((Math.log(num) / LOG_2), 10);
5314 };
5315 var bitMask = function (bits) { return parseInt(Array(bits + 1).join('1'), 2); };
5316 this.count = logBase2(length + 1);
5317 this.current_ = this.count - 1;
5318 var mask = bitMask(this.count);
5319 this.bits_ = (length + 1) & mask;
5320 }
5321 Base12Num.prototype.nextBitIsOne = function () {
5322 //noinspection JSBitwiseOperatorUsage
5323 var result = !(this.bits_ & (0x1 << this.current_));
5324 this.current_--;
5325 return result;
5326 };
5327 return Base12Num;
5328}());
5329/**
5330 * Takes a list of child nodes and constructs a SortedSet using the given comparison
5331 * function
5332 *
5333 * Uses the algorithm described in the paper linked here:
5334 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458
5335 *
5336 * @param childList - Unsorted list of children
5337 * @param cmp - The comparison method to be used
5338 * @param keyFn - An optional function to extract K from a node wrapper, if K's
5339 * type is not NamedNode
5340 * @param mapSortFn - An optional override for comparator used by the generated sorted map
5341 */
5342var buildChildSet = function (childList, cmp, keyFn, mapSortFn) {
5343 childList.sort(cmp);
5344 var buildBalancedTree = function (low, high) {
5345 var length = high - low;
5346 var namedNode;
5347 var key;
5348 if (length === 0) {
5349 return null;
5350 }
5351 else if (length === 1) {
5352 namedNode = childList[low];
5353 key = keyFn ? keyFn(namedNode) : namedNode;
5354 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, null, null);
5355 }
5356 else {
5357 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5358 var middle = parseInt((length / 2), 10) + low;
5359 var left = buildBalancedTree(low, middle);
5360 var right = buildBalancedTree(middle + 1, high);
5361 namedNode = childList[middle];
5362 key = keyFn ? keyFn(namedNode) : namedNode;
5363 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, left, right);
5364 }
5365 };
5366 var buildFrom12Array = function (base12) {
5367 var node = null;
5368 var root = null;
5369 var index = childList.length;
5370 var buildPennant = function (chunkSize, color) {
5371 var low = index - chunkSize;
5372 var high = index;
5373 index -= chunkSize;
5374 var childTree = buildBalancedTree(low + 1, high);
5375 var namedNode = childList[low];
5376 var key = keyFn ? keyFn(namedNode) : namedNode;
5377 attachPennant(new LLRBNode(key, namedNode.node, color, null, childTree));
5378 };
5379 var attachPennant = function (pennant) {
5380 if (node) {
5381 node.left = pennant;
5382 node = pennant;
5383 }
5384 else {
5385 root = pennant;
5386 node = pennant;
5387 }
5388 };
5389 for (var i = 0; i < base12.count; ++i) {
5390 var isOne = base12.nextBitIsOne();
5391 // The number of nodes taken in each slice is 2^(arr.length - (i + 1))
5392 var chunkSize = Math.pow(2, base12.count - (i + 1));
5393 if (isOne) {
5394 buildPennant(chunkSize, LLRBNode.BLACK);
5395 }
5396 else {
5397 // current == 2
5398 buildPennant(chunkSize, LLRBNode.BLACK);
5399 buildPennant(chunkSize, LLRBNode.RED);
5400 }
5401 }
5402 return root;
5403 };
5404 var base12 = new Base12Num(childList.length);
5405 var root = buildFrom12Array(base12);
5406 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5407 return new SortedMap(mapSortFn || cmp, root);
5408};
5409
5410/**
5411 * @license
5412 * Copyright 2017 Google LLC
5413 *
5414 * Licensed under the Apache License, Version 2.0 (the "License");
5415 * you may not use this file except in compliance with the License.
5416 * You may obtain a copy of the License at
5417 *
5418 * http://www.apache.org/licenses/LICENSE-2.0
5419 *
5420 * Unless required by applicable law or agreed to in writing, software
5421 * distributed under the License is distributed on an "AS IS" BASIS,
5422 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5423 * See the License for the specific language governing permissions and
5424 * limitations under the License.
5425 */
5426var _defaultIndexMap;
5427var fallbackObject = {};
5428var IndexMap = /** @class */ (function () {
5429 function IndexMap(indexes_, indexSet_) {
5430 this.indexes_ = indexes_;
5431 this.indexSet_ = indexSet_;
5432 }
5433 Object.defineProperty(IndexMap, "Default", {
5434 /**
5435 * The default IndexMap for nodes without a priority
5436 */
5437 get: function () {
5438 assert(fallbackObject && PRIORITY_INDEX, 'ChildrenNode.ts has not been loaded');
5439 _defaultIndexMap =
5440 _defaultIndexMap ||
5441 new IndexMap({ '.priority': fallbackObject }, { '.priority': PRIORITY_INDEX });
5442 return _defaultIndexMap;
5443 },
5444 enumerable: false,
5445 configurable: true
5446 });
5447 IndexMap.prototype.get = function (indexKey) {
5448 var sortedMap = safeGet(this.indexes_, indexKey);
5449 if (!sortedMap) {
5450 throw new Error('No index defined for ' + indexKey);
5451 }
5452 if (sortedMap instanceof SortedMap) {
5453 return sortedMap;
5454 }
5455 else {
5456 // The index exists, but it falls back to just name comparison. Return null so that the calling code uses the
5457 // regular child map
5458 return null;
5459 }
5460 };
5461 IndexMap.prototype.hasIndex = function (indexDefinition) {
5462 return contains(this.indexSet_, indexDefinition.toString());
5463 };
5464 IndexMap.prototype.addIndex = function (indexDefinition, existingChildren) {
5465 assert(indexDefinition !== KEY_INDEX, "KeyIndex always exists and isn't meant to be added to the IndexMap.");
5466 var childList = [];
5467 var sawIndexedValue = false;
5468 var iter = existingChildren.getIterator(NamedNode.Wrap);
5469 var next = iter.getNext();
5470 while (next) {
5471 sawIndexedValue =
5472 sawIndexedValue || indexDefinition.isDefinedOn(next.node);
5473 childList.push(next);
5474 next = iter.getNext();
5475 }
5476 var newIndex;
5477 if (sawIndexedValue) {
5478 newIndex = buildChildSet(childList, indexDefinition.getCompare());
5479 }
5480 else {
5481 newIndex = fallbackObject;
5482 }
5483 var indexName = indexDefinition.toString();
5484 var newIndexSet = __assign({}, this.indexSet_);
5485 newIndexSet[indexName] = indexDefinition;
5486 var newIndexes = __assign({}, this.indexes_);
5487 newIndexes[indexName] = newIndex;
5488 return new IndexMap(newIndexes, newIndexSet);
5489 };
5490 /**
5491 * Ensure that this node is properly tracked in any indexes that we're maintaining
5492 */
5493 IndexMap.prototype.addToIndexes = function (namedNode, existingChildren) {
5494 var _this = this;
5495 var newIndexes = map(this.indexes_, function (indexedChildren, indexName) {
5496 var index = safeGet(_this.indexSet_, indexName);
5497 assert(index, 'Missing index implementation for ' + indexName);
5498 if (indexedChildren === fallbackObject) {
5499 // Check to see if we need to index everything
5500 if (index.isDefinedOn(namedNode.node)) {
5501 // We need to build this index
5502 var childList = [];
5503 var iter = existingChildren.getIterator(NamedNode.Wrap);
5504 var next = iter.getNext();
5505 while (next) {
5506 if (next.name !== namedNode.name) {
5507 childList.push(next);
5508 }
5509 next = iter.getNext();
5510 }
5511 childList.push(namedNode);
5512 return buildChildSet(childList, index.getCompare());
5513 }
5514 else {
5515 // No change, this remains a fallback
5516 return fallbackObject;
5517 }
5518 }
5519 else {
5520 var existingSnap = existingChildren.get(namedNode.name);
5521 var newChildren = indexedChildren;
5522 if (existingSnap) {
5523 newChildren = newChildren.remove(new NamedNode(namedNode.name, existingSnap));
5524 }
5525 return newChildren.insert(namedNode, namedNode.node);
5526 }
5527 });
5528 return new IndexMap(newIndexes, this.indexSet_);
5529 };
5530 /**
5531 * Create a new IndexMap instance with the given value removed
5532 */
5533 IndexMap.prototype.removeFromIndexes = function (namedNode, existingChildren) {
5534 var newIndexes = map(this.indexes_, function (indexedChildren) {
5535 if (indexedChildren === fallbackObject) {
5536 // This is the fallback. Just return it, nothing to do in this case
5537 return indexedChildren;
5538 }
5539 else {
5540 var existingSnap = existingChildren.get(namedNode.name);
5541 if (existingSnap) {
5542 return indexedChildren.remove(new NamedNode(namedNode.name, existingSnap));
5543 }
5544 else {
5545 // No record of this child
5546 return indexedChildren;
5547 }
5548 }
5549 });
5550 return new IndexMap(newIndexes, this.indexSet_);
5551 };
5552 return IndexMap;
5553}());
5554
5555/**
5556 * @license
5557 * Copyright 2017 Google LLC
5558 *
5559 * Licensed under the Apache License, Version 2.0 (the "License");
5560 * you may not use this file except in compliance with the License.
5561 * You may obtain a copy of the License at
5562 *
5563 * http://www.apache.org/licenses/LICENSE-2.0
5564 *
5565 * Unless required by applicable law or agreed to in writing, software
5566 * distributed under the License is distributed on an "AS IS" BASIS,
5567 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5568 * See the License for the specific language governing permissions and
5569 * limitations under the License.
5570 */
5571// TODO: For memory savings, don't store priorityNode_ if it's empty.
5572var EMPTY_NODE;
5573/**
5574 * ChildrenNode is a class for storing internal nodes in a DataSnapshot
5575 * (i.e. nodes with children). It implements Node and stores the
5576 * list of children in the children property, sorted by child name.
5577 */
5578var ChildrenNode = /** @class */ (function () {
5579 /**
5580 * @param children_ - List of children of this node..
5581 * @param priorityNode_ - The priority of this node (as a snapshot node).
5582 */
5583 function ChildrenNode(children_, priorityNode_, indexMap_) {
5584 this.children_ = children_;
5585 this.priorityNode_ = priorityNode_;
5586 this.indexMap_ = indexMap_;
5587 this.lazyHash_ = null;
5588 /**
5589 * Note: The only reason we allow null priority is for EMPTY_NODE, since we can't use
5590 * EMPTY_NODE as the priority of EMPTY_NODE. We might want to consider making EMPTY_NODE its own
5591 * class instead of an empty ChildrenNode.
5592 */
5593 if (this.priorityNode_) {
5594 validatePriorityNode(this.priorityNode_);
5595 }
5596 if (this.children_.isEmpty()) {
5597 assert(!this.priorityNode_ || this.priorityNode_.isEmpty(), 'An empty node cannot have a priority');
5598 }
5599 }
5600 Object.defineProperty(ChildrenNode, "EMPTY_NODE", {
5601 get: function () {
5602 return (EMPTY_NODE ||
5603 (EMPTY_NODE = new ChildrenNode(new SortedMap(NAME_COMPARATOR), null, IndexMap.Default)));
5604 },
5605 enumerable: false,
5606 configurable: true
5607 });
5608 /** @inheritDoc */
5609 ChildrenNode.prototype.isLeafNode = function () {
5610 return false;
5611 };
5612 /** @inheritDoc */
5613 ChildrenNode.prototype.getPriority = function () {
5614 return this.priorityNode_ || EMPTY_NODE;
5615 };
5616 /** @inheritDoc */
5617 ChildrenNode.prototype.updatePriority = function (newPriorityNode) {
5618 if (this.children_.isEmpty()) {
5619 // Don't allow priorities on empty nodes
5620 return this;
5621 }
5622 else {
5623 return new ChildrenNode(this.children_, newPriorityNode, this.indexMap_);
5624 }
5625 };
5626 /** @inheritDoc */
5627 ChildrenNode.prototype.getImmediateChild = function (childName) {
5628 // Hack to treat priority as a regular child
5629 if (childName === '.priority') {
5630 return this.getPriority();
5631 }
5632 else {
5633 var child = this.children_.get(childName);
5634 return child === null ? EMPTY_NODE : child;
5635 }
5636 };
5637 /** @inheritDoc */
5638 ChildrenNode.prototype.getChild = function (path) {
5639 var front = pathGetFront(path);
5640 if (front === null) {
5641 return this;
5642 }
5643 return this.getImmediateChild(front).getChild(pathPopFront(path));
5644 };
5645 /** @inheritDoc */
5646 ChildrenNode.prototype.hasChild = function (childName) {
5647 return this.children_.get(childName) !== null;
5648 };
5649 /** @inheritDoc */
5650 ChildrenNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5651 assert(newChildNode, 'We should always be passing snapshot nodes');
5652 if (childName === '.priority') {
5653 return this.updatePriority(newChildNode);
5654 }
5655 else {
5656 var namedNode = new NamedNode(childName, newChildNode);
5657 var newChildren = void 0, newIndexMap = void 0;
5658 if (newChildNode.isEmpty()) {
5659 newChildren = this.children_.remove(childName);
5660 newIndexMap = this.indexMap_.removeFromIndexes(namedNode, this.children_);
5661 }
5662 else {
5663 newChildren = this.children_.insert(childName, newChildNode);
5664 newIndexMap = this.indexMap_.addToIndexes(namedNode, this.children_);
5665 }
5666 var newPriority = newChildren.isEmpty()
5667 ? EMPTY_NODE
5668 : this.priorityNode_;
5669 return new ChildrenNode(newChildren, newPriority, newIndexMap);
5670 }
5671 };
5672 /** @inheritDoc */
5673 ChildrenNode.prototype.updateChild = function (path, newChildNode) {
5674 var front = pathGetFront(path);
5675 if (front === null) {
5676 return newChildNode;
5677 }
5678 else {
5679 assert(pathGetFront(path) !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5680 var newImmediateChild = this.getImmediateChild(front).updateChild(pathPopFront(path), newChildNode);
5681 return this.updateImmediateChild(front, newImmediateChild);
5682 }
5683 };
5684 /** @inheritDoc */
5685 ChildrenNode.prototype.isEmpty = function () {
5686 return this.children_.isEmpty();
5687 };
5688 /** @inheritDoc */
5689 ChildrenNode.prototype.numChildren = function () {
5690 return this.children_.count();
5691 };
5692 /** @inheritDoc */
5693 ChildrenNode.prototype.val = function (exportFormat) {
5694 if (this.isEmpty()) {
5695 return null;
5696 }
5697 var obj = {};
5698 var numKeys = 0, maxKey = 0, allIntegerKeys = true;
5699 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5700 obj[key] = childNode.val(exportFormat);
5701 numKeys++;
5702 if (allIntegerKeys && ChildrenNode.INTEGER_REGEXP_.test(key)) {
5703 maxKey = Math.max(maxKey, Number(key));
5704 }
5705 else {
5706 allIntegerKeys = false;
5707 }
5708 });
5709 if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
5710 // convert to array.
5711 var array = [];
5712 // eslint-disable-next-line guard-for-in
5713 for (var key in obj) {
5714 array[key] = obj[key];
5715 }
5716 return array;
5717 }
5718 else {
5719 if (exportFormat && !this.getPriority().isEmpty()) {
5720 obj['.priority'] = this.getPriority().val();
5721 }
5722 return obj;
5723 }
5724 };
5725 /** @inheritDoc */
5726 ChildrenNode.prototype.hash = function () {
5727 if (this.lazyHash_ === null) {
5728 var toHash_1 = '';
5729 if (!this.getPriority().isEmpty()) {
5730 toHash_1 +=
5731 'priority:' +
5732 priorityHashText(this.getPriority().val()) +
5733 ':';
5734 }
5735 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5736 var childHash = childNode.hash();
5737 if (childHash !== '') {
5738 toHash_1 += ':' + key + ':' + childHash;
5739 }
5740 });
5741 this.lazyHash_ = toHash_1 === '' ? '' : sha1(toHash_1);
5742 }
5743 return this.lazyHash_;
5744 };
5745 /** @inheritDoc */
5746 ChildrenNode.prototype.getPredecessorChildName = function (childName, childNode, index) {
5747 var idx = this.resolveIndex_(index);
5748 if (idx) {
5749 var predecessor = idx.getPredecessorKey(new NamedNode(childName, childNode));
5750 return predecessor ? predecessor.name : null;
5751 }
5752 else {
5753 return this.children_.getPredecessorKey(childName);
5754 }
5755 };
5756 ChildrenNode.prototype.getFirstChildName = function (indexDefinition) {
5757 var idx = this.resolveIndex_(indexDefinition);
5758 if (idx) {
5759 var minKey = idx.minKey();
5760 return minKey && minKey.name;
5761 }
5762 else {
5763 return this.children_.minKey();
5764 }
5765 };
5766 ChildrenNode.prototype.getFirstChild = function (indexDefinition) {
5767 var minKey = this.getFirstChildName(indexDefinition);
5768 if (minKey) {
5769 return new NamedNode(minKey, this.children_.get(minKey));
5770 }
5771 else {
5772 return null;
5773 }
5774 };
5775 /**
5776 * Given an index, return the key name of the largest value we have, according to that index
5777 */
5778 ChildrenNode.prototype.getLastChildName = function (indexDefinition) {
5779 var idx = this.resolveIndex_(indexDefinition);
5780 if (idx) {
5781 var maxKey = idx.maxKey();
5782 return maxKey && maxKey.name;
5783 }
5784 else {
5785 return this.children_.maxKey();
5786 }
5787 };
5788 ChildrenNode.prototype.getLastChild = function (indexDefinition) {
5789 var maxKey = this.getLastChildName(indexDefinition);
5790 if (maxKey) {
5791 return new NamedNode(maxKey, this.children_.get(maxKey));
5792 }
5793 else {
5794 return null;
5795 }
5796 };
5797 ChildrenNode.prototype.forEachChild = function (index, action) {
5798 var idx = this.resolveIndex_(index);
5799 if (idx) {
5800 return idx.inorderTraversal(function (wrappedNode) {
5801 return action(wrappedNode.name, wrappedNode.node);
5802 });
5803 }
5804 else {
5805 return this.children_.inorderTraversal(action);
5806 }
5807 };
5808 ChildrenNode.prototype.getIterator = function (indexDefinition) {
5809 return this.getIteratorFrom(indexDefinition.minPost(), indexDefinition);
5810 };
5811 ChildrenNode.prototype.getIteratorFrom = function (startPost, indexDefinition) {
5812 var idx = this.resolveIndex_(indexDefinition);
5813 if (idx) {
5814 return idx.getIteratorFrom(startPost, function (key) { return key; });
5815 }
5816 else {
5817 var iterator = this.children_.getIteratorFrom(startPost.name, NamedNode.Wrap);
5818 var next = iterator.peek();
5819 while (next != null && indexDefinition.compare(next, startPost) < 0) {
5820 iterator.getNext();
5821 next = iterator.peek();
5822 }
5823 return iterator;
5824 }
5825 };
5826 ChildrenNode.prototype.getReverseIterator = function (indexDefinition) {
5827 return this.getReverseIteratorFrom(indexDefinition.maxPost(), indexDefinition);
5828 };
5829 ChildrenNode.prototype.getReverseIteratorFrom = function (endPost, indexDefinition) {
5830 var idx = this.resolveIndex_(indexDefinition);
5831 if (idx) {
5832 return idx.getReverseIteratorFrom(endPost, function (key) {
5833 return key;
5834 });
5835 }
5836 else {
5837 var iterator = this.children_.getReverseIteratorFrom(endPost.name, NamedNode.Wrap);
5838 var next = iterator.peek();
5839 while (next != null && indexDefinition.compare(next, endPost) > 0) {
5840 iterator.getNext();
5841 next = iterator.peek();
5842 }
5843 return iterator;
5844 }
5845 };
5846 ChildrenNode.prototype.compareTo = function (other) {
5847 if (this.isEmpty()) {
5848 if (other.isEmpty()) {
5849 return 0;
5850 }
5851 else {
5852 return -1;
5853 }
5854 }
5855 else if (other.isLeafNode() || other.isEmpty()) {
5856 return 1;
5857 }
5858 else if (other === MAX_NODE) {
5859 return -1;
5860 }
5861 else {
5862 // Must be another node with children.
5863 return 0;
5864 }
5865 };
5866 ChildrenNode.prototype.withIndex = function (indexDefinition) {
5867 if (indexDefinition === KEY_INDEX ||
5868 this.indexMap_.hasIndex(indexDefinition)) {
5869 return this;
5870 }
5871 else {
5872 var newIndexMap = this.indexMap_.addIndex(indexDefinition, this.children_);
5873 return new ChildrenNode(this.children_, this.priorityNode_, newIndexMap);
5874 }
5875 };
5876 ChildrenNode.prototype.isIndexed = function (index) {
5877 return index === KEY_INDEX || this.indexMap_.hasIndex(index);
5878 };
5879 ChildrenNode.prototype.equals = function (other) {
5880 if (other === this) {
5881 return true;
5882 }
5883 else if (other.isLeafNode()) {
5884 return false;
5885 }
5886 else {
5887 var otherChildrenNode = other;
5888 if (!this.getPriority().equals(otherChildrenNode.getPriority())) {
5889 return false;
5890 }
5891 else if (this.children_.count() === otherChildrenNode.children_.count()) {
5892 var thisIter = this.getIterator(PRIORITY_INDEX);
5893 var otherIter = otherChildrenNode.getIterator(PRIORITY_INDEX);
5894 var thisCurrent = thisIter.getNext();
5895 var otherCurrent = otherIter.getNext();
5896 while (thisCurrent && otherCurrent) {
5897 if (thisCurrent.name !== otherCurrent.name ||
5898 !thisCurrent.node.equals(otherCurrent.node)) {
5899 return false;
5900 }
5901 thisCurrent = thisIter.getNext();
5902 otherCurrent = otherIter.getNext();
5903 }
5904 return thisCurrent === null && otherCurrent === null;
5905 }
5906 else {
5907 return false;
5908 }
5909 }
5910 };
5911 /**
5912 * Returns a SortedMap ordered by index, or null if the default (by-key) ordering can be used
5913 * instead.
5914 *
5915 */
5916 ChildrenNode.prototype.resolveIndex_ = function (indexDefinition) {
5917 if (indexDefinition === KEY_INDEX) {
5918 return null;
5919 }
5920 else {
5921 return this.indexMap_.get(indexDefinition.toString());
5922 }
5923 };
5924 ChildrenNode.INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;
5925 return ChildrenNode;
5926}());
5927var MaxNode = /** @class */ (function (_super) {
5928 __extends(MaxNode, _super);
5929 function MaxNode() {
5930 return _super.call(this, new SortedMap(NAME_COMPARATOR), ChildrenNode.EMPTY_NODE, IndexMap.Default) || this;
5931 }
5932 MaxNode.prototype.compareTo = function (other) {
5933 if (other === this) {
5934 return 0;
5935 }
5936 else {
5937 return 1;
5938 }
5939 };
5940 MaxNode.prototype.equals = function (other) {
5941 // Not that we every compare it, but MAX_NODE is only ever equal to itself
5942 return other === this;
5943 };
5944 MaxNode.prototype.getPriority = function () {
5945 return this;
5946 };
5947 MaxNode.prototype.getImmediateChild = function (childName) {
5948 return ChildrenNode.EMPTY_NODE;
5949 };
5950 MaxNode.prototype.isEmpty = function () {
5951 return false;
5952 };
5953 return MaxNode;
5954}(ChildrenNode));
5955/**
5956 * Marker that will sort higher than any other snapshot.
5957 */
5958var MAX_NODE = new MaxNode();
5959Object.defineProperties(NamedNode, {
5960 MIN: {
5961 value: new NamedNode(MIN_NAME, ChildrenNode.EMPTY_NODE)
5962 },
5963 MAX: {
5964 value: new NamedNode(MAX_NAME, MAX_NODE)
5965 }
5966});
5967/**
5968 * Reference Extensions
5969 */
5970KeyIndex.__EMPTY_NODE = ChildrenNode.EMPTY_NODE;
5971LeafNode.__childrenNodeConstructor = ChildrenNode;
5972setMaxNode$1(MAX_NODE);
5973setMaxNode(MAX_NODE);
5974
5975/**
5976 * @license
5977 * Copyright 2017 Google LLC
5978 *
5979 * Licensed under the Apache License, Version 2.0 (the "License");
5980 * you may not use this file except in compliance with the License.
5981 * You may obtain a copy of the License at
5982 *
5983 * http://www.apache.org/licenses/LICENSE-2.0
5984 *
5985 * Unless required by applicable law or agreed to in writing, software
5986 * distributed under the License is distributed on an "AS IS" BASIS,
5987 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5988 * See the License for the specific language governing permissions and
5989 * limitations under the License.
5990 */
5991var USE_HINZE = true;
5992/**
5993 * Constructs a snapshot node representing the passed JSON and returns it.
5994 * @param json - JSON to create a node for.
5995 * @param priority - Optional priority to use. This will be ignored if the
5996 * passed JSON contains a .priority property.
5997 */
5998function nodeFromJSON(json, priority) {
5999 if (priority === void 0) { priority = null; }
6000 if (json === null) {
6001 return ChildrenNode.EMPTY_NODE;
6002 }
6003 if (typeof json === 'object' && '.priority' in json) {
6004 priority = json['.priority'];
6005 }
6006 assert(priority === null ||
6007 typeof priority === 'string' ||
6008 typeof priority === 'number' ||
6009 (typeof priority === 'object' && '.sv' in priority), 'Invalid priority type found: ' + typeof priority);
6010 if (typeof json === 'object' && '.value' in json && json['.value'] !== null) {
6011 json = json['.value'];
6012 }
6013 // Valid leaf nodes include non-objects or server-value wrapper objects
6014 if (typeof json !== 'object' || '.sv' in json) {
6015 var jsonLeaf = json;
6016 return new LeafNode(jsonLeaf, nodeFromJSON(priority));
6017 }
6018 if (!(json instanceof Array) && USE_HINZE) {
6019 var children_1 = [];
6020 var childrenHavePriority_1 = false;
6021 var hinzeJsonObj = json;
6022 each(hinzeJsonObj, function (key, child) {
6023 if (key.substring(0, 1) !== '.') {
6024 // Ignore metadata nodes
6025 var childNode = nodeFromJSON(child);
6026 if (!childNode.isEmpty()) {
6027 childrenHavePriority_1 =
6028 childrenHavePriority_1 || !childNode.getPriority().isEmpty();
6029 children_1.push(new NamedNode(key, childNode));
6030 }
6031 }
6032 });
6033 if (children_1.length === 0) {
6034 return ChildrenNode.EMPTY_NODE;
6035 }
6036 var childSet = buildChildSet(children_1, NAME_ONLY_COMPARATOR, function (namedNode) { return namedNode.name; }, NAME_COMPARATOR);
6037 if (childrenHavePriority_1) {
6038 var sortedChildSet = buildChildSet(children_1, PRIORITY_INDEX.getCompare());
6039 return new ChildrenNode(childSet, nodeFromJSON(priority), new IndexMap({ '.priority': sortedChildSet }, { '.priority': PRIORITY_INDEX }));
6040 }
6041 else {
6042 return new ChildrenNode(childSet, nodeFromJSON(priority), IndexMap.Default);
6043 }
6044 }
6045 else {
6046 var node_1 = ChildrenNode.EMPTY_NODE;
6047 each(json, function (key, childData) {
6048 if (contains(json, key)) {
6049 if (key.substring(0, 1) !== '.') {
6050 // ignore metadata nodes.
6051 var childNode = nodeFromJSON(childData);
6052 if (childNode.isLeafNode() || !childNode.isEmpty()) {
6053 node_1 = node_1.updateImmediateChild(key, childNode);
6054 }
6055 }
6056 }
6057 });
6058 return node_1.updatePriority(nodeFromJSON(priority));
6059 }
6060}
6061setNodeFromJSON(nodeFromJSON);
6062
6063/**
6064 * @license
6065 * Copyright 2017 Google LLC
6066 *
6067 * Licensed under the Apache License, Version 2.0 (the "License");
6068 * you may not use this file except in compliance with the License.
6069 * You may obtain a copy of the License at
6070 *
6071 * http://www.apache.org/licenses/LICENSE-2.0
6072 *
6073 * Unless required by applicable law or agreed to in writing, software
6074 * distributed under the License is distributed on an "AS IS" BASIS,
6075 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6076 * See the License for the specific language governing permissions and
6077 * limitations under the License.
6078 */
6079var PathIndex = /** @class */ (function (_super) {
6080 __extends(PathIndex, _super);
6081 function PathIndex(indexPath_) {
6082 var _this = _super.call(this) || this;
6083 _this.indexPath_ = indexPath_;
6084 assert(!pathIsEmpty(indexPath_) && pathGetFront(indexPath_) !== '.priority', "Can't create PathIndex with empty path or .priority key");
6085 return _this;
6086 }
6087 PathIndex.prototype.extractChild = function (snap) {
6088 return snap.getChild(this.indexPath_);
6089 };
6090 PathIndex.prototype.isDefinedOn = function (node) {
6091 return !node.getChild(this.indexPath_).isEmpty();
6092 };
6093 PathIndex.prototype.compare = function (a, b) {
6094 var aChild = this.extractChild(a.node);
6095 var bChild = this.extractChild(b.node);
6096 var indexCmp = aChild.compareTo(bChild);
6097 if (indexCmp === 0) {
6098 return nameCompare(a.name, b.name);
6099 }
6100 else {
6101 return indexCmp;
6102 }
6103 };
6104 PathIndex.prototype.makePost = function (indexValue, name) {
6105 var valueNode = nodeFromJSON(indexValue);
6106 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, valueNode);
6107 return new NamedNode(name, node);
6108 };
6109 PathIndex.prototype.maxPost = function () {
6110 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, MAX_NODE);
6111 return new NamedNode(MAX_NAME, node);
6112 };
6113 PathIndex.prototype.toString = function () {
6114 return pathSlice(this.indexPath_, 0).join('/');
6115 };
6116 return PathIndex;
6117}(Index));
6118
6119/**
6120 * @license
6121 * Copyright 2017 Google LLC
6122 *
6123 * Licensed under the Apache License, Version 2.0 (the "License");
6124 * you may not use this file except in compliance with the License.
6125 * You may obtain a copy of the License at
6126 *
6127 * http://www.apache.org/licenses/LICENSE-2.0
6128 *
6129 * Unless required by applicable law or agreed to in writing, software
6130 * distributed under the License is distributed on an "AS IS" BASIS,
6131 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6132 * See the License for the specific language governing permissions and
6133 * limitations under the License.
6134 */
6135var ValueIndex = /** @class */ (function (_super) {
6136 __extends(ValueIndex, _super);
6137 function ValueIndex() {
6138 return _super !== null && _super.apply(this, arguments) || this;
6139 }
6140 ValueIndex.prototype.compare = function (a, b) {
6141 var indexCmp = a.node.compareTo(b.node);
6142 if (indexCmp === 0) {
6143 return nameCompare(a.name, b.name);
6144 }
6145 else {
6146 return indexCmp;
6147 }
6148 };
6149 ValueIndex.prototype.isDefinedOn = function (node) {
6150 return true;
6151 };
6152 ValueIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
6153 return !oldNode.equals(newNode);
6154 };
6155 ValueIndex.prototype.minPost = function () {
6156 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6157 return NamedNode.MIN;
6158 };
6159 ValueIndex.prototype.maxPost = function () {
6160 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6161 return NamedNode.MAX;
6162 };
6163 ValueIndex.prototype.makePost = function (indexValue, name) {
6164 var valueNode = nodeFromJSON(indexValue);
6165 return new NamedNode(name, valueNode);
6166 };
6167 /**
6168 * @returns String representation for inclusion in a query spec
6169 */
6170 ValueIndex.prototype.toString = function () {
6171 return '.value';
6172 };
6173 return ValueIndex;
6174}(Index));
6175var VALUE_INDEX = new ValueIndex();
6176
6177/**
6178 * @license
6179 * Copyright 2017 Google LLC
6180 *
6181 * Licensed under the Apache License, Version 2.0 (the "License");
6182 * you may not use this file except in compliance with the License.
6183 * You may obtain a copy of the License at
6184 *
6185 * http://www.apache.org/licenses/LICENSE-2.0
6186 *
6187 * Unless required by applicable law or agreed to in writing, software
6188 * distributed under the License is distributed on an "AS IS" BASIS,
6189 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6190 * See the License for the specific language governing permissions and
6191 * limitations under the License.
6192 */
6193// Modeled after base64 web-safe chars, but ordered by ASCII.
6194var PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
6195var MIN_PUSH_CHAR = '-';
6196var MAX_PUSH_CHAR = 'z';
6197var MAX_KEY_LEN = 786;
6198/**
6199 * Fancy ID generator that creates 20-character string identifiers with the
6200 * following properties:
6201 *
6202 * 1. They're based on timestamp so that they sort *after* any existing ids.
6203 * 2. They contain 72-bits of random data after the timestamp so that IDs won't
6204 * collide with other clients' IDs.
6205 * 3. They sort *lexicographically* (so the timestamp is converted to characters
6206 * that will sort properly).
6207 * 4. They're monotonically increasing. Even if you generate more than one in
6208 * the same timestamp, the latter ones will sort after the former ones. We do
6209 * this by using the previous random bits but "incrementing" them by 1 (only
6210 * in the case of a timestamp collision).
6211 */
6212var nextPushId = (function () {
6213 // Timestamp of last push, used to prevent local collisions if you push twice
6214 // in one ms.
6215 var lastPushTime = 0;
6216 // We generate 72-bits of randomness which get turned into 12 characters and
6217 // appended to the timestamp to prevent collisions with other clients. We
6218 // store the last characters we generated because in the event of a collision,
6219 // we'll use those same characters except "incremented" by one.
6220 var lastRandChars = [];
6221 return function (now) {
6222 var duplicateTime = now === lastPushTime;
6223 lastPushTime = now;
6224 var i;
6225 var timeStampChars = new Array(8);
6226 for (i = 7; i >= 0; i--) {
6227 timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
6228 // NOTE: Can't use << here because javascript will convert to int and lose
6229 // the upper bits.
6230 now = Math.floor(now / 64);
6231 }
6232 assert(now === 0, 'Cannot push at time == 0');
6233 var id = timeStampChars.join('');
6234 if (!duplicateTime) {
6235 for (i = 0; i < 12; i++) {
6236 lastRandChars[i] = Math.floor(Math.random() * 64);
6237 }
6238 }
6239 else {
6240 // If the timestamp hasn't changed since last push, use the same random
6241 // number, except incremented by 1.
6242 for (i = 11; i >= 0 && lastRandChars[i] === 63; i--) {
6243 lastRandChars[i] = 0;
6244 }
6245 lastRandChars[i]++;
6246 }
6247 for (i = 0; i < 12; i++) {
6248 id += PUSH_CHARS.charAt(lastRandChars[i]);
6249 }
6250 assert(id.length === 20, 'nextPushId: Length should be 20.');
6251 return id;
6252 };
6253})();
6254var successor = function (key) {
6255 if (key === '' + INTEGER_32_MAX) {
6256 // See https://firebase.google.com/docs/database/web/lists-of-data#data-order
6257 return MIN_PUSH_CHAR;
6258 }
6259 var keyAsInt = tryParseInt(key);
6260 if (keyAsInt != null) {
6261 return '' + (keyAsInt + 1);
6262 }
6263 var next = new Array(key.length);
6264 for (var i_1 = 0; i_1 < next.length; i_1++) {
6265 next[i_1] = key.charAt(i_1);
6266 }
6267 if (next.length < MAX_KEY_LEN) {
6268 next.push(MIN_PUSH_CHAR);
6269 return next.join('');
6270 }
6271 var i = next.length - 1;
6272 while (i >= 0 && next[i] === MAX_PUSH_CHAR) {
6273 i--;
6274 }
6275 // `successor` was called on the largest possible key, so return the
6276 // MAX_NAME, which sorts larger than all keys.
6277 if (i === -1) {
6278 return MAX_NAME;
6279 }
6280 var source = next[i];
6281 var sourcePlusOne = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(source) + 1);
6282 next[i] = sourcePlusOne;
6283 return next.slice(0, i + 1).join('');
6284};
6285// `key` is assumed to be non-empty.
6286var predecessor = function (key) {
6287 if (key === '' + INTEGER_32_MIN) {
6288 return MIN_NAME;
6289 }
6290 var keyAsInt = tryParseInt(key);
6291 if (keyAsInt != null) {
6292 return '' + (keyAsInt - 1);
6293 }
6294 var next = new Array(key.length);
6295 for (var i = 0; i < next.length; i++) {
6296 next[i] = key.charAt(i);
6297 }
6298 // If `key` ends in `MIN_PUSH_CHAR`, the largest key lexicographically
6299 // smaller than `key`, is `key[0:key.length - 1]`. The next key smaller
6300 // than that, `predecessor(predecessor(key))`, is
6301 //
6302 // `key[0:key.length - 2] + (key[key.length - 1] - 1) + \
6303 // { MAX_PUSH_CHAR repeated MAX_KEY_LEN - (key.length - 1) times }
6304 //
6305 // analogous to increment/decrement for base-10 integers.
6306 //
6307 // This works because lexigographic comparison works character-by-character,
6308 // using length as a tie-breaker if one key is a prefix of the other.
6309 if (next[next.length - 1] === MIN_PUSH_CHAR) {
6310 if (next.length === 1) {
6311 // See https://firebase.google.com/docs/database/web/lists-of-data#orderbykey
6312 return '' + INTEGER_32_MAX;
6313 }
6314 delete next[next.length - 1];
6315 return next.join('');
6316 }
6317 // Replace the last character with it's immediate predecessor, and
6318 // fill the suffix of the key with MAX_PUSH_CHAR. This is the
6319 // lexicographically largest possible key smaller than `key`.
6320 next[next.length - 1] = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(next[next.length - 1]) - 1);
6321 return next.join('') + MAX_PUSH_CHAR.repeat(MAX_KEY_LEN - next.length);
6322};
6323
6324/**
6325 * @license
6326 * Copyright 2017 Google LLC
6327 *
6328 * Licensed under the Apache License, Version 2.0 (the "License");
6329 * you may not use this file except in compliance with the License.
6330 * You may obtain a copy of the License at
6331 *
6332 * http://www.apache.org/licenses/LICENSE-2.0
6333 *
6334 * Unless required by applicable law or agreed to in writing, software
6335 * distributed under the License is distributed on an "AS IS" BASIS,
6336 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6337 * See the License for the specific language governing permissions and
6338 * limitations under the License.
6339 */
6340function changeValue(snapshotNode) {
6341 return { type: "value" /* VALUE */, snapshotNode: snapshotNode };
6342}
6343function changeChildAdded(childName, snapshotNode) {
6344 return { type: "child_added" /* CHILD_ADDED */, snapshotNode: snapshotNode, childName: childName };
6345}
6346function changeChildRemoved(childName, snapshotNode) {
6347 return { type: "child_removed" /* CHILD_REMOVED */, snapshotNode: snapshotNode, childName: childName };
6348}
6349function changeChildChanged(childName, snapshotNode, oldSnap) {
6350 return {
6351 type: "child_changed" /* CHILD_CHANGED */,
6352 snapshotNode: snapshotNode,
6353 childName: childName,
6354 oldSnap: oldSnap
6355 };
6356}
6357function changeChildMoved(childName, snapshotNode) {
6358 return { type: "child_moved" /* CHILD_MOVED */, snapshotNode: snapshotNode, childName: childName };
6359}
6360
6361/**
6362 * @license
6363 * Copyright 2017 Google LLC
6364 *
6365 * Licensed under the Apache License, Version 2.0 (the "License");
6366 * you may not use this file except in compliance with the License.
6367 * You may obtain a copy of the License at
6368 *
6369 * http://www.apache.org/licenses/LICENSE-2.0
6370 *
6371 * Unless required by applicable law or agreed to in writing, software
6372 * distributed under the License is distributed on an "AS IS" BASIS,
6373 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6374 * See the License for the specific language governing permissions and
6375 * limitations under the License.
6376 */
6377/**
6378 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
6379 */
6380var IndexedFilter = /** @class */ (function () {
6381 function IndexedFilter(index_) {
6382 this.index_ = index_;
6383 }
6384 IndexedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6385 assert(snap.isIndexed(this.index_), 'A node must be indexed if only a child is updated');
6386 var oldChild = snap.getImmediateChild(key);
6387 // Check if anything actually changed.
6388 if (oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))) {
6389 // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
6390 // In this case, affectedPath will appear null in both the old and new snapshots. So we need
6391 // to avoid treating these cases as "nothing changed."
6392 if (oldChild.isEmpty() === newChild.isEmpty()) {
6393 // Nothing changed.
6394 // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
6395 //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
6396 return snap;
6397 }
6398 }
6399 if (optChangeAccumulator != null) {
6400 if (newChild.isEmpty()) {
6401 if (snap.hasChild(key)) {
6402 optChangeAccumulator.trackChildChange(changeChildRemoved(key, oldChild));
6403 }
6404 else {
6405 assert(snap.isLeafNode(), 'A child remove without an old child only makes sense on a leaf node');
6406 }
6407 }
6408 else if (oldChild.isEmpty()) {
6409 optChangeAccumulator.trackChildChange(changeChildAdded(key, newChild));
6410 }
6411 else {
6412 optChangeAccumulator.trackChildChange(changeChildChanged(key, newChild, oldChild));
6413 }
6414 }
6415 if (snap.isLeafNode() && newChild.isEmpty()) {
6416 return snap;
6417 }
6418 else {
6419 // Make sure the node is indexed
6420 return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
6421 }
6422 };
6423 IndexedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6424 if (optChangeAccumulator != null) {
6425 if (!oldSnap.isLeafNode()) {
6426 oldSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6427 if (!newSnap.hasChild(key)) {
6428 optChangeAccumulator.trackChildChange(changeChildRemoved(key, childNode));
6429 }
6430 });
6431 }
6432 if (!newSnap.isLeafNode()) {
6433 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6434 if (oldSnap.hasChild(key)) {
6435 var oldChild = oldSnap.getImmediateChild(key);
6436 if (!oldChild.equals(childNode)) {
6437 optChangeAccumulator.trackChildChange(changeChildChanged(key, childNode, oldChild));
6438 }
6439 }
6440 else {
6441 optChangeAccumulator.trackChildChange(changeChildAdded(key, childNode));
6442 }
6443 });
6444 }
6445 }
6446 return newSnap.withIndex(this.index_);
6447 };
6448 IndexedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6449 if (oldSnap.isEmpty()) {
6450 return ChildrenNode.EMPTY_NODE;
6451 }
6452 else {
6453 return oldSnap.updatePriority(newPriority);
6454 }
6455 };
6456 IndexedFilter.prototype.filtersNodes = function () {
6457 return false;
6458 };
6459 IndexedFilter.prototype.getIndexedFilter = function () {
6460 return this;
6461 };
6462 IndexedFilter.prototype.getIndex = function () {
6463 return this.index_;
6464 };
6465 return IndexedFilter;
6466}());
6467
6468/**
6469 * @license
6470 * Copyright 2017 Google LLC
6471 *
6472 * Licensed under the Apache License, Version 2.0 (the "License");
6473 * you may not use this file except in compliance with the License.
6474 * You may obtain a copy of the License at
6475 *
6476 * http://www.apache.org/licenses/LICENSE-2.0
6477 *
6478 * Unless required by applicable law or agreed to in writing, software
6479 * distributed under the License is distributed on an "AS IS" BASIS,
6480 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6481 * See the License for the specific language governing permissions and
6482 * limitations under the License.
6483 */
6484/**
6485 * Filters nodes by range and uses an IndexFilter to track any changes after filtering the node
6486 */
6487var RangedFilter = /** @class */ (function () {
6488 function RangedFilter(params) {
6489 this.indexedFilter_ = new IndexedFilter(params.getIndex());
6490 this.index_ = params.getIndex();
6491 this.startPost_ = RangedFilter.getStartPost_(params);
6492 this.endPost_ = RangedFilter.getEndPost_(params);
6493 }
6494 RangedFilter.prototype.getStartPost = function () {
6495 return this.startPost_;
6496 };
6497 RangedFilter.prototype.getEndPost = function () {
6498 return this.endPost_;
6499 };
6500 RangedFilter.prototype.matches = function (node) {
6501 return (this.index_.compare(this.getStartPost(), node) <= 0 &&
6502 this.index_.compare(node, this.getEndPost()) <= 0);
6503 };
6504 RangedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6505 if (!this.matches(new NamedNode(key, newChild))) {
6506 newChild = ChildrenNode.EMPTY_NODE;
6507 }
6508 return this.indexedFilter_.updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6509 };
6510 RangedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6511 if (newSnap.isLeafNode()) {
6512 // Make sure we have a children node with the correct index, not a leaf node;
6513 newSnap = ChildrenNode.EMPTY_NODE;
6514 }
6515 var filtered = newSnap.withIndex(this.index_);
6516 // Don't support priorities on queries
6517 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6518 var self = this;
6519 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6520 if (!self.matches(new NamedNode(key, childNode))) {
6521 filtered = filtered.updateImmediateChild(key, ChildrenNode.EMPTY_NODE);
6522 }
6523 });
6524 return this.indexedFilter_.updateFullNode(oldSnap, filtered, optChangeAccumulator);
6525 };
6526 RangedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6527 // Don't support priorities on queries
6528 return oldSnap;
6529 };
6530 RangedFilter.prototype.filtersNodes = function () {
6531 return true;
6532 };
6533 RangedFilter.prototype.getIndexedFilter = function () {
6534 return this.indexedFilter_;
6535 };
6536 RangedFilter.prototype.getIndex = function () {
6537 return this.index_;
6538 };
6539 RangedFilter.getStartPost_ = function (params) {
6540 if (params.hasStart()) {
6541 var startName = params.getIndexStartName();
6542 return params.getIndex().makePost(params.getIndexStartValue(), startName);
6543 }
6544 else {
6545 return params.getIndex().minPost();
6546 }
6547 };
6548 RangedFilter.getEndPost_ = function (params) {
6549 if (params.hasEnd()) {
6550 var endName = params.getIndexEndName();
6551 return params.getIndex().makePost(params.getIndexEndValue(), endName);
6552 }
6553 else {
6554 return params.getIndex().maxPost();
6555 }
6556 };
6557 return RangedFilter;
6558}());
6559
6560/**
6561 * @license
6562 * Copyright 2017 Google LLC
6563 *
6564 * Licensed under the Apache License, Version 2.0 (the "License");
6565 * you may not use this file except in compliance with the License.
6566 * You may obtain a copy of the License at
6567 *
6568 * http://www.apache.org/licenses/LICENSE-2.0
6569 *
6570 * Unless required by applicable law or agreed to in writing, software
6571 * distributed under the License is distributed on an "AS IS" BASIS,
6572 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6573 * See the License for the specific language governing permissions and
6574 * limitations under the License.
6575 */
6576/**
6577 * Applies a limit and a range to a node and uses RangedFilter to do the heavy lifting where possible
6578 */
6579var LimitedFilter = /** @class */ (function () {
6580 function LimitedFilter(params) {
6581 this.rangedFilter_ = new RangedFilter(params);
6582 this.index_ = params.getIndex();
6583 this.limit_ = params.getLimit();
6584 this.reverse_ = !params.isViewFromLeft();
6585 }
6586 LimitedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6587 if (!this.rangedFilter_.matches(new NamedNode(key, newChild))) {
6588 newChild = ChildrenNode.EMPTY_NODE;
6589 }
6590 if (snap.getImmediateChild(key).equals(newChild)) {
6591 // No change
6592 return snap;
6593 }
6594 else if (snap.numChildren() < this.limit_) {
6595 return this.rangedFilter_
6596 .getIndexedFilter()
6597 .updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6598 }
6599 else {
6600 return this.fullLimitUpdateChild_(snap, key, newChild, source, optChangeAccumulator);
6601 }
6602 };
6603 LimitedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6604 var filtered;
6605 if (newSnap.isLeafNode() || newSnap.isEmpty()) {
6606 // Make sure we have a children node with the correct index, not a leaf node;
6607 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6608 }
6609 else {
6610 if (this.limit_ * 2 < newSnap.numChildren() &&
6611 newSnap.isIndexed(this.index_)) {
6612 // Easier to build up a snapshot, since what we're given has more than twice the elements we want
6613 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6614 // anchor to the startPost, endPost, or last element as appropriate
6615 var iterator = void 0;
6616 if (this.reverse_) {
6617 iterator = newSnap.getReverseIteratorFrom(this.rangedFilter_.getEndPost(), this.index_);
6618 }
6619 else {
6620 iterator = newSnap.getIteratorFrom(this.rangedFilter_.getStartPost(), this.index_);
6621 }
6622 var count = 0;
6623 while (iterator.hasNext() && count < this.limit_) {
6624 var next = iterator.getNext();
6625 var inRange = void 0;
6626 if (this.reverse_) {
6627 inRange =
6628 this.index_.compare(this.rangedFilter_.getStartPost(), next) <= 0;
6629 }
6630 else {
6631 inRange =
6632 this.index_.compare(next, this.rangedFilter_.getEndPost()) <= 0;
6633 }
6634 if (inRange) {
6635 filtered = filtered.updateImmediateChild(next.name, next.node);
6636 count++;
6637 }
6638 else {
6639 // if we have reached the end post, we cannot keep adding elemments
6640 break;
6641 }
6642 }
6643 }
6644 else {
6645 // The snap contains less than twice the limit. Faster to delete from the snap than build up a new one
6646 filtered = newSnap.withIndex(this.index_);
6647 // Don't support priorities on queries
6648 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6649 var startPost = void 0;
6650 var endPost = void 0;
6651 var cmp = void 0;
6652 var iterator = void 0;
6653 if (this.reverse_) {
6654 iterator = filtered.getReverseIterator(this.index_);
6655 startPost = this.rangedFilter_.getEndPost();
6656 endPost = this.rangedFilter_.getStartPost();
6657 var indexCompare_1 = this.index_.getCompare();
6658 cmp = function (a, b) { return indexCompare_1(b, a); };
6659 }
6660 else {
6661 iterator = filtered.getIterator(this.index_);
6662 startPost = this.rangedFilter_.getStartPost();
6663 endPost = this.rangedFilter_.getEndPost();
6664 cmp = this.index_.getCompare();
6665 }
6666 var count = 0;
6667 var foundStartPost = false;
6668 while (iterator.hasNext()) {
6669 var next = iterator.getNext();
6670 if (!foundStartPost && cmp(startPost, next) <= 0) {
6671 // start adding
6672 foundStartPost = true;
6673 }
6674 var inRange = foundStartPost && count < this.limit_ && cmp(next, endPost) <= 0;
6675 if (inRange) {
6676 count++;
6677 }
6678 else {
6679 filtered = filtered.updateImmediateChild(next.name, ChildrenNode.EMPTY_NODE);
6680 }
6681 }
6682 }
6683 }
6684 return this.rangedFilter_
6685 .getIndexedFilter()
6686 .updateFullNode(oldSnap, filtered, optChangeAccumulator);
6687 };
6688 LimitedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6689 // Don't support priorities on queries
6690 return oldSnap;
6691 };
6692 LimitedFilter.prototype.filtersNodes = function () {
6693 return true;
6694 };
6695 LimitedFilter.prototype.getIndexedFilter = function () {
6696 return this.rangedFilter_.getIndexedFilter();
6697 };
6698 LimitedFilter.prototype.getIndex = function () {
6699 return this.index_;
6700 };
6701 LimitedFilter.prototype.fullLimitUpdateChild_ = function (snap, childKey, childSnap, source, changeAccumulator) {
6702 // TODO: rename all cache stuff etc to general snap terminology
6703 var cmp;
6704 if (this.reverse_) {
6705 var indexCmp_1 = this.index_.getCompare();
6706 cmp = function (a, b) { return indexCmp_1(b, a); };
6707 }
6708 else {
6709 cmp = this.index_.getCompare();
6710 }
6711 var oldEventCache = snap;
6712 assert(oldEventCache.numChildren() === this.limit_, '');
6713 var newChildNamedNode = new NamedNode(childKey, childSnap);
6714 var windowBoundary = this.reverse_
6715 ? oldEventCache.getFirstChild(this.index_)
6716 : oldEventCache.getLastChild(this.index_);
6717 var inRange = this.rangedFilter_.matches(newChildNamedNode);
6718 if (oldEventCache.hasChild(childKey)) {
6719 var oldChildSnap = oldEventCache.getImmediateChild(childKey);
6720 var nextChild = source.getChildAfterChild(this.index_, windowBoundary, this.reverse_);
6721 while (nextChild != null &&
6722 (nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))) {
6723 // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
6724 // been applied to the limited filter yet. Ignore this next child which will be updated later in
6725 // the limited filter...
6726 nextChild = source.getChildAfterChild(this.index_, nextChild, this.reverse_);
6727 }
6728 var compareNext = nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
6729 var remainsInWindow = inRange && !childSnap.isEmpty() && compareNext >= 0;
6730 if (remainsInWindow) {
6731 if (changeAccumulator != null) {
6732 changeAccumulator.trackChildChange(changeChildChanged(childKey, childSnap, oldChildSnap));
6733 }
6734 return oldEventCache.updateImmediateChild(childKey, childSnap);
6735 }
6736 else {
6737 if (changeAccumulator != null) {
6738 changeAccumulator.trackChildChange(changeChildRemoved(childKey, oldChildSnap));
6739 }
6740 var newEventCache = oldEventCache.updateImmediateChild(childKey, ChildrenNode.EMPTY_NODE);
6741 var nextChildInRange = nextChild != null && this.rangedFilter_.matches(nextChild);
6742 if (nextChildInRange) {
6743 if (changeAccumulator != null) {
6744 changeAccumulator.trackChildChange(changeChildAdded(nextChild.name, nextChild.node));
6745 }
6746 return newEventCache.updateImmediateChild(nextChild.name, nextChild.node);
6747 }
6748 else {
6749 return newEventCache;
6750 }
6751 }
6752 }
6753 else if (childSnap.isEmpty()) {
6754 // we're deleting a node, but it was not in the window, so ignore it
6755 return snap;
6756 }
6757 else if (inRange) {
6758 if (cmp(windowBoundary, newChildNamedNode) >= 0) {
6759 if (changeAccumulator != null) {
6760 changeAccumulator.trackChildChange(changeChildRemoved(windowBoundary.name, windowBoundary.node));
6761 changeAccumulator.trackChildChange(changeChildAdded(childKey, childSnap));
6762 }
6763 return oldEventCache
6764 .updateImmediateChild(childKey, childSnap)
6765 .updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
6766 }
6767 else {
6768 return snap;
6769 }
6770 }
6771 else {
6772 return snap;
6773 }
6774 };
6775 return LimitedFilter;
6776}());
6777
6778/**
6779 * @license
6780 * Copyright 2017 Google LLC
6781 *
6782 * Licensed under the Apache License, Version 2.0 (the "License");
6783 * you may not use this file except in compliance with the License.
6784 * You may obtain a copy of the License at
6785 *
6786 * http://www.apache.org/licenses/LICENSE-2.0
6787 *
6788 * Unless required by applicable law or agreed to in writing, software
6789 * distributed under the License is distributed on an "AS IS" BASIS,
6790 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6791 * See the License for the specific language governing permissions and
6792 * limitations under the License.
6793 */
6794/**
6795 * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a
6796 * range to be returned for a particular location. It is assumed that validation of parameters is done at the
6797 * user-facing API level, so it is not done here.
6798 *
6799 * @internal
6800 */
6801var QueryParams = /** @class */ (function () {
6802 function QueryParams() {
6803 this.limitSet_ = false;
6804 this.startSet_ = false;
6805 this.startNameSet_ = false;
6806 this.startAfterSet_ = false;
6807 this.endSet_ = false;
6808 this.endNameSet_ = false;
6809 this.endBeforeSet_ = false;
6810 this.limit_ = 0;
6811 this.viewFrom_ = '';
6812 this.indexStartValue_ = null;
6813 this.indexStartName_ = '';
6814 this.indexEndValue_ = null;
6815 this.indexEndName_ = '';
6816 this.index_ = PRIORITY_INDEX;
6817 }
6818 QueryParams.prototype.hasStart = function () {
6819 return this.startSet_;
6820 };
6821 QueryParams.prototype.hasStartAfter = function () {
6822 return this.startAfterSet_;
6823 };
6824 QueryParams.prototype.hasEndBefore = function () {
6825 return this.endBeforeSet_;
6826 };
6827 /**
6828 * @returns True if it would return from left.
6829 */
6830 QueryParams.prototype.isViewFromLeft = function () {
6831 if (this.viewFrom_ === '') {
6832 // limit(), rather than limitToFirst or limitToLast was called.
6833 // This means that only one of startSet_ and endSet_ is true. Use them
6834 // to calculate which side of the view to anchor to. If neither is set,
6835 // anchor to the end.
6836 return this.startSet_;
6837 }
6838 else {
6839 return this.viewFrom_ === "l" /* VIEW_FROM_LEFT */;
6840 }
6841 };
6842 /**
6843 * Only valid to call if hasStart() returns true
6844 */
6845 QueryParams.prototype.getIndexStartValue = function () {
6846 assert(this.startSet_, 'Only valid if start has been set');
6847 return this.indexStartValue_;
6848 };
6849 /**
6850 * Only valid to call if hasStart() returns true.
6851 * Returns the starting key name for the range defined by these query parameters
6852 */
6853 QueryParams.prototype.getIndexStartName = function () {
6854 assert(this.startSet_, 'Only valid if start has been set');
6855 if (this.startNameSet_) {
6856 return this.indexStartName_;
6857 }
6858 else {
6859 return MIN_NAME;
6860 }
6861 };
6862 QueryParams.prototype.hasEnd = function () {
6863 return this.endSet_;
6864 };
6865 /**
6866 * Only valid to call if hasEnd() returns true.
6867 */
6868 QueryParams.prototype.getIndexEndValue = function () {
6869 assert(this.endSet_, 'Only valid if end has been set');
6870 return this.indexEndValue_;
6871 };
6872 /**
6873 * Only valid to call if hasEnd() returns true.
6874 * Returns the end key name for the range defined by these query parameters
6875 */
6876 QueryParams.prototype.getIndexEndName = function () {
6877 assert(this.endSet_, 'Only valid if end has been set');
6878 if (this.endNameSet_) {
6879 return this.indexEndName_;
6880 }
6881 else {
6882 return MAX_NAME;
6883 }
6884 };
6885 QueryParams.prototype.hasLimit = function () {
6886 return this.limitSet_;
6887 };
6888 /**
6889 * @returns True if a limit has been set and it has been explicitly anchored
6890 */
6891 QueryParams.prototype.hasAnchoredLimit = function () {
6892 return this.limitSet_ && this.viewFrom_ !== '';
6893 };
6894 /**
6895 * Only valid to call if hasLimit() returns true
6896 */
6897 QueryParams.prototype.getLimit = function () {
6898 assert(this.limitSet_, 'Only valid if limit has been set');
6899 return this.limit_;
6900 };
6901 QueryParams.prototype.getIndex = function () {
6902 return this.index_;
6903 };
6904 QueryParams.prototype.loadsAllData = function () {
6905 return !(this.startSet_ || this.endSet_ || this.limitSet_);
6906 };
6907 QueryParams.prototype.isDefault = function () {
6908 return this.loadsAllData() && this.index_ === PRIORITY_INDEX;
6909 };
6910 QueryParams.prototype.copy = function () {
6911 var copy = new QueryParams();
6912 copy.limitSet_ = this.limitSet_;
6913 copy.limit_ = this.limit_;
6914 copy.startSet_ = this.startSet_;
6915 copy.indexStartValue_ = this.indexStartValue_;
6916 copy.startNameSet_ = this.startNameSet_;
6917 copy.indexStartName_ = this.indexStartName_;
6918 copy.endSet_ = this.endSet_;
6919 copy.indexEndValue_ = this.indexEndValue_;
6920 copy.endNameSet_ = this.endNameSet_;
6921 copy.indexEndName_ = this.indexEndName_;
6922 copy.index_ = this.index_;
6923 copy.viewFrom_ = this.viewFrom_;
6924 return copy;
6925 };
6926 return QueryParams;
6927}());
6928function queryParamsGetNodeFilter(queryParams) {
6929 if (queryParams.loadsAllData()) {
6930 return new IndexedFilter(queryParams.getIndex());
6931 }
6932 else if (queryParams.hasLimit()) {
6933 return new LimitedFilter(queryParams);
6934 }
6935 else {
6936 return new RangedFilter(queryParams);
6937 }
6938}
6939function queryParamsLimitToFirst(queryParams, newLimit) {
6940 var newParams = queryParams.copy();
6941 newParams.limitSet_ = true;
6942 newParams.limit_ = newLimit;
6943 newParams.viewFrom_ = "l" /* VIEW_FROM_LEFT */;
6944 return newParams;
6945}
6946function queryParamsLimitToLast(queryParams, newLimit) {
6947 var newParams = queryParams.copy();
6948 newParams.limitSet_ = true;
6949 newParams.limit_ = newLimit;
6950 newParams.viewFrom_ = "r" /* VIEW_FROM_RIGHT */;
6951 return newParams;
6952}
6953function queryParamsStartAt(queryParams, indexValue, key) {
6954 var newParams = queryParams.copy();
6955 newParams.startSet_ = true;
6956 if (indexValue === undefined) {
6957 indexValue = null;
6958 }
6959 newParams.indexStartValue_ = indexValue;
6960 if (key != null) {
6961 newParams.startNameSet_ = true;
6962 newParams.indexStartName_ = key;
6963 }
6964 else {
6965 newParams.startNameSet_ = false;
6966 newParams.indexStartName_ = '';
6967 }
6968 return newParams;
6969}
6970function queryParamsStartAfter(queryParams, indexValue, key) {
6971 var params;
6972 if (queryParams.index_ === KEY_INDEX) {
6973 if (typeof indexValue === 'string') {
6974 indexValue = successor(indexValue);
6975 }
6976 params = queryParamsStartAt(queryParams, indexValue, key);
6977 }
6978 else {
6979 var childKey = void 0;
6980 if (key == null) {
6981 childKey = MAX_NAME;
6982 }
6983 else {
6984 childKey = successor(key);
6985 }
6986 params = queryParamsStartAt(queryParams, indexValue, childKey);
6987 }
6988 params.startAfterSet_ = true;
6989 return params;
6990}
6991function queryParamsEndAt(queryParams, indexValue, key) {
6992 var newParams = queryParams.copy();
6993 newParams.endSet_ = true;
6994 if (indexValue === undefined) {
6995 indexValue = null;
6996 }
6997 newParams.indexEndValue_ = indexValue;
6998 if (key !== undefined) {
6999 newParams.endNameSet_ = true;
7000 newParams.indexEndName_ = key;
7001 }
7002 else {
7003 newParams.endNameSet_ = false;
7004 newParams.indexEndName_ = '';
7005 }
7006 return newParams;
7007}
7008function queryParamsEndBefore(queryParams, indexValue, key) {
7009 var childKey;
7010 var params;
7011 if (queryParams.index_ === KEY_INDEX) {
7012 if (typeof indexValue === 'string') {
7013 indexValue = predecessor(indexValue);
7014 }
7015 params = queryParamsEndAt(queryParams, indexValue, key);
7016 }
7017 else {
7018 if (key == null) {
7019 childKey = MIN_NAME;
7020 }
7021 else {
7022 childKey = predecessor(key);
7023 }
7024 params = queryParamsEndAt(queryParams, indexValue, childKey);
7025 }
7026 params.endBeforeSet_ = true;
7027 return params;
7028}
7029function queryParamsOrderBy(queryParams, index) {
7030 var newParams = queryParams.copy();
7031 newParams.index_ = index;
7032 return newParams;
7033}
7034/**
7035 * Returns a set of REST query string parameters representing this query.
7036 *
7037 * @returns query string parameters
7038 */
7039function queryParamsToRestQueryStringParameters(queryParams) {
7040 var qs = {};
7041 if (queryParams.isDefault()) {
7042 return qs;
7043 }
7044 var orderBy;
7045 if (queryParams.index_ === PRIORITY_INDEX) {
7046 orderBy = "$priority" /* PRIORITY_INDEX */;
7047 }
7048 else if (queryParams.index_ === VALUE_INDEX) {
7049 orderBy = "$value" /* VALUE_INDEX */;
7050 }
7051 else if (queryParams.index_ === KEY_INDEX) {
7052 orderBy = "$key" /* KEY_INDEX */;
7053 }
7054 else {
7055 assert(queryParams.index_ instanceof PathIndex, 'Unrecognized index type!');
7056 orderBy = queryParams.index_.toString();
7057 }
7058 qs["orderBy" /* ORDER_BY */] = stringify(orderBy);
7059 if (queryParams.startSet_) {
7060 qs["startAt" /* START_AT */] = stringify(queryParams.indexStartValue_);
7061 if (queryParams.startNameSet_) {
7062 qs["startAt" /* START_AT */] +=
7063 ',' + stringify(queryParams.indexStartName_);
7064 }
7065 }
7066 if (queryParams.endSet_) {
7067 qs["endAt" /* END_AT */] = stringify(queryParams.indexEndValue_);
7068 if (queryParams.endNameSet_) {
7069 qs["endAt" /* END_AT */] +=
7070 ',' + stringify(queryParams.indexEndName_);
7071 }
7072 }
7073 if (queryParams.limitSet_) {
7074 if (queryParams.isViewFromLeft()) {
7075 qs["limitToFirst" /* LIMIT_TO_FIRST */] = queryParams.limit_;
7076 }
7077 else {
7078 qs["limitToLast" /* LIMIT_TO_LAST */] = queryParams.limit_;
7079 }
7080 }
7081 return qs;
7082}
7083function queryParamsGetQueryObject(queryParams) {
7084 var obj = {};
7085 if (queryParams.startSet_) {
7086 obj["sp" /* INDEX_START_VALUE */] =
7087 queryParams.indexStartValue_;
7088 if (queryParams.startNameSet_) {
7089 obj["sn" /* INDEX_START_NAME */] =
7090 queryParams.indexStartName_;
7091 }
7092 }
7093 if (queryParams.endSet_) {
7094 obj["ep" /* INDEX_END_VALUE */] = queryParams.indexEndValue_;
7095 if (queryParams.endNameSet_) {
7096 obj["en" /* INDEX_END_NAME */] = queryParams.indexEndName_;
7097 }
7098 }
7099 if (queryParams.limitSet_) {
7100 obj["l" /* LIMIT */] = queryParams.limit_;
7101 var viewFrom = queryParams.viewFrom_;
7102 if (viewFrom === '') {
7103 if (queryParams.isViewFromLeft()) {
7104 viewFrom = "l" /* VIEW_FROM_LEFT */;
7105 }
7106 else {
7107 viewFrom = "r" /* VIEW_FROM_RIGHT */;
7108 }
7109 }
7110 obj["vf" /* VIEW_FROM */] = viewFrom;
7111 }
7112 // For now, priority index is the default, so we only specify if it's some other index
7113 if (queryParams.index_ !== PRIORITY_INDEX) {
7114 obj["i" /* INDEX */] = queryParams.index_.toString();
7115 }
7116 return obj;
7117}
7118
7119/**
7120 * @license
7121 * Copyright 2017 Google LLC
7122 *
7123 * Licensed under the Apache License, Version 2.0 (the "License");
7124 * you may not use this file except in compliance with the License.
7125 * You may obtain a copy of the License at
7126 *
7127 * http://www.apache.org/licenses/LICENSE-2.0
7128 *
7129 * Unless required by applicable law or agreed to in writing, software
7130 * distributed under the License is distributed on an "AS IS" BASIS,
7131 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7132 * See the License for the specific language governing permissions and
7133 * limitations under the License.
7134 */
7135/**
7136 * An implementation of ServerActions that communicates with the server via REST requests.
7137 * This is mostly useful for compatibility with crawlers, where we don't want to spin up a full
7138 * persistent connection (using WebSockets or long-polling)
7139 */
7140var ReadonlyRestClient = /** @class */ (function (_super) {
7141 __extends(ReadonlyRestClient, _super);
7142 /**
7143 * @param repoInfo_ - Data about the namespace we are connecting to
7144 * @param onDataUpdate_ - A callback for new data from the server
7145 */
7146 function ReadonlyRestClient(repoInfo_, onDataUpdate_, authTokenProvider_, appCheckTokenProvider_) {
7147 var _this = _super.call(this) || this;
7148 _this.repoInfo_ = repoInfo_;
7149 _this.onDataUpdate_ = onDataUpdate_;
7150 _this.authTokenProvider_ = authTokenProvider_;
7151 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
7152 /** @private {function(...[*])} */
7153 _this.log_ = logWrapper('p:rest:');
7154 /**
7155 * We don't actually need to track listens, except to prevent us calling an onComplete for a listen
7156 * that's been removed. :-/
7157 */
7158 _this.listens_ = {};
7159 return _this;
7160 }
7161 ReadonlyRestClient.prototype.reportStats = function (stats) {
7162 throw new Error('Method not implemented.');
7163 };
7164 ReadonlyRestClient.getListenId_ = function (query, tag) {
7165 if (tag !== undefined) {
7166 return 'tag$' + tag;
7167 }
7168 else {
7169 assert(query._queryParams.isDefault(), "should have a tag if it's not a default query.");
7170 return query._path.toString();
7171 }
7172 };
7173 /** @inheritDoc */
7174 ReadonlyRestClient.prototype.listen = function (query, currentHashFn, tag, onComplete) {
7175 var _this = this;
7176 var pathString = query._path.toString();
7177 this.log_('Listen called for ' + pathString + ' ' + query._queryIdentifier);
7178 // Mark this listener so we can tell if it's removed.
7179 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7180 var thisListen = {};
7181 this.listens_[listenId] = thisListen;
7182 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7183 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7184 var data = result;
7185 if (error === 404) {
7186 data = null;
7187 error = null;
7188 }
7189 if (error === null) {
7190 _this.onDataUpdate_(pathString, data, /*isMerge=*/ false, tag);
7191 }
7192 if (safeGet(_this.listens_, listenId) === thisListen) {
7193 var status_1;
7194 if (!error) {
7195 status_1 = 'ok';
7196 }
7197 else if (error === 401) {
7198 status_1 = 'permission_denied';
7199 }
7200 else {
7201 status_1 = 'rest_error:' + error;
7202 }
7203 onComplete(status_1, null);
7204 }
7205 });
7206 };
7207 /** @inheritDoc */
7208 ReadonlyRestClient.prototype.unlisten = function (query, tag) {
7209 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7210 delete this.listens_[listenId];
7211 };
7212 ReadonlyRestClient.prototype.get = function (query) {
7213 var _this = this;
7214 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7215 var pathString = query._path.toString();
7216 var deferred = new Deferred();
7217 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7218 var data = result;
7219 if (error === 404) {
7220 data = null;
7221 error = null;
7222 }
7223 if (error === null) {
7224 _this.onDataUpdate_(pathString, data,
7225 /*isMerge=*/ false,
7226 /*tag=*/ null);
7227 deferred.resolve(data);
7228 }
7229 else {
7230 deferred.reject(new Error(data));
7231 }
7232 });
7233 return deferred.promise;
7234 };
7235 /** @inheritDoc */
7236 ReadonlyRestClient.prototype.refreshAuthToken = function (token) {
7237 // no-op since we just always call getToken.
7238 };
7239 /**
7240 * Performs a REST request to the given path, with the provided query string parameters,
7241 * and any auth credentials we have.
7242 */
7243 ReadonlyRestClient.prototype.restRequest_ = function (pathString, queryStringParameters, callback) {
7244 var _this = this;
7245 if (queryStringParameters === void 0) { queryStringParameters = {}; }
7246 queryStringParameters['format'] = 'export';
7247 return Promise.all([
7248 this.authTokenProvider_.getToken(/*forceRefresh=*/ false),
7249 this.appCheckTokenProvider_.getToken(/*forceRefresh=*/ false)
7250 ]).then(function (_a) {
7251 var _b = __read(_a, 2), authToken = _b[0], appCheckToken = _b[1];
7252 if (authToken && authToken.accessToken) {
7253 queryStringParameters['auth'] = authToken.accessToken;
7254 }
7255 if (appCheckToken && appCheckToken.token) {
7256 queryStringParameters['ac'] = appCheckToken.token;
7257 }
7258 var url = (_this.repoInfo_.secure ? 'https://' : 'http://') +
7259 _this.repoInfo_.host +
7260 pathString +
7261 '?' +
7262 'ns=' +
7263 _this.repoInfo_.namespace +
7264 querystring(queryStringParameters);
7265 _this.log_('Sending REST request for ' + url);
7266 var xhr = new XMLHttpRequest();
7267 xhr.onreadystatechange = function () {
7268 if (callback && xhr.readyState === 4) {
7269 _this.log_('REST Response for ' + url + ' received. status:', xhr.status, 'response:', xhr.responseText);
7270 var res = null;
7271 if (xhr.status >= 200 && xhr.status < 300) {
7272 try {
7273 res = jsonEval(xhr.responseText);
7274 }
7275 catch (e) {
7276 warn('Failed to parse JSON response for ' +
7277 url +
7278 ': ' +
7279 xhr.responseText);
7280 }
7281 callback(null, res);
7282 }
7283 else {
7284 // 401 and 404 are expected.
7285 if (xhr.status !== 401 && xhr.status !== 404) {
7286 warn('Got unsuccessful REST response for ' +
7287 url +
7288 ' Status: ' +
7289 xhr.status);
7290 }
7291 callback(xhr.status);
7292 }
7293 callback = null;
7294 }
7295 };
7296 xhr.open('GET', url, /*asynchronous=*/ true);
7297 xhr.send();
7298 });
7299 };
7300 return ReadonlyRestClient;
7301}(ServerActions));
7302
7303/**
7304 * @license
7305 * Copyright 2017 Google LLC
7306 *
7307 * Licensed under the Apache License, Version 2.0 (the "License");
7308 * you may not use this file except in compliance with the License.
7309 * You may obtain a copy of the License at
7310 *
7311 * http://www.apache.org/licenses/LICENSE-2.0
7312 *
7313 * Unless required by applicable law or agreed to in writing, software
7314 * distributed under the License is distributed on an "AS IS" BASIS,
7315 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7316 * See the License for the specific language governing permissions and
7317 * limitations under the License.
7318 */
7319/**
7320 * Mutable object which basically just stores a reference to the "latest" immutable snapshot.
7321 */
7322var SnapshotHolder = /** @class */ (function () {
7323 function SnapshotHolder() {
7324 this.rootNode_ = ChildrenNode.EMPTY_NODE;
7325 }
7326 SnapshotHolder.prototype.getNode = function (path) {
7327 return this.rootNode_.getChild(path);
7328 };
7329 SnapshotHolder.prototype.updateSnapshot = function (path, newSnapshotNode) {
7330 this.rootNode_ = this.rootNode_.updateChild(path, newSnapshotNode);
7331 };
7332 return SnapshotHolder;
7333}());
7334
7335/**
7336 * @license
7337 * Copyright 2017 Google LLC
7338 *
7339 * Licensed under the Apache License, Version 2.0 (the "License");
7340 * you may not use this file except in compliance with the License.
7341 * You may obtain a copy of the License at
7342 *
7343 * http://www.apache.org/licenses/LICENSE-2.0
7344 *
7345 * Unless required by applicable law or agreed to in writing, software
7346 * distributed under the License is distributed on an "AS IS" BASIS,
7347 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7348 * See the License for the specific language governing permissions and
7349 * limitations under the License.
7350 */
7351function newSparseSnapshotTree() {
7352 return {
7353 value: null,
7354 children: new Map()
7355 };
7356}
7357/**
7358 * Stores the given node at the specified path. If there is already a node
7359 * at a shallower path, it merges the new data into that snapshot node.
7360 *
7361 * @param path - Path to look up snapshot for.
7362 * @param data - The new data, or null.
7363 */
7364function sparseSnapshotTreeRemember(sparseSnapshotTree, path, data) {
7365 if (pathIsEmpty(path)) {
7366 sparseSnapshotTree.value = data;
7367 sparseSnapshotTree.children.clear();
7368 }
7369 else if (sparseSnapshotTree.value !== null) {
7370 sparseSnapshotTree.value = sparseSnapshotTree.value.updateChild(path, data);
7371 }
7372 else {
7373 var childKey = pathGetFront(path);
7374 if (!sparseSnapshotTree.children.has(childKey)) {
7375 sparseSnapshotTree.children.set(childKey, newSparseSnapshotTree());
7376 }
7377 var child = sparseSnapshotTree.children.get(childKey);
7378 path = pathPopFront(path);
7379 sparseSnapshotTreeRemember(child, path, data);
7380 }
7381}
7382/**
7383 * Purge the data at path from the cache.
7384 *
7385 * @param path - Path to look up snapshot for.
7386 * @returns True if this node should now be removed.
7387 */
7388function sparseSnapshotTreeForget(sparseSnapshotTree, path) {
7389 if (pathIsEmpty(path)) {
7390 sparseSnapshotTree.value = null;
7391 sparseSnapshotTree.children.clear();
7392 return true;
7393 }
7394 else {
7395 if (sparseSnapshotTree.value !== null) {
7396 if (sparseSnapshotTree.value.isLeafNode()) {
7397 // We're trying to forget a node that doesn't exist
7398 return false;
7399 }
7400 else {
7401 var value = sparseSnapshotTree.value;
7402 sparseSnapshotTree.value = null;
7403 value.forEachChild(PRIORITY_INDEX, function (key, tree) {
7404 sparseSnapshotTreeRemember(sparseSnapshotTree, new Path(key), tree);
7405 });
7406 return sparseSnapshotTreeForget(sparseSnapshotTree, path);
7407 }
7408 }
7409 else if (sparseSnapshotTree.children.size > 0) {
7410 var childKey = pathGetFront(path);
7411 path = pathPopFront(path);
7412 if (sparseSnapshotTree.children.has(childKey)) {
7413 var safeToRemove = sparseSnapshotTreeForget(sparseSnapshotTree.children.get(childKey), path);
7414 if (safeToRemove) {
7415 sparseSnapshotTree.children.delete(childKey);
7416 }
7417 }
7418 return sparseSnapshotTree.children.size === 0;
7419 }
7420 else {
7421 return true;
7422 }
7423 }
7424}
7425/**
7426 * Recursively iterates through all of the stored tree and calls the
7427 * callback on each one.
7428 *
7429 * @param prefixPath - Path to look up node for.
7430 * @param func - The function to invoke for each tree.
7431 */
7432function sparseSnapshotTreeForEachTree(sparseSnapshotTree, prefixPath, func) {
7433 if (sparseSnapshotTree.value !== null) {
7434 func(prefixPath, sparseSnapshotTree.value);
7435 }
7436 else {
7437 sparseSnapshotTreeForEachChild(sparseSnapshotTree, function (key, tree) {
7438 var path = new Path(prefixPath.toString() + '/' + key);
7439 sparseSnapshotTreeForEachTree(tree, path, func);
7440 });
7441 }
7442}
7443/**
7444 * Iterates through each immediate child and triggers the callback.
7445 * Only seems to be used in tests.
7446 *
7447 * @param func - The function to invoke for each child.
7448 */
7449function sparseSnapshotTreeForEachChild(sparseSnapshotTree, func) {
7450 sparseSnapshotTree.children.forEach(function (tree, key) {
7451 func(key, tree);
7452 });
7453}
7454
7455/**
7456 * @license
7457 * Copyright 2017 Google LLC
7458 *
7459 * Licensed under the Apache License, Version 2.0 (the "License");
7460 * you may not use this file except in compliance with the License.
7461 * You may obtain a copy of the License at
7462 *
7463 * http://www.apache.org/licenses/LICENSE-2.0
7464 *
7465 * Unless required by applicable law or agreed to in writing, software
7466 * distributed under the License is distributed on an "AS IS" BASIS,
7467 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7468 * See the License for the specific language governing permissions and
7469 * limitations under the License.
7470 */
7471/**
7472 * Returns the delta from the previous call to get stats.
7473 *
7474 * @param collection_ - The collection to "listen" to.
7475 */
7476var StatsListener = /** @class */ (function () {
7477 function StatsListener(collection_) {
7478 this.collection_ = collection_;
7479 this.last_ = null;
7480 }
7481 StatsListener.prototype.get = function () {
7482 var newStats = this.collection_.get();
7483 var delta = __assign({}, newStats);
7484 if (this.last_) {
7485 each(this.last_, function (stat, value) {
7486 delta[stat] = delta[stat] - value;
7487 });
7488 }
7489 this.last_ = newStats;
7490 return delta;
7491 };
7492 return StatsListener;
7493}());
7494
7495/**
7496 * @license
7497 * Copyright 2017 Google LLC
7498 *
7499 * Licensed under the Apache License, Version 2.0 (the "License");
7500 * you may not use this file except in compliance with the License.
7501 * You may obtain a copy of the License at
7502 *
7503 * http://www.apache.org/licenses/LICENSE-2.0
7504 *
7505 * Unless required by applicable law or agreed to in writing, software
7506 * distributed under the License is distributed on an "AS IS" BASIS,
7507 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7508 * See the License for the specific language governing permissions and
7509 * limitations under the License.
7510 */
7511// Assuming some apps may have a short amount of time on page, and a bulk of firebase operations probably
7512// happen on page load, we try to report our first set of stats pretty quickly, but we wait at least 10
7513// seconds to try to ensure the Firebase connection is established / settled.
7514var FIRST_STATS_MIN_TIME = 10 * 1000;
7515var FIRST_STATS_MAX_TIME = 30 * 1000;
7516// We'll continue to report stats on average every 5 minutes.
7517var REPORT_STATS_INTERVAL = 5 * 60 * 1000;
7518var StatsReporter = /** @class */ (function () {
7519 function StatsReporter(collection, server_) {
7520 this.server_ = server_;
7521 this.statsToReport_ = {};
7522 this.statsListener_ = new StatsListener(collection);
7523 var timeout = FIRST_STATS_MIN_TIME +
7524 (FIRST_STATS_MAX_TIME - FIRST_STATS_MIN_TIME) * Math.random();
7525 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(timeout));
7526 }
7527 StatsReporter.prototype.reportStats_ = function () {
7528 var _this = this;
7529 var stats = this.statsListener_.get();
7530 var reportedStats = {};
7531 var haveStatsToReport = false;
7532 each(stats, function (stat, value) {
7533 if (value > 0 && contains(_this.statsToReport_, stat)) {
7534 reportedStats[stat] = value;
7535 haveStatsToReport = true;
7536 }
7537 });
7538 if (haveStatsToReport) {
7539 this.server_.reportStats(reportedStats);
7540 }
7541 // queue our next run.
7542 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(Math.random() * 2 * REPORT_STATS_INTERVAL));
7543 };
7544 return StatsReporter;
7545}());
7546
7547/**
7548 * @license
7549 * Copyright 2017 Google LLC
7550 *
7551 * Licensed under the Apache License, Version 2.0 (the "License");
7552 * you may not use this file except in compliance with the License.
7553 * You may obtain a copy of the License at
7554 *
7555 * http://www.apache.org/licenses/LICENSE-2.0
7556 *
7557 * Unless required by applicable law or agreed to in writing, software
7558 * distributed under the License is distributed on an "AS IS" BASIS,
7559 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7560 * See the License for the specific language governing permissions and
7561 * limitations under the License.
7562 */
7563/**
7564 *
7565 * @enum
7566 */
7567var OperationType;
7568(function (OperationType) {
7569 OperationType[OperationType["OVERWRITE"] = 0] = "OVERWRITE";
7570 OperationType[OperationType["MERGE"] = 1] = "MERGE";
7571 OperationType[OperationType["ACK_USER_WRITE"] = 2] = "ACK_USER_WRITE";
7572 OperationType[OperationType["LISTEN_COMPLETE"] = 3] = "LISTEN_COMPLETE";
7573})(OperationType || (OperationType = {}));
7574function newOperationSourceUser() {
7575 return {
7576 fromUser: true,
7577 fromServer: false,
7578 queryId: null,
7579 tagged: false
7580 };
7581}
7582function newOperationSourceServer() {
7583 return {
7584 fromUser: false,
7585 fromServer: true,
7586 queryId: null,
7587 tagged: false
7588 };
7589}
7590function newOperationSourceServerTaggedQuery(queryId) {
7591 return {
7592 fromUser: false,
7593 fromServer: true,
7594 queryId: queryId,
7595 tagged: true
7596 };
7597}
7598
7599/**
7600 * @license
7601 * Copyright 2017 Google LLC
7602 *
7603 * Licensed under the Apache License, Version 2.0 (the "License");
7604 * you may not use this file except in compliance with the License.
7605 * You may obtain a copy of the License at
7606 *
7607 * http://www.apache.org/licenses/LICENSE-2.0
7608 *
7609 * Unless required by applicable law or agreed to in writing, software
7610 * distributed under the License is distributed on an "AS IS" BASIS,
7611 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7612 * See the License for the specific language governing permissions and
7613 * limitations under the License.
7614 */
7615var AckUserWrite = /** @class */ (function () {
7616 /**
7617 * @param affectedTree - A tree containing true for each affected path. Affected paths can't overlap.
7618 */
7619 function AckUserWrite(
7620 /** @inheritDoc */ path,
7621 /** @inheritDoc */ affectedTree,
7622 /** @inheritDoc */ revert) {
7623 this.path = path;
7624 this.affectedTree = affectedTree;
7625 this.revert = revert;
7626 /** @inheritDoc */
7627 this.type = OperationType.ACK_USER_WRITE;
7628 /** @inheritDoc */
7629 this.source = newOperationSourceUser();
7630 }
7631 AckUserWrite.prototype.operationForChild = function (childName) {
7632 if (!pathIsEmpty(this.path)) {
7633 assert(pathGetFront(this.path) === childName, 'operationForChild called for unrelated child.');
7634 return new AckUserWrite(pathPopFront(this.path), this.affectedTree, this.revert);
7635 }
7636 else if (this.affectedTree.value != null) {
7637 assert(this.affectedTree.children.isEmpty(), 'affectedTree should not have overlapping affected paths.');
7638 // All child locations are affected as well; just return same operation.
7639 return this;
7640 }
7641 else {
7642 var childTree = this.affectedTree.subtree(new Path(childName));
7643 return new AckUserWrite(newEmptyPath(), childTree, this.revert);
7644 }
7645 };
7646 return AckUserWrite;
7647}());
7648
7649/**
7650 * @license
7651 * Copyright 2017 Google LLC
7652 *
7653 * Licensed under the Apache License, Version 2.0 (the "License");
7654 * you may not use this file except in compliance with the License.
7655 * You may obtain a copy of the License at
7656 *
7657 * http://www.apache.org/licenses/LICENSE-2.0
7658 *
7659 * Unless required by applicable law or agreed to in writing, software
7660 * distributed under the License is distributed on an "AS IS" BASIS,
7661 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7662 * See the License for the specific language governing permissions and
7663 * limitations under the License.
7664 */
7665var ListenComplete = /** @class */ (function () {
7666 function ListenComplete(source, path) {
7667 this.source = source;
7668 this.path = path;
7669 /** @inheritDoc */
7670 this.type = OperationType.LISTEN_COMPLETE;
7671 }
7672 ListenComplete.prototype.operationForChild = function (childName) {
7673 if (pathIsEmpty(this.path)) {
7674 return new ListenComplete(this.source, newEmptyPath());
7675 }
7676 else {
7677 return new ListenComplete(this.source, pathPopFront(this.path));
7678 }
7679 };
7680 return ListenComplete;
7681}());
7682
7683/**
7684 * @license
7685 * Copyright 2017 Google LLC
7686 *
7687 * Licensed under the Apache License, Version 2.0 (the "License");
7688 * you may not use this file except in compliance with the License.
7689 * You may obtain a copy of the License at
7690 *
7691 * http://www.apache.org/licenses/LICENSE-2.0
7692 *
7693 * Unless required by applicable law or agreed to in writing, software
7694 * distributed under the License is distributed on an "AS IS" BASIS,
7695 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7696 * See the License for the specific language governing permissions and
7697 * limitations under the License.
7698 */
7699var Overwrite = /** @class */ (function () {
7700 function Overwrite(source, path, snap) {
7701 this.source = source;
7702 this.path = path;
7703 this.snap = snap;
7704 /** @inheritDoc */
7705 this.type = OperationType.OVERWRITE;
7706 }
7707 Overwrite.prototype.operationForChild = function (childName) {
7708 if (pathIsEmpty(this.path)) {
7709 return new Overwrite(this.source, newEmptyPath(), this.snap.getImmediateChild(childName));
7710 }
7711 else {
7712 return new Overwrite(this.source, pathPopFront(this.path), this.snap);
7713 }
7714 };
7715 return Overwrite;
7716}());
7717
7718/**
7719 * @license
7720 * Copyright 2017 Google LLC
7721 *
7722 * Licensed under the Apache License, Version 2.0 (the "License");
7723 * you may not use this file except in compliance with the License.
7724 * You may obtain a copy of the License at
7725 *
7726 * http://www.apache.org/licenses/LICENSE-2.0
7727 *
7728 * Unless required by applicable law or agreed to in writing, software
7729 * distributed under the License is distributed on an "AS IS" BASIS,
7730 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7731 * See the License for the specific language governing permissions and
7732 * limitations under the License.
7733 */
7734var Merge = /** @class */ (function () {
7735 function Merge(
7736 /** @inheritDoc */ source,
7737 /** @inheritDoc */ path,
7738 /** @inheritDoc */ children) {
7739 this.source = source;
7740 this.path = path;
7741 this.children = children;
7742 /** @inheritDoc */
7743 this.type = OperationType.MERGE;
7744 }
7745 Merge.prototype.operationForChild = function (childName) {
7746 if (pathIsEmpty(this.path)) {
7747 var childTree = this.children.subtree(new Path(childName));
7748 if (childTree.isEmpty()) {
7749 // This child is unaffected
7750 return null;
7751 }
7752 else if (childTree.value) {
7753 // We have a snapshot for the child in question. This becomes an overwrite of the child.
7754 return new Overwrite(this.source, newEmptyPath(), childTree.value);
7755 }
7756 else {
7757 // This is a merge at a deeper level
7758 return new Merge(this.source, newEmptyPath(), childTree);
7759 }
7760 }
7761 else {
7762 assert(pathGetFront(this.path) === childName, "Can't get a merge for a child not on the path of the operation");
7763 return new Merge(this.source, pathPopFront(this.path), this.children);
7764 }
7765 };
7766 Merge.prototype.toString = function () {
7767 return ('Operation(' +
7768 this.path +
7769 ': ' +
7770 this.source.toString() +
7771 ' merge: ' +
7772 this.children.toString() +
7773 ')');
7774 };
7775 return Merge;
7776}());
7777
7778/**
7779 * @license
7780 * Copyright 2017 Google LLC
7781 *
7782 * Licensed under the Apache License, Version 2.0 (the "License");
7783 * you may not use this file except in compliance with the License.
7784 * You may obtain a copy of the License at
7785 *
7786 * http://www.apache.org/licenses/LICENSE-2.0
7787 *
7788 * Unless required by applicable law or agreed to in writing, software
7789 * distributed under the License is distributed on an "AS IS" BASIS,
7790 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7791 * See the License for the specific language governing permissions and
7792 * limitations under the License.
7793 */
7794/**
7795 * A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
7796 * initialized in the sense that we know at one point in time this represented a valid state of the world, e.g.
7797 * initialized with data from the server, or a complete overwrite by the client. The filtered flag also tracks
7798 * whether a node potentially had children removed due to a filter.
7799 */
7800var CacheNode = /** @class */ (function () {
7801 function CacheNode(node_, fullyInitialized_, filtered_) {
7802 this.node_ = node_;
7803 this.fullyInitialized_ = fullyInitialized_;
7804 this.filtered_ = filtered_;
7805 }
7806 /**
7807 * Returns whether this node was fully initialized with either server data or a complete overwrite by the client
7808 */
7809 CacheNode.prototype.isFullyInitialized = function () {
7810 return this.fullyInitialized_;
7811 };
7812 /**
7813 * Returns whether this node is potentially missing children due to a filter applied to the node
7814 */
7815 CacheNode.prototype.isFiltered = function () {
7816 return this.filtered_;
7817 };
7818 CacheNode.prototype.isCompleteForPath = function (path) {
7819 if (pathIsEmpty(path)) {
7820 return this.isFullyInitialized() && !this.filtered_;
7821 }
7822 var childKey = pathGetFront(path);
7823 return this.isCompleteForChild(childKey);
7824 };
7825 CacheNode.prototype.isCompleteForChild = function (key) {
7826 return ((this.isFullyInitialized() && !this.filtered_) || this.node_.hasChild(key));
7827 };
7828 CacheNode.prototype.getNode = function () {
7829 return this.node_;
7830 };
7831 return CacheNode;
7832}());
7833
7834/**
7835 * @license
7836 * Copyright 2017 Google LLC
7837 *
7838 * Licensed under the Apache License, Version 2.0 (the "License");
7839 * you may not use this file except in compliance with the License.
7840 * You may obtain a copy of the License at
7841 *
7842 * http://www.apache.org/licenses/LICENSE-2.0
7843 *
7844 * Unless required by applicable law or agreed to in writing, software
7845 * distributed under the License is distributed on an "AS IS" BASIS,
7846 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7847 * See the License for the specific language governing permissions and
7848 * limitations under the License.
7849 */
7850/**
7851 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
7852 * CacheDiffer into actual events (Event) that can be raised. See generateEventsForChanges()
7853 * for details.
7854 *
7855 */
7856var EventGenerator = /** @class */ (function () {
7857 function EventGenerator(query_) {
7858 this.query_ = query_;
7859 this.index_ = this.query_._queryParams.getIndex();
7860 }
7861 return EventGenerator;
7862}());
7863/**
7864 * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
7865 * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
7866 *
7867 * Notes:
7868 * - child_moved events will be synthesized at this time for any child_changed events that affect
7869 * our index.
7870 * - prevName will be calculated based on the index ordering.
7871 */
7872function eventGeneratorGenerateEventsForChanges(eventGenerator, changes, eventCache, eventRegistrations) {
7873 var events = [];
7874 var moves = [];
7875 changes.forEach(function (change) {
7876 if (change.type === "child_changed" /* CHILD_CHANGED */ &&
7877 eventGenerator.index_.indexedValueChanged(change.oldSnap, change.snapshotNode)) {
7878 moves.push(changeChildMoved(change.childName, change.snapshotNode));
7879 }
7880 });
7881 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_removed" /* CHILD_REMOVED */, changes, eventRegistrations, eventCache);
7882 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_added" /* CHILD_ADDED */, changes, eventRegistrations, eventCache);
7883 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_moved" /* CHILD_MOVED */, moves, eventRegistrations, eventCache);
7884 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_changed" /* CHILD_CHANGED */, changes, eventRegistrations, eventCache);
7885 eventGeneratorGenerateEventsForType(eventGenerator, events, "value" /* VALUE */, changes, eventRegistrations, eventCache);
7886 return events;
7887}
7888/**
7889 * Given changes of a single change type, generate the corresponding events.
7890 */
7891function eventGeneratorGenerateEventsForType(eventGenerator, events, eventType, changes, registrations, eventCache) {
7892 var filteredChanges = changes.filter(function (change) { return change.type === eventType; });
7893 filteredChanges.sort(function (a, b) {
7894 return eventGeneratorCompareChanges(eventGenerator, a, b);
7895 });
7896 filteredChanges.forEach(function (change) {
7897 var materializedChange = eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache);
7898 registrations.forEach(function (registration) {
7899 if (registration.respondsTo(change.type)) {
7900 events.push(registration.createEvent(materializedChange, eventGenerator.query_));
7901 }
7902 });
7903 });
7904}
7905function eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache) {
7906 if (change.type === 'value' || change.type === 'child_removed') {
7907 return change;
7908 }
7909 else {
7910 change.prevName = eventCache.getPredecessorChildName(change.childName, change.snapshotNode, eventGenerator.index_);
7911 return change;
7912 }
7913}
7914function eventGeneratorCompareChanges(eventGenerator, a, b) {
7915 if (a.childName == null || b.childName == null) {
7916 throw assertionError('Should only compare child_ events.');
7917 }
7918 var aWrapped = new NamedNode(a.childName, a.snapshotNode);
7919 var bWrapped = new NamedNode(b.childName, b.snapshotNode);
7920 return eventGenerator.index_.compare(aWrapped, bWrapped);
7921}
7922
7923/**
7924 * @license
7925 * Copyright 2017 Google LLC
7926 *
7927 * Licensed under the Apache License, Version 2.0 (the "License");
7928 * you may not use this file except in compliance with the License.
7929 * You may obtain a copy of the License at
7930 *
7931 * http://www.apache.org/licenses/LICENSE-2.0
7932 *
7933 * Unless required by applicable law or agreed to in writing, software
7934 * distributed under the License is distributed on an "AS IS" BASIS,
7935 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7936 * See the License for the specific language governing permissions and
7937 * limitations under the License.
7938 */
7939function newViewCache(eventCache, serverCache) {
7940 return { eventCache: eventCache, serverCache: serverCache };
7941}
7942function viewCacheUpdateEventSnap(viewCache, eventSnap, complete, filtered) {
7943 return newViewCache(new CacheNode(eventSnap, complete, filtered), viewCache.serverCache);
7944}
7945function viewCacheUpdateServerSnap(viewCache, serverSnap, complete, filtered) {
7946 return newViewCache(viewCache.eventCache, new CacheNode(serverSnap, complete, filtered));
7947}
7948function viewCacheGetCompleteEventSnap(viewCache) {
7949 return viewCache.eventCache.isFullyInitialized()
7950 ? viewCache.eventCache.getNode()
7951 : null;
7952}
7953function viewCacheGetCompleteServerSnap(viewCache) {
7954 return viewCache.serverCache.isFullyInitialized()
7955 ? viewCache.serverCache.getNode()
7956 : null;
7957}
7958
7959/**
7960 * @license
7961 * Copyright 2017 Google LLC
7962 *
7963 * Licensed under the Apache License, Version 2.0 (the "License");
7964 * you may not use this file except in compliance with the License.
7965 * You may obtain a copy of the License at
7966 *
7967 * http://www.apache.org/licenses/LICENSE-2.0
7968 *
7969 * Unless required by applicable law or agreed to in writing, software
7970 * distributed under the License is distributed on an "AS IS" BASIS,
7971 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7972 * See the License for the specific language governing permissions and
7973 * limitations under the License.
7974 */
7975var emptyChildrenSingleton;
7976/**
7977 * Singleton empty children collection.
7978 *
7979 */
7980var EmptyChildren = function () {
7981 if (!emptyChildrenSingleton) {
7982 emptyChildrenSingleton = new SortedMap(stringCompare);
7983 }
7984 return emptyChildrenSingleton;
7985};
7986/**
7987 * A tree with immutable elements.
7988 */
7989var ImmutableTree = /** @class */ (function () {
7990 function ImmutableTree(value, children) {
7991 if (children === void 0) { children = EmptyChildren(); }
7992 this.value = value;
7993 this.children = children;
7994 }
7995 ImmutableTree.fromObject = function (obj) {
7996 var tree = new ImmutableTree(null);
7997 each(obj, function (childPath, childSnap) {
7998 tree = tree.set(new Path(childPath), childSnap);
7999 });
8000 return tree;
8001 };
8002 /**
8003 * True if the value is empty and there are no children
8004 */
8005 ImmutableTree.prototype.isEmpty = function () {
8006 return this.value === null && this.children.isEmpty();
8007 };
8008 /**
8009 * Given a path and predicate, return the first node and the path to that node
8010 * where the predicate returns true.
8011 *
8012 * TODO Do a perf test -- If we're creating a bunch of `{path: value:}`
8013 * objects on the way back out, it may be better to pass down a pathSoFar obj.
8014 *
8015 * @param relativePath - The remainder of the path
8016 * @param predicate - The predicate to satisfy to return a node
8017 */
8018 ImmutableTree.prototype.findRootMostMatchingPathAndValue = function (relativePath, predicate) {
8019 if (this.value != null && predicate(this.value)) {
8020 return { path: newEmptyPath(), value: this.value };
8021 }
8022 else {
8023 if (pathIsEmpty(relativePath)) {
8024 return null;
8025 }
8026 else {
8027 var front = pathGetFront(relativePath);
8028 var child = this.children.get(front);
8029 if (child !== null) {
8030 var childExistingPathAndValue = child.findRootMostMatchingPathAndValue(pathPopFront(relativePath), predicate);
8031 if (childExistingPathAndValue != null) {
8032 var fullPath = pathChild(new Path(front), childExistingPathAndValue.path);
8033 return { path: fullPath, value: childExistingPathAndValue.value };
8034 }
8035 else {
8036 return null;
8037 }
8038 }
8039 else {
8040 return null;
8041 }
8042 }
8043 }
8044 };
8045 /**
8046 * Find, if it exists, the shortest subpath of the given path that points a defined
8047 * value in the tree
8048 */
8049 ImmutableTree.prototype.findRootMostValueAndPath = function (relativePath) {
8050 return this.findRootMostMatchingPathAndValue(relativePath, function () { return true; });
8051 };
8052 /**
8053 * @returns The subtree at the given path
8054 */
8055 ImmutableTree.prototype.subtree = function (relativePath) {
8056 if (pathIsEmpty(relativePath)) {
8057 return this;
8058 }
8059 else {
8060 var front = pathGetFront(relativePath);
8061 var childTree = this.children.get(front);
8062 if (childTree !== null) {
8063 return childTree.subtree(pathPopFront(relativePath));
8064 }
8065 else {
8066 return new ImmutableTree(null);
8067 }
8068 }
8069 };
8070 /**
8071 * Sets a value at the specified path.
8072 *
8073 * @param relativePath - Path to set value at.
8074 * @param toSet - Value to set.
8075 * @returns Resulting tree.
8076 */
8077 ImmutableTree.prototype.set = function (relativePath, toSet) {
8078 if (pathIsEmpty(relativePath)) {
8079 return new ImmutableTree(toSet, this.children);
8080 }
8081 else {
8082 var front = pathGetFront(relativePath);
8083 var child = this.children.get(front) || new ImmutableTree(null);
8084 var newChild = child.set(pathPopFront(relativePath), toSet);
8085 var newChildren = this.children.insert(front, newChild);
8086 return new ImmutableTree(this.value, newChildren);
8087 }
8088 };
8089 /**
8090 * Removes the value at the specified path.
8091 *
8092 * @param relativePath - Path to value to remove.
8093 * @returns Resulting tree.
8094 */
8095 ImmutableTree.prototype.remove = function (relativePath) {
8096 if (pathIsEmpty(relativePath)) {
8097 if (this.children.isEmpty()) {
8098 return new ImmutableTree(null);
8099 }
8100 else {
8101 return new ImmutableTree(null, this.children);
8102 }
8103 }
8104 else {
8105 var front = pathGetFront(relativePath);
8106 var child = this.children.get(front);
8107 if (child) {
8108 var newChild = child.remove(pathPopFront(relativePath));
8109 var newChildren = void 0;
8110 if (newChild.isEmpty()) {
8111 newChildren = this.children.remove(front);
8112 }
8113 else {
8114 newChildren = this.children.insert(front, newChild);
8115 }
8116 if (this.value === null && newChildren.isEmpty()) {
8117 return new ImmutableTree(null);
8118 }
8119 else {
8120 return new ImmutableTree(this.value, newChildren);
8121 }
8122 }
8123 else {
8124 return this;
8125 }
8126 }
8127 };
8128 /**
8129 * Gets a value from the tree.
8130 *
8131 * @param relativePath - Path to get value for.
8132 * @returns Value at path, or null.
8133 */
8134 ImmutableTree.prototype.get = function (relativePath) {
8135 if (pathIsEmpty(relativePath)) {
8136 return this.value;
8137 }
8138 else {
8139 var front = pathGetFront(relativePath);
8140 var child = this.children.get(front);
8141 if (child) {
8142 return child.get(pathPopFront(relativePath));
8143 }
8144 else {
8145 return null;
8146 }
8147 }
8148 };
8149 /**
8150 * Replace the subtree at the specified path with the given new tree.
8151 *
8152 * @param relativePath - Path to replace subtree for.
8153 * @param newTree - New tree.
8154 * @returns Resulting tree.
8155 */
8156 ImmutableTree.prototype.setTree = function (relativePath, newTree) {
8157 if (pathIsEmpty(relativePath)) {
8158 return newTree;
8159 }
8160 else {
8161 var front = pathGetFront(relativePath);
8162 var child = this.children.get(front) || new ImmutableTree(null);
8163 var newChild = child.setTree(pathPopFront(relativePath), newTree);
8164 var newChildren = void 0;
8165 if (newChild.isEmpty()) {
8166 newChildren = this.children.remove(front);
8167 }
8168 else {
8169 newChildren = this.children.insert(front, newChild);
8170 }
8171 return new ImmutableTree(this.value, newChildren);
8172 }
8173 };
8174 /**
8175 * Performs a depth first fold on this tree. Transforms a tree into a single
8176 * value, given a function that operates on the path to a node, an optional
8177 * current value, and a map of child names to folded subtrees
8178 */
8179 ImmutableTree.prototype.fold = function (fn) {
8180 return this.fold_(newEmptyPath(), fn);
8181 };
8182 /**
8183 * Recursive helper for public-facing fold() method
8184 */
8185 ImmutableTree.prototype.fold_ = function (pathSoFar, fn) {
8186 var accum = {};
8187 this.children.inorderTraversal(function (childKey, childTree) {
8188 accum[childKey] = childTree.fold_(pathChild(pathSoFar, childKey), fn);
8189 });
8190 return fn(pathSoFar, this.value, accum);
8191 };
8192 /**
8193 * Find the first matching value on the given path. Return the result of applying f to it.
8194 */
8195 ImmutableTree.prototype.findOnPath = function (path, f) {
8196 return this.findOnPath_(path, newEmptyPath(), f);
8197 };
8198 ImmutableTree.prototype.findOnPath_ = function (pathToFollow, pathSoFar, f) {
8199 var result = this.value ? f(pathSoFar, this.value) : false;
8200 if (result) {
8201 return result;
8202 }
8203 else {
8204 if (pathIsEmpty(pathToFollow)) {
8205 return null;
8206 }
8207 else {
8208 var front = pathGetFront(pathToFollow);
8209 var nextChild = this.children.get(front);
8210 if (nextChild) {
8211 return nextChild.findOnPath_(pathPopFront(pathToFollow), pathChild(pathSoFar, front), f);
8212 }
8213 else {
8214 return null;
8215 }
8216 }
8217 }
8218 };
8219 ImmutableTree.prototype.foreachOnPath = function (path, f) {
8220 return this.foreachOnPath_(path, newEmptyPath(), f);
8221 };
8222 ImmutableTree.prototype.foreachOnPath_ = function (pathToFollow, currentRelativePath, f) {
8223 if (pathIsEmpty(pathToFollow)) {
8224 return this;
8225 }
8226 else {
8227 if (this.value) {
8228 f(currentRelativePath, this.value);
8229 }
8230 var front = pathGetFront(pathToFollow);
8231 var nextChild = this.children.get(front);
8232 if (nextChild) {
8233 return nextChild.foreachOnPath_(pathPopFront(pathToFollow), pathChild(currentRelativePath, front), f);
8234 }
8235 else {
8236 return new ImmutableTree(null);
8237 }
8238 }
8239 };
8240 /**
8241 * Calls the given function for each node in the tree that has a value.
8242 *
8243 * @param f - A function to be called with the path from the root of the tree to
8244 * a node, and the value at that node. Called in depth-first order.
8245 */
8246 ImmutableTree.prototype.foreach = function (f) {
8247 this.foreach_(newEmptyPath(), f);
8248 };
8249 ImmutableTree.prototype.foreach_ = function (currentRelativePath, f) {
8250 this.children.inorderTraversal(function (childName, childTree) {
8251 childTree.foreach_(pathChild(currentRelativePath, childName), f);
8252 });
8253 if (this.value) {
8254 f(currentRelativePath, this.value);
8255 }
8256 };
8257 ImmutableTree.prototype.foreachChild = function (f) {
8258 this.children.inorderTraversal(function (childName, childTree) {
8259 if (childTree.value) {
8260 f(childName, childTree.value);
8261 }
8262 });
8263 };
8264 return ImmutableTree;
8265}());
8266
8267/**
8268 * @license
8269 * Copyright 2017 Google LLC
8270 *
8271 * Licensed under the Apache License, Version 2.0 (the "License");
8272 * you may not use this file except in compliance with the License.
8273 * You may obtain a copy of the License at
8274 *
8275 * http://www.apache.org/licenses/LICENSE-2.0
8276 *
8277 * Unless required by applicable law or agreed to in writing, software
8278 * distributed under the License is distributed on an "AS IS" BASIS,
8279 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8280 * See the License for the specific language governing permissions and
8281 * limitations under the License.
8282 */
8283/**
8284 * This class holds a collection of writes that can be applied to nodes in unison. It abstracts away the logic with
8285 * dealing with priority writes and multiple nested writes. At any given path there is only allowed to be one write
8286 * modifying that path. Any write to an existing path or shadowing an existing path will modify that existing write
8287 * to reflect the write added.
8288 */
8289var CompoundWrite = /** @class */ (function () {
8290 function CompoundWrite(writeTree_) {
8291 this.writeTree_ = writeTree_;
8292 }
8293 CompoundWrite.empty = function () {
8294 return new CompoundWrite(new ImmutableTree(null));
8295 };
8296 return CompoundWrite;
8297}());
8298function compoundWriteAddWrite(compoundWrite, path, node) {
8299 if (pathIsEmpty(path)) {
8300 return new CompoundWrite(new ImmutableTree(node));
8301 }
8302 else {
8303 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8304 if (rootmost != null) {
8305 var rootMostPath = rootmost.path;
8306 var value = rootmost.value;
8307 var relativePath = newRelativePath(rootMostPath, path);
8308 value = value.updateChild(relativePath, node);
8309 return new CompoundWrite(compoundWrite.writeTree_.set(rootMostPath, value));
8310 }
8311 else {
8312 var subtree = new ImmutableTree(node);
8313 var newWriteTree = compoundWrite.writeTree_.setTree(path, subtree);
8314 return new CompoundWrite(newWriteTree);
8315 }
8316 }
8317}
8318function compoundWriteAddWrites(compoundWrite, path, updates) {
8319 var newWrite = compoundWrite;
8320 each(updates, function (childKey, node) {
8321 newWrite = compoundWriteAddWrite(newWrite, pathChild(path, childKey), node);
8322 });
8323 return newWrite;
8324}
8325/**
8326 * Will remove a write at the given path and deeper paths. This will <em>not</em> modify a write at a higher
8327 * location, which must be removed by calling this method with that path.
8328 *
8329 * @param compoundWrite - The CompoundWrite to remove.
8330 * @param path - The path at which a write and all deeper writes should be removed
8331 * @returns The new CompoundWrite with the removed path
8332 */
8333function compoundWriteRemoveWrite(compoundWrite, path) {
8334 if (pathIsEmpty(path)) {
8335 return CompoundWrite.empty();
8336 }
8337 else {
8338 var newWriteTree = compoundWrite.writeTree_.setTree(path, new ImmutableTree(null));
8339 return new CompoundWrite(newWriteTree);
8340 }
8341}
8342/**
8343 * Returns whether this CompoundWrite will fully overwrite a node at a given location and can therefore be
8344 * considered "complete".
8345 *
8346 * @param compoundWrite - The CompoundWrite to check.
8347 * @param path - The path to check for
8348 * @returns Whether there is a complete write at that path
8349 */
8350function compoundWriteHasCompleteWrite(compoundWrite, path) {
8351 return compoundWriteGetCompleteNode(compoundWrite, path) != null;
8352}
8353/**
8354 * Returns a node for a path if and only if the node is a "complete" overwrite at that path. This will not aggregate
8355 * writes from deeper paths, but will return child nodes from a more shallow path.
8356 *
8357 * @param compoundWrite - The CompoundWrite to get the node from.
8358 * @param path - The path to get a complete write
8359 * @returns The node if complete at that path, or null otherwise.
8360 */
8361function compoundWriteGetCompleteNode(compoundWrite, path) {
8362 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8363 if (rootmost != null) {
8364 return compoundWrite.writeTree_
8365 .get(rootmost.path)
8366 .getChild(newRelativePath(rootmost.path, path));
8367 }
8368 else {
8369 return null;
8370 }
8371}
8372/**
8373 * Returns all children that are guaranteed to be a complete overwrite.
8374 *
8375 * @param compoundWrite - The CompoundWrite to get children from.
8376 * @returns A list of all complete children.
8377 */
8378function compoundWriteGetCompleteChildren(compoundWrite) {
8379 var children = [];
8380 var node = compoundWrite.writeTree_.value;
8381 if (node != null) {
8382 // If it's a leaf node, it has no children; so nothing to do.
8383 if (!node.isLeafNode()) {
8384 node.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8385 children.push(new NamedNode(childName, childNode));
8386 });
8387 }
8388 }
8389 else {
8390 compoundWrite.writeTree_.children.inorderTraversal(function (childName, childTree) {
8391 if (childTree.value != null) {
8392 children.push(new NamedNode(childName, childTree.value));
8393 }
8394 });
8395 }
8396 return children;
8397}
8398function compoundWriteChildCompoundWrite(compoundWrite, path) {
8399 if (pathIsEmpty(path)) {
8400 return compoundWrite;
8401 }
8402 else {
8403 var shadowingNode = compoundWriteGetCompleteNode(compoundWrite, path);
8404 if (shadowingNode != null) {
8405 return new CompoundWrite(new ImmutableTree(shadowingNode));
8406 }
8407 else {
8408 return new CompoundWrite(compoundWrite.writeTree_.subtree(path));
8409 }
8410 }
8411}
8412/**
8413 * Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
8414 * @returns Whether this CompoundWrite is empty
8415 */
8416function compoundWriteIsEmpty(compoundWrite) {
8417 return compoundWrite.writeTree_.isEmpty();
8418}
8419/**
8420 * Applies this CompoundWrite to a node. The node is returned with all writes from this CompoundWrite applied to the
8421 * node
8422 * @param node - The node to apply this CompoundWrite to
8423 * @returns The node with all writes applied
8424 */
8425function compoundWriteApply(compoundWrite, node) {
8426 return applySubtreeWrite(newEmptyPath(), compoundWrite.writeTree_, node);
8427}
8428function applySubtreeWrite(relativePath, writeTree, node) {
8429 if (writeTree.value != null) {
8430 // Since there a write is always a leaf, we're done here
8431 return node.updateChild(relativePath, writeTree.value);
8432 }
8433 else {
8434 var priorityWrite_1 = null;
8435 writeTree.children.inorderTraversal(function (childKey, childTree) {
8436 if (childKey === '.priority') {
8437 // Apply priorities at the end so we don't update priorities for either empty nodes or forget
8438 // to apply priorities to empty nodes that are later filled
8439 assert(childTree.value !== null, 'Priority writes must always be leaf nodes');
8440 priorityWrite_1 = childTree.value;
8441 }
8442 else {
8443 node = applySubtreeWrite(pathChild(relativePath, childKey), childTree, node);
8444 }
8445 });
8446 // If there was a priority write, we only apply it if the node is not empty
8447 if (!node.getChild(relativePath).isEmpty() && priorityWrite_1 !== null) {
8448 node = node.updateChild(pathChild(relativePath, '.priority'), priorityWrite_1);
8449 }
8450 return node;
8451 }
8452}
8453
8454/**
8455 * @license
8456 * Copyright 2017 Google LLC
8457 *
8458 * Licensed under the Apache License, Version 2.0 (the "License");
8459 * you may not use this file except in compliance with the License.
8460 * You may obtain a copy of the License at
8461 *
8462 * http://www.apache.org/licenses/LICENSE-2.0
8463 *
8464 * Unless required by applicable law or agreed to in writing, software
8465 * distributed under the License is distributed on an "AS IS" BASIS,
8466 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8467 * See the License for the specific language governing permissions and
8468 * limitations under the License.
8469 */
8470/**
8471 * Create a new WriteTreeRef for the given path. For use with a new sync point at the given path.
8472 *
8473 */
8474function writeTreeChildWrites(writeTree, path) {
8475 return newWriteTreeRef(path, writeTree);
8476}
8477/**
8478 * Record a new overwrite from user code.
8479 *
8480 * @param visible - This is set to false by some transactions. It should be excluded from event caches
8481 */
8482function writeTreeAddOverwrite(writeTree, path, snap, writeId, visible) {
8483 assert(writeId > writeTree.lastWriteId, 'Stacking an older write on top of newer ones');
8484 if (visible === undefined) {
8485 visible = true;
8486 }
8487 writeTree.allWrites.push({
8488 path: path,
8489 snap: snap,
8490 writeId: writeId,
8491 visible: visible
8492 });
8493 if (visible) {
8494 writeTree.visibleWrites = compoundWriteAddWrite(writeTree.visibleWrites, path, snap);
8495 }
8496 writeTree.lastWriteId = writeId;
8497}
8498/**
8499 * Record a new merge from user code.
8500 */
8501function writeTreeAddMerge(writeTree, path, changedChildren, writeId) {
8502 assert(writeId > writeTree.lastWriteId, 'Stacking an older merge on top of newer ones');
8503 writeTree.allWrites.push({
8504 path: path,
8505 children: changedChildren,
8506 writeId: writeId,
8507 visible: true
8508 });
8509 writeTree.visibleWrites = compoundWriteAddWrites(writeTree.visibleWrites, path, changedChildren);
8510 writeTree.lastWriteId = writeId;
8511}
8512function writeTreeGetWrite(writeTree, writeId) {
8513 for (var i = 0; i < writeTree.allWrites.length; i++) {
8514 var record = writeTree.allWrites[i];
8515 if (record.writeId === writeId) {
8516 return record;
8517 }
8518 }
8519 return null;
8520}
8521/**
8522 * Remove a write (either an overwrite or merge) that has been successfully acknowledge by the server. Recalculates
8523 * the tree if necessary. We return true if it may have been visible, meaning views need to reevaluate.
8524 *
8525 * @returns true if the write may have been visible (meaning we'll need to reevaluate / raise
8526 * events as a result).
8527 */
8528function writeTreeRemoveWrite(writeTree, writeId) {
8529 // Note: disabling this check. It could be a transaction that preempted another transaction, and thus was applied
8530 // out of order.
8531 //const validClear = revert || this.allWrites_.length === 0 || writeId <= this.allWrites_[0].writeId;
8532 //assert(validClear, "Either we don't have this write, or it's the first one in the queue");
8533 var idx = writeTree.allWrites.findIndex(function (s) {
8534 return s.writeId === writeId;
8535 });
8536 assert(idx >= 0, 'removeWrite called with nonexistent writeId.');
8537 var writeToRemove = writeTree.allWrites[idx];
8538 writeTree.allWrites.splice(idx, 1);
8539 var removedWriteWasVisible = writeToRemove.visible;
8540 var removedWriteOverlapsWithOtherWrites = false;
8541 var i = writeTree.allWrites.length - 1;
8542 while (removedWriteWasVisible && i >= 0) {
8543 var currentWrite = writeTree.allWrites[i];
8544 if (currentWrite.visible) {
8545 if (i >= idx &&
8546 writeTreeRecordContainsPath_(currentWrite, writeToRemove.path)) {
8547 // The removed write was completely shadowed by a subsequent write.
8548 removedWriteWasVisible = false;
8549 }
8550 else if (pathContains(writeToRemove.path, currentWrite.path)) {
8551 // Either we're covering some writes or they're covering part of us (depending on which came first).
8552 removedWriteOverlapsWithOtherWrites = true;
8553 }
8554 }
8555 i--;
8556 }
8557 if (!removedWriteWasVisible) {
8558 return false;
8559 }
8560 else if (removedWriteOverlapsWithOtherWrites) {
8561 // There's some shadowing going on. Just rebuild the visible writes from scratch.
8562 writeTreeResetTree_(writeTree);
8563 return true;
8564 }
8565 else {
8566 // There's no shadowing. We can safely just remove the write(s) from visibleWrites.
8567 if (writeToRemove.snap) {
8568 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, writeToRemove.path);
8569 }
8570 else {
8571 var children = writeToRemove.children;
8572 each(children, function (childName) {
8573 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, pathChild(writeToRemove.path, childName));
8574 });
8575 }
8576 return true;
8577 }
8578}
8579function writeTreeRecordContainsPath_(writeRecord, path) {
8580 if (writeRecord.snap) {
8581 return pathContains(writeRecord.path, path);
8582 }
8583 else {
8584 for (var childName in writeRecord.children) {
8585 if (writeRecord.children.hasOwnProperty(childName) &&
8586 pathContains(pathChild(writeRecord.path, childName), path)) {
8587 return true;
8588 }
8589 }
8590 return false;
8591 }
8592}
8593/**
8594 * Re-layer the writes and merges into a tree so we can efficiently calculate event snapshots
8595 */
8596function writeTreeResetTree_(writeTree) {
8597 writeTree.visibleWrites = writeTreeLayerTree_(writeTree.allWrites, writeTreeDefaultFilter_, newEmptyPath());
8598 if (writeTree.allWrites.length > 0) {
8599 writeTree.lastWriteId =
8600 writeTree.allWrites[writeTree.allWrites.length - 1].writeId;
8601 }
8602 else {
8603 writeTree.lastWriteId = -1;
8604 }
8605}
8606/**
8607 * The default filter used when constructing the tree. Keep everything that's visible.
8608 */
8609function writeTreeDefaultFilter_(write) {
8610 return write.visible;
8611}
8612/**
8613 * Static method. Given an array of WriteRecords, a filter for which ones to include, and a path, construct the tree of
8614 * event data at that path.
8615 */
8616function writeTreeLayerTree_(writes, filter, treeRoot) {
8617 var compoundWrite = CompoundWrite.empty();
8618 for (var i = 0; i < writes.length; ++i) {
8619 var write = writes[i];
8620 // Theory, a later set will either:
8621 // a) abort a relevant transaction, so no need to worry about excluding it from calculating that transaction
8622 // b) not be relevant to a transaction (separate branch), so again will not affect the data for that transaction
8623 if (filter(write)) {
8624 var writePath = write.path;
8625 var relativePath = void 0;
8626 if (write.snap) {
8627 if (pathContains(treeRoot, writePath)) {
8628 relativePath = newRelativePath(treeRoot, writePath);
8629 compoundWrite = compoundWriteAddWrite(compoundWrite, relativePath, write.snap);
8630 }
8631 else if (pathContains(writePath, treeRoot)) {
8632 relativePath = newRelativePath(writePath, treeRoot);
8633 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), write.snap.getChild(relativePath));
8634 }
8635 else ;
8636 }
8637 else if (write.children) {
8638 if (pathContains(treeRoot, writePath)) {
8639 relativePath = newRelativePath(treeRoot, writePath);
8640 compoundWrite = compoundWriteAddWrites(compoundWrite, relativePath, write.children);
8641 }
8642 else if (pathContains(writePath, treeRoot)) {
8643 relativePath = newRelativePath(writePath, treeRoot);
8644 if (pathIsEmpty(relativePath)) {
8645 compoundWrite = compoundWriteAddWrites(compoundWrite, newEmptyPath(), write.children);
8646 }
8647 else {
8648 var child = safeGet(write.children, pathGetFront(relativePath));
8649 if (child) {
8650 // There exists a child in this node that matches the root path
8651 var deepNode = child.getChild(pathPopFront(relativePath));
8652 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), deepNode);
8653 }
8654 }
8655 }
8656 else ;
8657 }
8658 else {
8659 throw assertionError('WriteRecord should have .snap or .children');
8660 }
8661 }
8662 }
8663 return compoundWrite;
8664}
8665/**
8666 * Given optional, underlying server data, and an optional set of constraints (exclude some sets, include hidden
8667 * writes), attempt to calculate a complete snapshot for the given path
8668 *
8669 * @param writeIdsToExclude - An optional set to be excluded
8670 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8671 */
8672function writeTreeCalcCompleteEventCache(writeTree, treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8673 if (!writeIdsToExclude && !includeHiddenWrites) {
8674 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8675 if (shadowingNode != null) {
8676 return shadowingNode;
8677 }
8678 else {
8679 var subMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8680 if (compoundWriteIsEmpty(subMerge)) {
8681 return completeServerCache;
8682 }
8683 else if (completeServerCache == null &&
8684 !compoundWriteHasCompleteWrite(subMerge, newEmptyPath())) {
8685 // We wouldn't have a complete snapshot, since there's no underlying data and no complete shadow
8686 return null;
8687 }
8688 else {
8689 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8690 return compoundWriteApply(subMerge, layeredCache);
8691 }
8692 }
8693 }
8694 else {
8695 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8696 if (!includeHiddenWrites && compoundWriteIsEmpty(merge)) {
8697 return completeServerCache;
8698 }
8699 else {
8700 // If the server cache is null, and we don't have a complete cache, we need to return null
8701 if (!includeHiddenWrites &&
8702 completeServerCache == null &&
8703 !compoundWriteHasCompleteWrite(merge, newEmptyPath())) {
8704 return null;
8705 }
8706 else {
8707 var filter = function (write) {
8708 return ((write.visible || includeHiddenWrites) &&
8709 (!writeIdsToExclude ||
8710 !~writeIdsToExclude.indexOf(write.writeId)) &&
8711 (pathContains(write.path, treePath) ||
8712 pathContains(treePath, write.path)));
8713 };
8714 var mergeAtPath = writeTreeLayerTree_(writeTree.allWrites, filter, treePath);
8715 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8716 return compoundWriteApply(mergeAtPath, layeredCache);
8717 }
8718 }
8719 }
8720}
8721/**
8722 * With optional, underlying server data, attempt to return a children node of children that we have complete data for.
8723 * Used when creating new views, to pre-fill their complete event children snapshot.
8724 */
8725function writeTreeCalcCompleteEventChildren(writeTree, treePath, completeServerChildren) {
8726 var completeChildren = ChildrenNode.EMPTY_NODE;
8727 var topLevelSet = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8728 if (topLevelSet) {
8729 if (!topLevelSet.isLeafNode()) {
8730 // we're shadowing everything. Return the children.
8731 topLevelSet.forEachChild(PRIORITY_INDEX, function (childName, childSnap) {
8732 completeChildren = completeChildren.updateImmediateChild(childName, childSnap);
8733 });
8734 }
8735 return completeChildren;
8736 }
8737 else if (completeServerChildren) {
8738 // Layer any children we have on top of this
8739 // We know we don't have a top-level set, so just enumerate existing children
8740 var merge_1 = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8741 completeServerChildren.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8742 var node = compoundWriteApply(compoundWriteChildCompoundWrite(merge_1, new Path(childName)), childNode);
8743 completeChildren = completeChildren.updateImmediateChild(childName, node);
8744 });
8745 // Add any complete children we have from the set
8746 compoundWriteGetCompleteChildren(merge_1).forEach(function (namedNode) {
8747 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8748 });
8749 return completeChildren;
8750 }
8751 else {
8752 // We don't have anything to layer on top of. Layer on any children we have
8753 // Note that we can return an empty snap if we have a defined delete
8754 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8755 compoundWriteGetCompleteChildren(merge).forEach(function (namedNode) {
8756 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8757 });
8758 return completeChildren;
8759 }
8760}
8761/**
8762 * Given that the underlying server data has updated, determine what, if anything, needs to be
8763 * applied to the event cache.
8764 *
8765 * Possibilities:
8766 *
8767 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8768 *
8769 * 2. Some write is completely shadowing. No events to be raised
8770 *
8771 * 3. Is partially shadowed. Events
8772 *
8773 * Either existingEventSnap or existingServerSnap must exist
8774 */
8775function writeTreeCalcEventCacheAfterServerOverwrite(writeTree, treePath, childPath, existingEventSnap, existingServerSnap) {
8776 assert(existingEventSnap || existingServerSnap, 'Either existingEventSnap or existingServerSnap must exist');
8777 var path = pathChild(treePath, childPath);
8778 if (compoundWriteHasCompleteWrite(writeTree.visibleWrites, path)) {
8779 // At this point we can probably guarantee that we're in case 2, meaning no events
8780 // May need to check visibility while doing the findRootMostValueAndPath call
8781 return null;
8782 }
8783 else {
8784 // No complete shadowing. We're either partially shadowing or not shadowing at all.
8785 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8786 if (compoundWriteIsEmpty(childMerge)) {
8787 // We're not shadowing at all. Case 1
8788 return existingServerSnap.getChild(childPath);
8789 }
8790 else {
8791 // This could be more efficient if the serverNode + updates doesn't change the eventSnap
8792 // However this is tricky to find out, since user updates don't necessary change the server
8793 // snap, e.g. priority updates on empty nodes, or deep deletes. Another special case is if the server
8794 // adds nodes, but doesn't change any existing writes. It is therefore not enough to
8795 // only check if the updates change the serverNode.
8796 // Maybe check if the merge tree contains these special cases and only do a full overwrite in that case?
8797 return compoundWriteApply(childMerge, existingServerSnap.getChild(childPath));
8798 }
8799 }
8800}
8801/**
8802 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8803 * complete child for this ChildKey.
8804 */
8805function writeTreeCalcCompleteChild(writeTree, treePath, childKey, existingServerSnap) {
8806 var path = pathChild(treePath, childKey);
8807 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8808 if (shadowingNode != null) {
8809 return shadowingNode;
8810 }
8811 else {
8812 if (existingServerSnap.isCompleteForChild(childKey)) {
8813 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8814 return compoundWriteApply(childMerge, existingServerSnap.getNode().getImmediateChild(childKey));
8815 }
8816 else {
8817 return null;
8818 }
8819 }
8820}
8821/**
8822 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8823 * a higher path, this will return the child of that write relative to the write and this path.
8824 * Returns null if there is no write at this path.
8825 */
8826function writeTreeShadowingWrite(writeTree, path) {
8827 return compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8828}
8829/**
8830 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8831 * the window, but may now be in the window.
8832 */
8833function writeTreeCalcIndexedSlice(writeTree, treePath, completeServerData, startPost, count, reverse, index) {
8834 var toIterate;
8835 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8836 var shadowingNode = compoundWriteGetCompleteNode(merge, newEmptyPath());
8837 if (shadowingNode != null) {
8838 toIterate = shadowingNode;
8839 }
8840 else if (completeServerData != null) {
8841 toIterate = compoundWriteApply(merge, completeServerData);
8842 }
8843 else {
8844 // no children to iterate on
8845 return [];
8846 }
8847 toIterate = toIterate.withIndex(index);
8848 if (!toIterate.isEmpty() && !toIterate.isLeafNode()) {
8849 var nodes = [];
8850 var cmp = index.getCompare();
8851 var iter = reverse
8852 ? toIterate.getReverseIteratorFrom(startPost, index)
8853 : toIterate.getIteratorFrom(startPost, index);
8854 var next = iter.getNext();
8855 while (next && nodes.length < count) {
8856 if (cmp(next, startPost) !== 0) {
8857 nodes.push(next);
8858 }
8859 next = iter.getNext();
8860 }
8861 return nodes;
8862 }
8863 else {
8864 return [];
8865 }
8866}
8867function newWriteTree() {
8868 return {
8869 visibleWrites: CompoundWrite.empty(),
8870 allWrites: [],
8871 lastWriteId: -1
8872 };
8873}
8874/**
8875 * If possible, returns a complete event cache, using the underlying server data if possible. In addition, can be used
8876 * to get a cache that includes hidden writes, and excludes arbitrary writes. Note that customizing the returned node
8877 * can lead to a more expensive calculation.
8878 *
8879 * @param writeIdsToExclude - Optional writes to exclude.
8880 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8881 */
8882function writeTreeRefCalcCompleteEventCache(writeTreeRef, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8883 return writeTreeCalcCompleteEventCache(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites);
8884}
8885/**
8886 * If possible, returns a children node containing all of the complete children we have data for. The returned data is a
8887 * mix of the given server data and write data.
8888 *
8889 */
8890function writeTreeRefCalcCompleteEventChildren(writeTreeRef, completeServerChildren) {
8891 return writeTreeCalcCompleteEventChildren(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerChildren);
8892}
8893/**
8894 * Given that either the underlying server data has updated or the outstanding writes have updated, determine what,
8895 * if anything, needs to be applied to the event cache.
8896 *
8897 * Possibilities:
8898 *
8899 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8900 *
8901 * 2. Some write is completely shadowing. No events to be raised
8902 *
8903 * 3. Is partially shadowed. Events should be raised
8904 *
8905 * Either existingEventSnap or existingServerSnap must exist, this is validated via an assert
8906 *
8907 *
8908 */
8909function writeTreeRefCalcEventCacheAfterServerOverwrite(writeTreeRef, path, existingEventSnap, existingServerSnap) {
8910 return writeTreeCalcEventCacheAfterServerOverwrite(writeTreeRef.writeTree, writeTreeRef.treePath, path, existingEventSnap, existingServerSnap);
8911}
8912/**
8913 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8914 * a higher path, this will return the child of that write relative to the write and this path.
8915 * Returns null if there is no write at this path.
8916 *
8917 */
8918function writeTreeRefShadowingWrite(writeTreeRef, path) {
8919 return writeTreeShadowingWrite(writeTreeRef.writeTree, pathChild(writeTreeRef.treePath, path));
8920}
8921/**
8922 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8923 * the window, but may now be in the window
8924 */
8925function writeTreeRefCalcIndexedSlice(writeTreeRef, completeServerData, startPost, count, reverse, index) {
8926 return writeTreeCalcIndexedSlice(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerData, startPost, count, reverse, index);
8927}
8928/**
8929 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8930 * complete child for this ChildKey.
8931 */
8932function writeTreeRefCalcCompleteChild(writeTreeRef, childKey, existingServerCache) {
8933 return writeTreeCalcCompleteChild(writeTreeRef.writeTree, writeTreeRef.treePath, childKey, existingServerCache);
8934}
8935/**
8936 * Return a WriteTreeRef for a child.
8937 */
8938function writeTreeRefChild(writeTreeRef, childName) {
8939 return newWriteTreeRef(pathChild(writeTreeRef.treePath, childName), writeTreeRef.writeTree);
8940}
8941function newWriteTreeRef(path, writeTree) {
8942 return {
8943 treePath: path,
8944 writeTree: writeTree
8945 };
8946}
8947
8948/**
8949 * @license
8950 * Copyright 2017 Google LLC
8951 *
8952 * Licensed under the Apache License, Version 2.0 (the "License");
8953 * you may not use this file except in compliance with the License.
8954 * You may obtain a copy of the License at
8955 *
8956 * http://www.apache.org/licenses/LICENSE-2.0
8957 *
8958 * Unless required by applicable law or agreed to in writing, software
8959 * distributed under the License is distributed on an "AS IS" BASIS,
8960 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8961 * See the License for the specific language governing permissions and
8962 * limitations under the License.
8963 */
8964var ChildChangeAccumulator = /** @class */ (function () {
8965 function ChildChangeAccumulator() {
8966 this.changeMap = new Map();
8967 }
8968 ChildChangeAccumulator.prototype.trackChildChange = function (change) {
8969 var type = change.type;
8970 var childKey = change.childName;
8971 assert(type === "child_added" /* CHILD_ADDED */ ||
8972 type === "child_changed" /* CHILD_CHANGED */ ||
8973 type === "child_removed" /* CHILD_REMOVED */, 'Only child changes supported for tracking');
8974 assert(childKey !== '.priority', 'Only non-priority child changes can be tracked.');
8975 var oldChange = this.changeMap.get(childKey);
8976 if (oldChange) {
8977 var oldType = oldChange.type;
8978 if (type === "child_added" /* CHILD_ADDED */ &&
8979 oldType === "child_removed" /* CHILD_REMOVED */) {
8980 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.snapshotNode));
8981 }
8982 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8983 oldType === "child_added" /* CHILD_ADDED */) {
8984 this.changeMap.delete(childKey);
8985 }
8986 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8987 oldType === "child_changed" /* CHILD_CHANGED */) {
8988 this.changeMap.set(childKey, changeChildRemoved(childKey, oldChange.oldSnap));
8989 }
8990 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8991 oldType === "child_added" /* CHILD_ADDED */) {
8992 this.changeMap.set(childKey, changeChildAdded(childKey, change.snapshotNode));
8993 }
8994 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8995 oldType === "child_changed" /* CHILD_CHANGED */) {
8996 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.oldSnap));
8997 }
8998 else {
8999 throw assertionError('Illegal combination of changes: ' +
9000 change +
9001 ' occurred after ' +
9002 oldChange);
9003 }
9004 }
9005 else {
9006 this.changeMap.set(childKey, change);
9007 }
9008 };
9009 ChildChangeAccumulator.prototype.getChanges = function () {
9010 return Array.from(this.changeMap.values());
9011 };
9012 return ChildChangeAccumulator;
9013}());
9014
9015/**
9016 * @license
9017 * Copyright 2017 Google LLC
9018 *
9019 * Licensed under the Apache License, Version 2.0 (the "License");
9020 * you may not use this file except in compliance with the License.
9021 * You may obtain a copy of the License at
9022 *
9023 * http://www.apache.org/licenses/LICENSE-2.0
9024 *
9025 * Unless required by applicable law or agreed to in writing, software
9026 * distributed under the License is distributed on an "AS IS" BASIS,
9027 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9028 * See the License for the specific language governing permissions and
9029 * limitations under the License.
9030 */
9031/**
9032 * An implementation of CompleteChildSource that never returns any additional children
9033 */
9034// eslint-disable-next-line @typescript-eslint/naming-convention
9035var NoCompleteChildSource_ = /** @class */ (function () {
9036 function NoCompleteChildSource_() {
9037 }
9038 NoCompleteChildSource_.prototype.getCompleteChild = function (childKey) {
9039 return null;
9040 };
9041 NoCompleteChildSource_.prototype.getChildAfterChild = function (index, child, reverse) {
9042 return null;
9043 };
9044 return NoCompleteChildSource_;
9045}());
9046/**
9047 * Singleton instance.
9048 */
9049var NO_COMPLETE_CHILD_SOURCE = new NoCompleteChildSource_();
9050/**
9051 * An implementation of CompleteChildSource that uses a WriteTree in addition to any other server data or
9052 * old event caches available to calculate complete children.
9053 */
9054var WriteTreeCompleteChildSource = /** @class */ (function () {
9055 function WriteTreeCompleteChildSource(writes_, viewCache_, optCompleteServerCache_) {
9056 if (optCompleteServerCache_ === void 0) { optCompleteServerCache_ = null; }
9057 this.writes_ = writes_;
9058 this.viewCache_ = viewCache_;
9059 this.optCompleteServerCache_ = optCompleteServerCache_;
9060 }
9061 WriteTreeCompleteChildSource.prototype.getCompleteChild = function (childKey) {
9062 var node = this.viewCache_.eventCache;
9063 if (node.isCompleteForChild(childKey)) {
9064 return node.getNode().getImmediateChild(childKey);
9065 }
9066 else {
9067 var serverNode = this.optCompleteServerCache_ != null
9068 ? new CacheNode(this.optCompleteServerCache_, true, false)
9069 : this.viewCache_.serverCache;
9070 return writeTreeRefCalcCompleteChild(this.writes_, childKey, serverNode);
9071 }
9072 };
9073 WriteTreeCompleteChildSource.prototype.getChildAfterChild = function (index, child, reverse) {
9074 var completeServerData = this.optCompleteServerCache_ != null
9075 ? this.optCompleteServerCache_
9076 : viewCacheGetCompleteServerSnap(this.viewCache_);
9077 var nodes = writeTreeRefCalcIndexedSlice(this.writes_, completeServerData, child, 1, reverse, index);
9078 if (nodes.length === 0) {
9079 return null;
9080 }
9081 else {
9082 return nodes[0];
9083 }
9084 };
9085 return WriteTreeCompleteChildSource;
9086}());
9087
9088/**
9089 * @license
9090 * Copyright 2017 Google LLC
9091 *
9092 * Licensed under the Apache License, Version 2.0 (the "License");
9093 * you may not use this file except in compliance with the License.
9094 * You may obtain a copy of the License at
9095 *
9096 * http://www.apache.org/licenses/LICENSE-2.0
9097 *
9098 * Unless required by applicable law or agreed to in writing, software
9099 * distributed under the License is distributed on an "AS IS" BASIS,
9100 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9101 * See the License for the specific language governing permissions and
9102 * limitations under the License.
9103 */
9104function newViewProcessor(filter) {
9105 return { filter: filter };
9106}
9107function viewProcessorAssertIndexed(viewProcessor, viewCache) {
9108 assert(viewCache.eventCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Event snap not indexed');
9109 assert(viewCache.serverCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Server snap not indexed');
9110}
9111function viewProcessorApplyOperation(viewProcessor, oldViewCache, operation, writesCache, completeCache) {
9112 var accumulator = new ChildChangeAccumulator();
9113 var newViewCache, filterServerNode;
9114 if (operation.type === OperationType.OVERWRITE) {
9115 var overwrite = operation;
9116 if (overwrite.source.fromUser) {
9117 newViewCache = viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, accumulator);
9118 }
9119 else {
9120 assert(overwrite.source.fromServer, 'Unknown source.');
9121 // We filter the node if it's a tagged update or the node has been previously filtered and the
9122 // update is not at the root in which case it is ok (and necessary) to mark the node unfiltered
9123 // again
9124 filterServerNode =
9125 overwrite.source.tagged ||
9126 (oldViewCache.serverCache.isFiltered() && !pathIsEmpty(overwrite.path));
9127 newViewCache = viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, filterServerNode, accumulator);
9128 }
9129 }
9130 else if (operation.type === OperationType.MERGE) {
9131 var merge = operation;
9132 if (merge.source.fromUser) {
9133 newViewCache = viewProcessorApplyUserMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, accumulator);
9134 }
9135 else {
9136 assert(merge.source.fromServer, 'Unknown source.');
9137 // We filter the node if it's a tagged update or the node has been previously filtered
9138 filterServerNode =
9139 merge.source.tagged || oldViewCache.serverCache.isFiltered();
9140 newViewCache = viewProcessorApplyServerMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, filterServerNode, accumulator);
9141 }
9142 }
9143 else if (operation.type === OperationType.ACK_USER_WRITE) {
9144 var ackUserWrite = operation;
9145 if (!ackUserWrite.revert) {
9146 newViewCache = viewProcessorAckUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, ackUserWrite.affectedTree, writesCache, completeCache, accumulator);
9147 }
9148 else {
9149 newViewCache = viewProcessorRevertUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, writesCache, completeCache, accumulator);
9150 }
9151 }
9152 else if (operation.type === OperationType.LISTEN_COMPLETE) {
9153 newViewCache = viewProcessorListenComplete(viewProcessor, oldViewCache, operation.path, writesCache, accumulator);
9154 }
9155 else {
9156 throw assertionError('Unknown operation type: ' + operation.type);
9157 }
9158 var changes = accumulator.getChanges();
9159 viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, changes);
9160 return { viewCache: newViewCache, changes: changes };
9161}
9162function viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, accumulator) {
9163 var eventSnap = newViewCache.eventCache;
9164 if (eventSnap.isFullyInitialized()) {
9165 var isLeafOrEmpty = eventSnap.getNode().isLeafNode() || eventSnap.getNode().isEmpty();
9166 var oldCompleteSnap = viewCacheGetCompleteEventSnap(oldViewCache);
9167 if (accumulator.length > 0 ||
9168 !oldViewCache.eventCache.isFullyInitialized() ||
9169 (isLeafOrEmpty && !eventSnap.getNode().equals(oldCompleteSnap)) ||
9170 !eventSnap.getNode().getPriority().equals(oldCompleteSnap.getPriority())) {
9171 accumulator.push(changeValue(viewCacheGetCompleteEventSnap(newViewCache)));
9172 }
9173 }
9174}
9175function viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, viewCache, changePath, writesCache, source, accumulator) {
9176 var oldEventSnap = viewCache.eventCache;
9177 if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
9178 // we have a shadowing write, ignore changes
9179 return viewCache;
9180 }
9181 else {
9182 var newEventCache = void 0, serverNode = void 0;
9183 if (pathIsEmpty(changePath)) {
9184 // TODO: figure out how this plays with "sliding ack windows"
9185 assert(viewCache.serverCache.isFullyInitialized(), 'If change path is empty, we must have complete server data');
9186 if (viewCache.serverCache.isFiltered()) {
9187 // We need to special case this, because we need to only apply writes to complete children, or
9188 // we might end up raising events for incomplete children. If the server data is filtered deep
9189 // writes cannot be guaranteed to be complete
9190 var serverCache = viewCacheGetCompleteServerSnap(viewCache);
9191 var completeChildren = serverCache instanceof ChildrenNode
9192 ? serverCache
9193 : ChildrenNode.EMPTY_NODE;
9194 var completeEventChildren = writeTreeRefCalcCompleteEventChildren(writesCache, completeChildren);
9195 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeEventChildren, accumulator);
9196 }
9197 else {
9198 var completeNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9199 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeNode, accumulator);
9200 }
9201 }
9202 else {
9203 var childKey = pathGetFront(changePath);
9204 if (childKey === '.priority') {
9205 assert(pathGetLength(changePath) === 1, "Can't have a priority with additional path components");
9206 var oldEventNode = oldEventSnap.getNode();
9207 serverNode = viewCache.serverCache.getNode();
9208 // we might have overwrites for this priority
9209 var updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventNode, serverNode);
9210 if (updatedPriority != null) {
9211 newEventCache = viewProcessor.filter.updatePriority(oldEventNode, updatedPriority);
9212 }
9213 else {
9214 // priority didn't change, keep old node
9215 newEventCache = oldEventSnap.getNode();
9216 }
9217 }
9218 else {
9219 var childChangePath = pathPopFront(changePath);
9220 // update child
9221 var newEventChild = void 0;
9222 if (oldEventSnap.isCompleteForChild(childKey)) {
9223 serverNode = viewCache.serverCache.getNode();
9224 var eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventSnap.getNode(), serverNode);
9225 if (eventChildUpdate != null) {
9226 newEventChild = oldEventSnap
9227 .getNode()
9228 .getImmediateChild(childKey)
9229 .updateChild(childChangePath, eventChildUpdate);
9230 }
9231 else {
9232 // Nothing changed, just keep the old child
9233 newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
9234 }
9235 }
9236 else {
9237 newEventChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9238 }
9239 if (newEventChild != null) {
9240 newEventCache = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newEventChild, childChangePath, source, accumulator);
9241 }
9242 else {
9243 // no complete child available or no change
9244 newEventCache = oldEventSnap.getNode();
9245 }
9246 }
9247 }
9248 return viewCacheUpdateEventSnap(viewCache, newEventCache, oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath), viewProcessor.filter.filtersNodes());
9249 }
9250}
9251function viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, filterServerNode, accumulator) {
9252 var oldServerSnap = oldViewCache.serverCache;
9253 var newServerCache;
9254 var serverFilter = filterServerNode
9255 ? viewProcessor.filter
9256 : viewProcessor.filter.getIndexedFilter();
9257 if (pathIsEmpty(changePath)) {
9258 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), changedSnap, null);
9259 }
9260 else if (serverFilter.filtersNodes() && !oldServerSnap.isFiltered()) {
9261 // we want to filter the server node, but we didn't filter the server node yet, so simulate a full update
9262 var newServerNode = oldServerSnap
9263 .getNode()
9264 .updateChild(changePath, changedSnap);
9265 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), newServerNode, null);
9266 }
9267 else {
9268 var childKey = pathGetFront(changePath);
9269 if (!oldServerSnap.isCompleteForPath(changePath) &&
9270 pathGetLength(changePath) > 1) {
9271 // We don't update incomplete nodes with updates intended for other listeners
9272 return oldViewCache;
9273 }
9274 var childChangePath = pathPopFront(changePath);
9275 var childNode = oldServerSnap.getNode().getImmediateChild(childKey);
9276 var newChildNode = childNode.updateChild(childChangePath, changedSnap);
9277 if (childKey === '.priority') {
9278 newServerCache = serverFilter.updatePriority(oldServerSnap.getNode(), newChildNode);
9279 }
9280 else {
9281 newServerCache = serverFilter.updateChild(oldServerSnap.getNode(), childKey, newChildNode, childChangePath, NO_COMPLETE_CHILD_SOURCE, null);
9282 }
9283 }
9284 var newViewCache = viewCacheUpdateServerSnap(oldViewCache, newServerCache, oldServerSnap.isFullyInitialized() || pathIsEmpty(changePath), serverFilter.filtersNodes());
9285 var source = new WriteTreeCompleteChildSource(writesCache, newViewCache, completeCache);
9286 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, changePath, writesCache, source, accumulator);
9287}
9288function viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, accumulator) {
9289 var oldEventSnap = oldViewCache.eventCache;
9290 var newViewCache, newEventCache;
9291 var source = new WriteTreeCompleteChildSource(writesCache, oldViewCache, completeCache);
9292 if (pathIsEmpty(changePath)) {
9293 newEventCache = viewProcessor.filter.updateFullNode(oldViewCache.eventCache.getNode(), changedSnap, accumulator);
9294 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, true, viewProcessor.filter.filtersNodes());
9295 }
9296 else {
9297 var childKey = pathGetFront(changePath);
9298 if (childKey === '.priority') {
9299 newEventCache = viewProcessor.filter.updatePriority(oldViewCache.eventCache.getNode(), changedSnap);
9300 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, oldEventSnap.isFullyInitialized(), oldEventSnap.isFiltered());
9301 }
9302 else {
9303 var childChangePath = pathPopFront(changePath);
9304 var oldChild = oldEventSnap.getNode().getImmediateChild(childKey);
9305 var newChild = void 0;
9306 if (pathIsEmpty(childChangePath)) {
9307 // Child overwrite, we can replace the child
9308 newChild = changedSnap;
9309 }
9310 else {
9311 var childNode = source.getCompleteChild(childKey);
9312 if (childNode != null) {
9313 if (pathGetBack(childChangePath) === '.priority' &&
9314 childNode.getChild(pathParent(childChangePath)).isEmpty()) {
9315 // This is a priority update on an empty node. If this node exists on the server, the
9316 // server will send down the priority in the update, so ignore for now
9317 newChild = childNode;
9318 }
9319 else {
9320 newChild = childNode.updateChild(childChangePath, changedSnap);
9321 }
9322 }
9323 else {
9324 // There is no complete child node available
9325 newChild = ChildrenNode.EMPTY_NODE;
9326 }
9327 }
9328 if (!oldChild.equals(newChild)) {
9329 var newEventSnap = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newChild, childChangePath, source, accumulator);
9330 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventSnap, oldEventSnap.isFullyInitialized(), viewProcessor.filter.filtersNodes());
9331 }
9332 else {
9333 newViewCache = oldViewCache;
9334 }
9335 }
9336 }
9337 return newViewCache;
9338}
9339function viewProcessorCacheHasChild(viewCache, childKey) {
9340 return viewCache.eventCache.isCompleteForChild(childKey);
9341}
9342function viewProcessorApplyUserMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, accumulator) {
9343 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9344 // window leaving room for new items. It's important we process these changes first, so we
9345 // iterate the changes twice, first processing any that affect items currently in view.
9346 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9347 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9348 // not the other.
9349 var curViewCache = viewCache;
9350 changedChildren.foreach(function (relativePath, childNode) {
9351 var writePath = pathChild(path, relativePath);
9352 if (viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9353 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9354 }
9355 });
9356 changedChildren.foreach(function (relativePath, childNode) {
9357 var writePath = pathChild(path, relativePath);
9358 if (!viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9359 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9360 }
9361 });
9362 return curViewCache;
9363}
9364function viewProcessorApplyMerge(viewProcessor, node, merge) {
9365 merge.foreach(function (relativePath, childNode) {
9366 node = node.updateChild(relativePath, childNode);
9367 });
9368 return node;
9369}
9370function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, filterServerNode, accumulator) {
9371 // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
9372 // wait for the complete data update coming soon.
9373 if (viewCache.serverCache.getNode().isEmpty() &&
9374 !viewCache.serverCache.isFullyInitialized()) {
9375 return viewCache;
9376 }
9377 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9378 // window leaving room for new items. It's important we process these changes first, so we
9379 // iterate the changes twice, first processing any that affect items currently in view.
9380 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9381 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9382 // not the other.
9383 var curViewCache = viewCache;
9384 var viewMergeTree;
9385 if (pathIsEmpty(path)) {
9386 viewMergeTree = changedChildren;
9387 }
9388 else {
9389 viewMergeTree = new ImmutableTree(null).setTree(path, changedChildren);
9390 }
9391 var serverNode = viewCache.serverCache.getNode();
9392 viewMergeTree.children.inorderTraversal(function (childKey, childTree) {
9393 if (serverNode.hasChild(childKey)) {
9394 var serverChild = viewCache.serverCache
9395 .getNode()
9396 .getImmediateChild(childKey);
9397 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childTree);
9398 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9399 }
9400 });
9401 viewMergeTree.children.inorderTraversal(function (childKey, childMergeTree) {
9402 var isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9403 childMergeTree.value === undefined;
9404 if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9405 var serverChild = viewCache.serverCache
9406 .getNode()
9407 .getImmediateChild(childKey);
9408 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childMergeTree);
9409 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9410 }
9411 });
9412 return curViewCache;
9413}
9414function viewProcessorAckUserWrite(viewProcessor, viewCache, ackPath, affectedTree, writesCache, completeCache, accumulator) {
9415 if (writeTreeRefShadowingWrite(writesCache, ackPath) != null) {
9416 return viewCache;
9417 }
9418 // Only filter server node if it is currently filtered
9419 var filterServerNode = viewCache.serverCache.isFiltered();
9420 // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
9421 // now that it won't be shadowed.
9422 var serverCache = viewCache.serverCache;
9423 if (affectedTree.value != null) {
9424 // This is an overwrite.
9425 if ((pathIsEmpty(ackPath) && serverCache.isFullyInitialized()) ||
9426 serverCache.isCompleteForPath(ackPath)) {
9427 return viewProcessorApplyServerOverwrite(viewProcessor, viewCache, ackPath, serverCache.getNode().getChild(ackPath), writesCache, completeCache, filterServerNode, accumulator);
9428 }
9429 else if (pathIsEmpty(ackPath)) {
9430 // This is a goofy edge case where we are acking data at this location but don't have full data. We
9431 // should just re-apply whatever we have in our cache as a merge.
9432 var changedChildren_1 = new ImmutableTree(null);
9433 serverCache.getNode().forEachChild(KEY_INDEX, function (name, node) {
9434 changedChildren_1 = changedChildren_1.set(new Path(name), node);
9435 });
9436 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_1, writesCache, completeCache, filterServerNode, accumulator);
9437 }
9438 else {
9439 return viewCache;
9440 }
9441 }
9442 else {
9443 // This is a merge.
9444 var changedChildren_2 = new ImmutableTree(null);
9445 affectedTree.foreach(function (mergePath, value) {
9446 var serverCachePath = pathChild(ackPath, mergePath);
9447 if (serverCache.isCompleteForPath(serverCachePath)) {
9448 changedChildren_2 = changedChildren_2.set(mergePath, serverCache.getNode().getChild(serverCachePath));
9449 }
9450 });
9451 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_2, writesCache, completeCache, filterServerNode, accumulator);
9452 }
9453}
9454function viewProcessorListenComplete(viewProcessor, viewCache, path, writesCache, accumulator) {
9455 var oldServerNode = viewCache.serverCache;
9456 var newViewCache = viewCacheUpdateServerSnap(viewCache, oldServerNode.getNode(), oldServerNode.isFullyInitialized() || pathIsEmpty(path), oldServerNode.isFiltered());
9457 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, path, writesCache, NO_COMPLETE_CHILD_SOURCE, accumulator);
9458}
9459function viewProcessorRevertUserWrite(viewProcessor, viewCache, path, writesCache, completeServerCache, accumulator) {
9460 var complete;
9461 if (writeTreeRefShadowingWrite(writesCache, path) != null) {
9462 return viewCache;
9463 }
9464 else {
9465 var source = new WriteTreeCompleteChildSource(writesCache, viewCache, completeServerCache);
9466 var oldEventCache = viewCache.eventCache.getNode();
9467 var newEventCache = void 0;
9468 if (pathIsEmpty(path) || pathGetFront(path) === '.priority') {
9469 var newNode = void 0;
9470 if (viewCache.serverCache.isFullyInitialized()) {
9471 newNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9472 }
9473 else {
9474 var serverChildren = viewCache.serverCache.getNode();
9475 assert(serverChildren instanceof ChildrenNode, 'serverChildren would be complete if leaf node');
9476 newNode = writeTreeRefCalcCompleteEventChildren(writesCache, serverChildren);
9477 }
9478 newNode = newNode;
9479 newEventCache = viewProcessor.filter.updateFullNode(oldEventCache, newNode, accumulator);
9480 }
9481 else {
9482 var childKey = pathGetFront(path);
9483 var newChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9484 if (newChild == null &&
9485 viewCache.serverCache.isCompleteForChild(childKey)) {
9486 newChild = oldEventCache.getImmediateChild(childKey);
9487 }
9488 if (newChild != null) {
9489 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, newChild, pathPopFront(path), source, accumulator);
9490 }
9491 else if (viewCache.eventCache.getNode().hasChild(childKey)) {
9492 // No complete child available, delete the existing one, if any
9493 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, ChildrenNode.EMPTY_NODE, pathPopFront(path), source, accumulator);
9494 }
9495 else {
9496 newEventCache = oldEventCache;
9497 }
9498 if (newEventCache.isEmpty() &&
9499 viewCache.serverCache.isFullyInitialized()) {
9500 // We might have reverted all child writes. Maybe the old event was a leaf node
9501 complete = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9502 if (complete.isLeafNode()) {
9503 newEventCache = viewProcessor.filter.updateFullNode(newEventCache, complete, accumulator);
9504 }
9505 }
9506 }
9507 complete =
9508 viewCache.serverCache.isFullyInitialized() ||
9509 writeTreeRefShadowingWrite(writesCache, newEmptyPath()) != null;
9510 return viewCacheUpdateEventSnap(viewCache, newEventCache, complete, viewProcessor.filter.filtersNodes());
9511 }
9512}
9513
9514/**
9515 * @license
9516 * Copyright 2017 Google LLC
9517 *
9518 * Licensed under the Apache License, Version 2.0 (the "License");
9519 * you may not use this file except in compliance with the License.
9520 * You may obtain a copy of the License at
9521 *
9522 * http://www.apache.org/licenses/LICENSE-2.0
9523 *
9524 * Unless required by applicable law or agreed to in writing, software
9525 * distributed under the License is distributed on an "AS IS" BASIS,
9526 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9527 * See the License for the specific language governing permissions and
9528 * limitations under the License.
9529 */
9530/**
9531 * A view represents a specific location and query that has 1 or more event registrations.
9532 *
9533 * It does several things:
9534 * - Maintains the list of event registrations for this location/query.
9535 * - Maintains a cache of the data visible for this location/query.
9536 * - Applies new operations (via applyOperation), updates the cache, and based on the event
9537 * registrations returns the set of events to be raised.
9538 */
9539var View = /** @class */ (function () {
9540 function View(query_, initialViewCache) {
9541 this.query_ = query_;
9542 this.eventRegistrations_ = [];
9543 var params = this.query_._queryParams;
9544 var indexFilter = new IndexedFilter(params.getIndex());
9545 var filter = queryParamsGetNodeFilter(params);
9546 this.processor_ = newViewProcessor(filter);
9547 var initialServerCache = initialViewCache.serverCache;
9548 var initialEventCache = initialViewCache.eventCache;
9549 // Don't filter server node with other filter than index, wait for tagged listen
9550 var serverSnap = indexFilter.updateFullNode(ChildrenNode.EMPTY_NODE, initialServerCache.getNode(), null);
9551 var eventSnap = filter.updateFullNode(ChildrenNode.EMPTY_NODE, initialEventCache.getNode(), null);
9552 var newServerCache = new CacheNode(serverSnap, initialServerCache.isFullyInitialized(), indexFilter.filtersNodes());
9553 var newEventCache = new CacheNode(eventSnap, initialEventCache.isFullyInitialized(), filter.filtersNodes());
9554 this.viewCache_ = newViewCache(newEventCache, newServerCache);
9555 this.eventGenerator_ = new EventGenerator(this.query_);
9556 }
9557 Object.defineProperty(View.prototype, "query", {
9558 get: function () {
9559 return this.query_;
9560 },
9561 enumerable: false,
9562 configurable: true
9563 });
9564 return View;
9565}());
9566function viewGetServerCache(view) {
9567 return view.viewCache_.serverCache.getNode();
9568}
9569function viewGetCompleteNode(view) {
9570 return viewCacheGetCompleteEventSnap(view.viewCache_);
9571}
9572function viewGetCompleteServerCache(view, path) {
9573 var cache = viewCacheGetCompleteServerSnap(view.viewCache_);
9574 if (cache) {
9575 // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
9576 // we need to see if it contains the child we're interested in.
9577 if (view.query._queryParams.loadsAllData() ||
9578 (!pathIsEmpty(path) &&
9579 !cache.getImmediateChild(pathGetFront(path)).isEmpty())) {
9580 return cache.getChild(path);
9581 }
9582 }
9583 return null;
9584}
9585function viewIsEmpty(view) {
9586 return view.eventRegistrations_.length === 0;
9587}
9588function viewAddEventRegistration(view, eventRegistration) {
9589 view.eventRegistrations_.push(eventRegistration);
9590}
9591/**
9592 * @param eventRegistration - If null, remove all callbacks.
9593 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9594 * @returns Cancel events, if cancelError was provided.
9595 */
9596function viewRemoveEventRegistration(view, eventRegistration, cancelError) {
9597 var cancelEvents = [];
9598 if (cancelError) {
9599 assert(eventRegistration == null, 'A cancel should cancel all event registrations.');
9600 var path_1 = view.query._path;
9601 view.eventRegistrations_.forEach(function (registration) {
9602 var maybeEvent = registration.createCancelEvent(cancelError, path_1);
9603 if (maybeEvent) {
9604 cancelEvents.push(maybeEvent);
9605 }
9606 });
9607 }
9608 if (eventRegistration) {
9609 var remaining = [];
9610 for (var i = 0; i < view.eventRegistrations_.length; ++i) {
9611 var existing = view.eventRegistrations_[i];
9612 if (!existing.matches(eventRegistration)) {
9613 remaining.push(existing);
9614 }
9615 else if (eventRegistration.hasAnyCallback()) {
9616 // We're removing just this one
9617 remaining = remaining.concat(view.eventRegistrations_.slice(i + 1));
9618 break;
9619 }
9620 }
9621 view.eventRegistrations_ = remaining;
9622 }
9623 else {
9624 view.eventRegistrations_ = [];
9625 }
9626 return cancelEvents;
9627}
9628/**
9629 * Applies the given Operation, updates our cache, and returns the appropriate events.
9630 */
9631function viewApplyOperation(view, operation, writesCache, completeServerCache) {
9632 if (operation.type === OperationType.MERGE &&
9633 operation.source.queryId !== null) {
9634 assert(viewCacheGetCompleteServerSnap(view.viewCache_), 'We should always have a full cache before handling merges');
9635 assert(viewCacheGetCompleteEventSnap(view.viewCache_), 'Missing event cache, even though we have a server cache');
9636 }
9637 var oldViewCache = view.viewCache_;
9638 var result = viewProcessorApplyOperation(view.processor_, oldViewCache, operation, writesCache, completeServerCache);
9639 viewProcessorAssertIndexed(view.processor_, result.viewCache);
9640 assert(result.viewCache.serverCache.isFullyInitialized() ||
9641 !oldViewCache.serverCache.isFullyInitialized(), 'Once a server snap is complete, it should never go back');
9642 view.viewCache_ = result.viewCache;
9643 return viewGenerateEventsForChanges_(view, result.changes, result.viewCache.eventCache.getNode(), null);
9644}
9645function viewGetInitialEvents(view, registration) {
9646 var eventSnap = view.viewCache_.eventCache;
9647 var initialChanges = [];
9648 if (!eventSnap.getNode().isLeafNode()) {
9649 var eventNode = eventSnap.getNode();
9650 eventNode.forEachChild(PRIORITY_INDEX, function (key, childNode) {
9651 initialChanges.push(changeChildAdded(key, childNode));
9652 });
9653 }
9654 if (eventSnap.isFullyInitialized()) {
9655 initialChanges.push(changeValue(eventSnap.getNode()));
9656 }
9657 return viewGenerateEventsForChanges_(view, initialChanges, eventSnap.getNode(), registration);
9658}
9659function viewGenerateEventsForChanges_(view, changes, eventCache, eventRegistration) {
9660 var registrations = eventRegistration
9661 ? [eventRegistration]
9662 : view.eventRegistrations_;
9663 return eventGeneratorGenerateEventsForChanges(view.eventGenerator_, changes, eventCache, registrations);
9664}
9665
9666/**
9667 * @license
9668 * Copyright 2017 Google LLC
9669 *
9670 * Licensed under the Apache License, Version 2.0 (the "License");
9671 * you may not use this file except in compliance with the License.
9672 * You may obtain a copy of the License at
9673 *
9674 * http://www.apache.org/licenses/LICENSE-2.0
9675 *
9676 * Unless required by applicable law or agreed to in writing, software
9677 * distributed under the License is distributed on an "AS IS" BASIS,
9678 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9679 * See the License for the specific language governing permissions and
9680 * limitations under the License.
9681 */
9682var referenceConstructor$1;
9683/**
9684 * SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
9685 * maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
9686 * and user writes (set, transaction, update).
9687 *
9688 * It's responsible for:
9689 * - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
9690 * - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
9691 * applyUserOverwrite, etc.)
9692 */
9693var SyncPoint = /** @class */ (function () {
9694 function SyncPoint() {
9695 /**
9696 * The Views being tracked at this location in the tree, stored as a map where the key is a
9697 * queryId and the value is the View for that query.
9698 *
9699 * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
9700 */
9701 this.views = new Map();
9702 }
9703 return SyncPoint;
9704}());
9705function syncPointSetReferenceConstructor(val) {
9706 assert(!referenceConstructor$1, '__referenceConstructor has already been defined');
9707 referenceConstructor$1 = val;
9708}
9709function syncPointGetReferenceConstructor() {
9710 assert(referenceConstructor$1, 'Reference.ts has not been loaded');
9711 return referenceConstructor$1;
9712}
9713function syncPointIsEmpty(syncPoint) {
9714 return syncPoint.views.size === 0;
9715}
9716function syncPointApplyOperation(syncPoint, operation, writesCache, optCompleteServerCache) {
9717 var e_1, _a;
9718 var queryId = operation.source.queryId;
9719 if (queryId !== null) {
9720 var view = syncPoint.views.get(queryId);
9721 assert(view != null, 'SyncTree gave us an op for an invalid query.');
9722 return viewApplyOperation(view, operation, writesCache, optCompleteServerCache);
9723 }
9724 else {
9725 var events = [];
9726 try {
9727 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9728 var view = _c.value;
9729 events = events.concat(viewApplyOperation(view, operation, writesCache, optCompleteServerCache));
9730 }
9731 }
9732 catch (e_1_1) { e_1 = { error: e_1_1 }; }
9733 finally {
9734 try {
9735 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9736 }
9737 finally { if (e_1) throw e_1.error; }
9738 }
9739 return events;
9740 }
9741}
9742/**
9743 * Get a view for the specified query.
9744 *
9745 * @param query - The query to return a view for
9746 * @param writesCache
9747 * @param serverCache
9748 * @param serverCacheComplete
9749 * @returns Events to raise.
9750 */
9751function syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete) {
9752 var queryId = query._queryIdentifier;
9753 var view = syncPoint.views.get(queryId);
9754 if (!view) {
9755 // TODO: make writesCache take flag for complete server node
9756 var eventCache = writeTreeRefCalcCompleteEventCache(writesCache, serverCacheComplete ? serverCache : null);
9757 var eventCacheComplete = false;
9758 if (eventCache) {
9759 eventCacheComplete = true;
9760 }
9761 else if (serverCache instanceof ChildrenNode) {
9762 eventCache = writeTreeRefCalcCompleteEventChildren(writesCache, serverCache);
9763 eventCacheComplete = false;
9764 }
9765 else {
9766 eventCache = ChildrenNode.EMPTY_NODE;
9767 eventCacheComplete = false;
9768 }
9769 var viewCache = newViewCache(new CacheNode(eventCache, eventCacheComplete, false), new CacheNode(serverCache, serverCacheComplete, false));
9770 return new View(query, viewCache);
9771 }
9772 return view;
9773}
9774/**
9775 * Add an event callback for the specified query.
9776 *
9777 * @param query
9778 * @param eventRegistration
9779 * @param writesCache
9780 * @param serverCache - Complete server cache, if we have it.
9781 * @param serverCacheComplete
9782 * @returns Events to raise.
9783 */
9784function syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete) {
9785 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9786 if (!syncPoint.views.has(query._queryIdentifier)) {
9787 syncPoint.views.set(query._queryIdentifier, view);
9788 }
9789 // This is guaranteed to exist now, we just created anything that was missing
9790 viewAddEventRegistration(view, eventRegistration);
9791 return viewGetInitialEvents(view, eventRegistration);
9792}
9793/**
9794 * Remove event callback(s). Return cancelEvents if a cancelError is specified.
9795 *
9796 * If query is the default query, we'll check all views for the specified eventRegistration.
9797 * If eventRegistration is null, we'll remove all callbacks for the specified view(s).
9798 *
9799 * @param eventRegistration - If null, remove all callbacks.
9800 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9801 * @returns removed queries and any cancel events
9802 */
9803function syncPointRemoveEventRegistration(syncPoint, query, eventRegistration, cancelError) {
9804 var e_2, _a;
9805 var queryId = query._queryIdentifier;
9806 var removed = [];
9807 var cancelEvents = [];
9808 var hadCompleteView = syncPointHasCompleteView(syncPoint);
9809 if (queryId === 'default') {
9810 try {
9811 // When you do ref.off(...), we search all views for the registration to remove.
9812 for (var _b = __values(syncPoint.views.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
9813 var _d = __read(_c.value, 2), viewQueryId = _d[0], view = _d[1];
9814 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9815 if (viewIsEmpty(view)) {
9816 syncPoint.views.delete(viewQueryId);
9817 // We'll deal with complete views later.
9818 if (!view.query._queryParams.loadsAllData()) {
9819 removed.push(view.query);
9820 }
9821 }
9822 }
9823 }
9824 catch (e_2_1) { e_2 = { error: e_2_1 }; }
9825 finally {
9826 try {
9827 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9828 }
9829 finally { if (e_2) throw e_2.error; }
9830 }
9831 }
9832 else {
9833 // remove the callback from the specific view.
9834 var view = syncPoint.views.get(queryId);
9835 if (view) {
9836 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9837 if (viewIsEmpty(view)) {
9838 syncPoint.views.delete(queryId);
9839 // We'll deal with complete views later.
9840 if (!view.query._queryParams.loadsAllData()) {
9841 removed.push(view.query);
9842 }
9843 }
9844 }
9845 }
9846 if (hadCompleteView && !syncPointHasCompleteView(syncPoint)) {
9847 // We removed our last complete view.
9848 removed.push(new (syncPointGetReferenceConstructor())(query._repo, query._path));
9849 }
9850 return { removed: removed, events: cancelEvents };
9851}
9852function syncPointGetQueryViews(syncPoint) {
9853 var e_3, _a;
9854 var result = [];
9855 try {
9856 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9857 var view = _c.value;
9858 if (!view.query._queryParams.loadsAllData()) {
9859 result.push(view);
9860 }
9861 }
9862 }
9863 catch (e_3_1) { e_3 = { error: e_3_1 }; }
9864 finally {
9865 try {
9866 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9867 }
9868 finally { if (e_3) throw e_3.error; }
9869 }
9870 return result;
9871}
9872/**
9873 * @param path - The path to the desired complete snapshot
9874 * @returns A complete cache, if it exists
9875 */
9876function syncPointGetCompleteServerCache(syncPoint, path) {
9877 var e_4, _a;
9878 var serverCache = null;
9879 try {
9880 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9881 var view = _c.value;
9882 serverCache = serverCache || viewGetCompleteServerCache(view, path);
9883 }
9884 }
9885 catch (e_4_1) { e_4 = { error: e_4_1 }; }
9886 finally {
9887 try {
9888 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9889 }
9890 finally { if (e_4) throw e_4.error; }
9891 }
9892 return serverCache;
9893}
9894function syncPointViewForQuery(syncPoint, query) {
9895 var params = query._queryParams;
9896 if (params.loadsAllData()) {
9897 return syncPointGetCompleteView(syncPoint);
9898 }
9899 else {
9900 var queryId = query._queryIdentifier;
9901 return syncPoint.views.get(queryId);
9902 }
9903}
9904function syncPointViewExistsForQuery(syncPoint, query) {
9905 return syncPointViewForQuery(syncPoint, query) != null;
9906}
9907function syncPointHasCompleteView(syncPoint) {
9908 return syncPointGetCompleteView(syncPoint) != null;
9909}
9910function syncPointGetCompleteView(syncPoint) {
9911 var e_5, _a;
9912 try {
9913 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9914 var view = _c.value;
9915 if (view.query._queryParams.loadsAllData()) {
9916 return view;
9917 }
9918 }
9919 }
9920 catch (e_5_1) { e_5 = { error: e_5_1 }; }
9921 finally {
9922 try {
9923 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9924 }
9925 finally { if (e_5) throw e_5.error; }
9926 }
9927 return null;
9928}
9929
9930/**
9931 * @license
9932 * Copyright 2017 Google LLC
9933 *
9934 * Licensed under the Apache License, Version 2.0 (the "License");
9935 * you may not use this file except in compliance with the License.
9936 * You may obtain a copy of the License at
9937 *
9938 * http://www.apache.org/licenses/LICENSE-2.0
9939 *
9940 * Unless required by applicable law or agreed to in writing, software
9941 * distributed under the License is distributed on an "AS IS" BASIS,
9942 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9943 * See the License for the specific language governing permissions and
9944 * limitations under the License.
9945 */
9946var referenceConstructor;
9947function syncTreeSetReferenceConstructor(val) {
9948 assert(!referenceConstructor, '__referenceConstructor has already been defined');
9949 referenceConstructor = val;
9950}
9951function syncTreeGetReferenceConstructor() {
9952 assert(referenceConstructor, 'Reference.ts has not been loaded');
9953 return referenceConstructor;
9954}
9955/**
9956 * Static tracker for next query tag.
9957 */
9958var syncTreeNextQueryTag_ = 1;
9959/**
9960 * SyncTree is the central class for managing event callback registration, data caching, views
9961 * (query processing), and event generation. There are typically two SyncTree instances for
9962 * each Repo, one for the normal Firebase data, and one for the .info data.
9963 *
9964 * It has a number of responsibilities, including:
9965 * - Tracking all user event callbacks (registered via addEventRegistration() and removeEventRegistration()).
9966 * - Applying and caching data changes for user set(), transaction(), and update() calls
9967 * (applyUserOverwrite(), applyUserMerge()).
9968 * - Applying and caching data changes for server data changes (applyServerOverwrite(),
9969 * applyServerMerge()).
9970 * - Generating user-facing events for server and user changes (all of the apply* methods
9971 * return the set of events that need to be raised as a result).
9972 * - Maintaining the appropriate set of server listens to ensure we are always subscribed
9973 * to the correct set of paths and queries to satisfy the current set of user event
9974 * callbacks (listens are started/stopped using the provided listenProvider).
9975 *
9976 * NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
9977 * events are returned to the caller rather than raised synchronously.
9978 *
9979 */
9980var SyncTree = /** @class */ (function () {
9981 /**
9982 * @param listenProvider_ - Used by SyncTree to start / stop listening
9983 * to server data.
9984 */
9985 function SyncTree(listenProvider_) {
9986 this.listenProvider_ = listenProvider_;
9987 /**
9988 * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
9989 */
9990 this.syncPointTree_ = new ImmutableTree(null);
9991 /**
9992 * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
9993 */
9994 this.pendingWriteTree_ = newWriteTree();
9995 this.tagToQueryMap = new Map();
9996 this.queryToTagMap = new Map();
9997 }
9998 return SyncTree;
9999}());
10000/**
10001 * Apply the data changes for a user-generated set() or transaction() call.
10002 *
10003 * @returns Events to raise.
10004 */
10005function syncTreeApplyUserOverwrite(syncTree, path, newData, writeId, visible) {
10006 // Record pending write.
10007 writeTreeAddOverwrite(syncTree.pendingWriteTree_, path, newData, writeId, visible);
10008 if (!visible) {
10009 return [];
10010 }
10011 else {
10012 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceUser(), path, newData));
10013 }
10014}
10015/**
10016 * Apply the data from a user-generated update() call
10017 *
10018 * @returns Events to raise.
10019 */
10020function syncTreeApplyUserMerge(syncTree, path, changedChildren, writeId) {
10021 // Record pending merge.
10022 writeTreeAddMerge(syncTree.pendingWriteTree_, path, changedChildren, writeId);
10023 var changeTree = ImmutableTree.fromObject(changedChildren);
10024 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceUser(), path, changeTree));
10025}
10026/**
10027 * Acknowledge a pending user write that was previously registered with applyUserOverwrite() or applyUserMerge().
10028 *
10029 * @param revert - True if the given write failed and needs to be reverted
10030 * @returns Events to raise.
10031 */
10032function syncTreeAckUserWrite(syncTree, writeId, revert) {
10033 if (revert === void 0) { revert = false; }
10034 var write = writeTreeGetWrite(syncTree.pendingWriteTree_, writeId);
10035 var needToReevaluate = writeTreeRemoveWrite(syncTree.pendingWriteTree_, writeId);
10036 if (!needToReevaluate) {
10037 return [];
10038 }
10039 else {
10040 var affectedTree_1 = new ImmutableTree(null);
10041 if (write.snap != null) {
10042 // overwrite
10043 affectedTree_1 = affectedTree_1.set(newEmptyPath(), true);
10044 }
10045 else {
10046 each(write.children, function (pathString) {
10047 affectedTree_1 = affectedTree_1.set(new Path(pathString), true);
10048 });
10049 }
10050 return syncTreeApplyOperationToSyncPoints_(syncTree, new AckUserWrite(write.path, affectedTree_1, revert));
10051 }
10052}
10053/**
10054 * Apply new server data for the specified path..
10055 *
10056 * @returns Events to raise.
10057 */
10058function syncTreeApplyServerOverwrite(syncTree, path, newData) {
10059 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceServer(), path, newData));
10060}
10061/**
10062 * Apply new server data to be merged in at the specified path.
10063 *
10064 * @returns Events to raise.
10065 */
10066function syncTreeApplyServerMerge(syncTree, path, changedChildren) {
10067 var changeTree = ImmutableTree.fromObject(changedChildren);
10068 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceServer(), path, changeTree));
10069}
10070/**
10071 * Apply a listen complete for a query
10072 *
10073 * @returns Events to raise.
10074 */
10075function syncTreeApplyListenComplete(syncTree, path) {
10076 return syncTreeApplyOperationToSyncPoints_(syncTree, new ListenComplete(newOperationSourceServer(), path));
10077}
10078/**
10079 * Apply a listen complete for a tagged query
10080 *
10081 * @returns Events to raise.
10082 */
10083function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
10084 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10085 if (queryKey) {
10086 var r = syncTreeParseQueryKey_(queryKey);
10087 var queryPath = r.path, queryId = r.queryId;
10088 var relativePath = newRelativePath(queryPath, path);
10089 var op = new ListenComplete(newOperationSourceServerTaggedQuery(queryId), relativePath);
10090 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10091 }
10092 else {
10093 // We've already removed the query. No big deal, ignore the update
10094 return [];
10095 }
10096}
10097/**
10098 * Remove event callback(s).
10099 *
10100 * If query is the default query, we'll check all queries for the specified eventRegistration.
10101 * If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
10102 *
10103 * @param eventRegistration - If null, all callbacks are removed.
10104 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
10105 * @returns Cancel events, if cancelError was provided.
10106 */
10107function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError) {
10108 // Find the syncPoint first. Then deal with whether or not it has matching listeners
10109 var path = query._path;
10110 var maybeSyncPoint = syncTree.syncPointTree_.get(path);
10111 var cancelEvents = [];
10112 // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
10113 // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
10114 // not loadsAllData().
10115 if (maybeSyncPoint &&
10116 (query._queryIdentifier === 'default' ||
10117 syncPointViewExistsForQuery(maybeSyncPoint, query))) {
10118 var removedAndEvents = syncPointRemoveEventRegistration(maybeSyncPoint, query, eventRegistration, cancelError);
10119 if (syncPointIsEmpty(maybeSyncPoint)) {
10120 syncTree.syncPointTree_ = syncTree.syncPointTree_.remove(path);
10121 }
10122 var removed = removedAndEvents.removed;
10123 cancelEvents = removedAndEvents.events;
10124 // We may have just removed one of many listeners and can short-circuit this whole process
10125 // We may also not have removed a default listener, in which case all of the descendant listeners should already be
10126 // properly set up.
10127 //
10128 // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
10129 // queryId === 'default'
10130 var removingDefault = -1 !==
10131 removed.findIndex(function (query) {
10132 return query._queryParams.loadsAllData();
10133 });
10134 var covered = syncTree.syncPointTree_.findOnPath(path, function (relativePath, parentSyncPoint) {
10135 return syncPointHasCompleteView(parentSyncPoint);
10136 });
10137 if (removingDefault && !covered) {
10138 var subtree = syncTree.syncPointTree_.subtree(path);
10139 // There are potentially child listeners. Determine what if any listens we need to send before executing the
10140 // removal
10141 if (!subtree.isEmpty()) {
10142 // We need to fold over our subtree and collect the listeners to send
10143 var newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
10144 // Ok, we've collected all the listens we need. Set them up.
10145 for (var i = 0; i < newViews.length; ++i) {
10146 var view = newViews[i], newQuery = view.query;
10147 var listener = syncTreeCreateListenerForView_(syncTree, view);
10148 syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery_(syncTree, newQuery), listener.hashFn, listener.onComplete);
10149 }
10150 }
10151 }
10152 // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
10153 // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
10154 // Also, note that if we have a cancelError, it's already been removed at the provider level.
10155 if (!covered && removed.length > 0 && !cancelError) {
10156 // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
10157 // default. Otherwise, we need to iterate through and cancel each individual query
10158 if (removingDefault) {
10159 // We don't tag default listeners
10160 var defaultTag = null;
10161 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
10162 }
10163 else {
10164 removed.forEach(function (queryToRemove) {
10165 var tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
10166 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
10167 });
10168 }
10169 }
10170 // Now, clear all of the tags we're tracking for the removed listens
10171 syncTreeRemoveTags_(syncTree, removed);
10172 }
10173 return cancelEvents;
10174}
10175/**
10176 * This function was added to support non-listener queries,
10177 * specifically for use in repoGetValue. It sets up all the same
10178 * local cache data-structures (SyncPoint + View) that are
10179 * needed for listeners without installing an event registration.
10180 * If `query` is not `loadsAllData`, it will also provision a tag for
10181 * the query so that query results can be merged into the sync
10182 * tree using existing logic for tagged listener queries.
10183 *
10184 * @param syncTree - Synctree to add the query to.
10185 * @param query - Query to register
10186 * @returns tag as a string if query is not a default query, null if query is not.
10187 */
10188function syncTreeRegisterQuery(syncTree, query) {
10189 var _a = syncTreeRegisterSyncPoint(query, syncTree), syncPoint = _a.syncPoint, serverCache = _a.serverCache, writesCache = _a.writesCache, serverCacheComplete = _a.serverCacheComplete;
10190 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
10191 if (!syncPoint.views.has(query._queryIdentifier)) {
10192 syncPoint.views.set(query._queryIdentifier, view);
10193 }
10194 if (!query._queryParams.loadsAllData()) {
10195 return syncTreeTagForQuery_(syncTree, query);
10196 }
10197 return null;
10198}
10199/**
10200 * Apply new server data for the specified tagged query.
10201 *
10202 * @returns Events to raise.
10203 */
10204function syncTreeApplyTaggedQueryOverwrite(syncTree, path, snap, tag) {
10205 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10206 if (queryKey != null) {
10207 var r = syncTreeParseQueryKey_(queryKey);
10208 var queryPath = r.path, queryId = r.queryId;
10209 var relativePath = newRelativePath(queryPath, path);
10210 var op = new Overwrite(newOperationSourceServerTaggedQuery(queryId), relativePath, snap);
10211 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10212 }
10213 else {
10214 // Query must have been removed already
10215 return [];
10216 }
10217}
10218/**
10219 * Apply server data to be merged in for the specified tagged query.
10220 *
10221 * @returns Events to raise.
10222 */
10223function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
10224 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10225 if (queryKey) {
10226 var r = syncTreeParseQueryKey_(queryKey);
10227 var queryPath = r.path, queryId = r.queryId;
10228 var relativePath = newRelativePath(queryPath, path);
10229 var changeTree = ImmutableTree.fromObject(changedChildren);
10230 var op = new Merge(newOperationSourceServerTaggedQuery(queryId), relativePath, changeTree);
10231 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10232 }
10233 else {
10234 // We've already removed the query. No big deal, ignore the update
10235 return [];
10236 }
10237}
10238/**
10239 * Creates a new syncpoint for a query and creates a tag if the view doesn't exist.
10240 * Extracted from addEventRegistration to allow `repoGetValue` to properly set up the SyncTree
10241 * without actually listening on a query.
10242 */
10243function syncTreeRegisterSyncPoint(query, syncTree) {
10244 var path = query._path;
10245 var serverCache = null;
10246 var foundAncestorDefaultView = false;
10247 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10248 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10249 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10250 var relativePath = newRelativePath(pathToSyncPoint, path);
10251 serverCache =
10252 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10253 foundAncestorDefaultView =
10254 foundAncestorDefaultView || syncPointHasCompleteView(sp);
10255 });
10256 var syncPoint = syncTree.syncPointTree_.get(path);
10257 if (!syncPoint) {
10258 syncPoint = new SyncPoint();
10259 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10260 }
10261 else {
10262 foundAncestorDefaultView =
10263 foundAncestorDefaultView || syncPointHasCompleteView(syncPoint);
10264 serverCache =
10265 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10266 }
10267 var serverCacheComplete;
10268 if (serverCache != null) {
10269 serverCacheComplete = true;
10270 }
10271 else {
10272 serverCacheComplete = false;
10273 serverCache = ChildrenNode.EMPTY_NODE;
10274 var subtree = syncTree.syncPointTree_.subtree(path);
10275 subtree.foreachChild(function (childName, childSyncPoint) {
10276 var completeCache = syncPointGetCompleteServerCache(childSyncPoint, newEmptyPath());
10277 if (completeCache) {
10278 serverCache = serverCache.updateImmediateChild(childName, completeCache);
10279 }
10280 });
10281 }
10282 var viewAlreadyExists = syncPointViewExistsForQuery(syncPoint, query);
10283 if (!viewAlreadyExists && !query._queryParams.loadsAllData()) {
10284 // We need to track a tag for this query
10285 var queryKey = syncTreeMakeQueryKey_(query);
10286 assert(!syncTree.queryToTagMap.has(queryKey), 'View does not exist, but we have a tag');
10287 var tag = syncTreeGetNextQueryTag_();
10288 syncTree.queryToTagMap.set(queryKey, tag);
10289 syncTree.tagToQueryMap.set(tag, queryKey);
10290 }
10291 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
10292 return {
10293 syncPoint: syncPoint,
10294 writesCache: writesCache,
10295 serverCache: serverCache,
10296 serverCacheComplete: serverCacheComplete,
10297 foundAncestorDefaultView: foundAncestorDefaultView,
10298 viewAlreadyExists: viewAlreadyExists
10299 };
10300}
10301/**
10302 * Add an event callback for the specified query.
10303 *
10304 * @returns Events to raise.
10305 */
10306function syncTreeAddEventRegistration(syncTree, query, eventRegistration) {
10307 var _a = syncTreeRegisterSyncPoint(query, syncTree), syncPoint = _a.syncPoint, serverCache = _a.serverCache, writesCache = _a.writesCache, serverCacheComplete = _a.serverCacheComplete, viewAlreadyExists = _a.viewAlreadyExists, foundAncestorDefaultView = _a.foundAncestorDefaultView;
10308 var events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
10309 if (!viewAlreadyExists && !foundAncestorDefaultView) {
10310 var view = syncPointViewForQuery(syncPoint, query);
10311 events = events.concat(syncTreeSetupListener_(syncTree, query, view));
10312 }
10313 return events;
10314}
10315/**
10316 * Returns a complete cache, if we have one, of the data at a particular path. If the location does not have a
10317 * listener above it, we will get a false "null". This shouldn't be a problem because transactions will always
10318 * have a listener above, and atomic operations would correctly show a jitter of <increment value> ->
10319 * <incremented total> as the write is applied locally and then acknowledged at the server.
10320 *
10321 * Note: this method will *include* hidden writes from transaction with applyLocally set to false.
10322 *
10323 * @param path - The path to the data we want
10324 * @param writeIdsToExclude - A specific set to be excluded
10325 */
10326function syncTreeCalcCompleteEventCache(syncTree, path, writeIdsToExclude) {
10327 var includeHiddenSets = true;
10328 var writeTree = syncTree.pendingWriteTree_;
10329 var serverCache = syncTree.syncPointTree_.findOnPath(path, function (pathSoFar, syncPoint) {
10330 var relativePath = newRelativePath(pathSoFar, path);
10331 var serverCache = syncPointGetCompleteServerCache(syncPoint, relativePath);
10332 if (serverCache) {
10333 return serverCache;
10334 }
10335 });
10336 return writeTreeCalcCompleteEventCache(writeTree, path, serverCache, writeIdsToExclude, includeHiddenSets);
10337}
10338function syncTreeGetServerValue(syncTree, query) {
10339 var path = query._path;
10340 var serverCache = null;
10341 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10342 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10343 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10344 var relativePath = newRelativePath(pathToSyncPoint, path);
10345 serverCache =
10346 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10347 });
10348 var syncPoint = syncTree.syncPointTree_.get(path);
10349 if (!syncPoint) {
10350 syncPoint = new SyncPoint();
10351 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10352 }
10353 else {
10354 serverCache =
10355 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10356 }
10357 var serverCacheComplete = serverCache != null;
10358 var serverCacheNode = serverCacheComplete
10359 ? new CacheNode(serverCache, true, false)
10360 : null;
10361 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, query._path);
10362 var view = syncPointGetView(syncPoint, query, writesCache, serverCacheComplete ? serverCacheNode.getNode() : ChildrenNode.EMPTY_NODE, serverCacheComplete);
10363 return viewGetCompleteNode(view);
10364}
10365/**
10366 * A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
10367 *
10368 * NOTES:
10369 * - Descendant SyncPoints will be visited first (since we raise events depth-first).
10370 *
10371 * - We call applyOperation() on each SyncPoint passing three things:
10372 * 1. A version of the Operation that has been made relative to the SyncPoint location.
10373 * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
10374 * 3. A snapshot Node with cached server data, if we have it.
10375 *
10376 * - We concatenate all of the events returned by each SyncPoint and return the result.
10377 */
10378function syncTreeApplyOperationToSyncPoints_(syncTree, operation) {
10379 return syncTreeApplyOperationHelper_(operation, syncTree.syncPointTree_,
10380 /*serverCache=*/ null, writeTreeChildWrites(syncTree.pendingWriteTree_, newEmptyPath()));
10381}
10382/**
10383 * Recursive helper for applyOperationToSyncPoints_
10384 */
10385function syncTreeApplyOperationHelper_(operation, syncPointTree, serverCache, writesCache) {
10386 if (pathIsEmpty(operation.path)) {
10387 return syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache);
10388 }
10389 else {
10390 var syncPoint = syncPointTree.get(newEmptyPath());
10391 // If we don't have cached server data, see if we can get it from this SyncPoint.
10392 if (serverCache == null && syncPoint != null) {
10393 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10394 }
10395 var events = [];
10396 var childName = pathGetFront(operation.path);
10397 var childOperation = operation.operationForChild(childName);
10398 var childTree = syncPointTree.children.get(childName);
10399 if (childTree && childOperation) {
10400 var childServerCache = serverCache
10401 ? serverCache.getImmediateChild(childName)
10402 : null;
10403 var childWritesCache = writeTreeRefChild(writesCache, childName);
10404 events = events.concat(syncTreeApplyOperationHelper_(childOperation, childTree, childServerCache, childWritesCache));
10405 }
10406 if (syncPoint) {
10407 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10408 }
10409 return events;
10410 }
10411}
10412/**
10413 * Recursive helper for applyOperationToSyncPoints_
10414 */
10415function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache) {
10416 var syncPoint = syncPointTree.get(newEmptyPath());
10417 // If we don't have cached server data, see if we can get it from this SyncPoint.
10418 if (serverCache == null && syncPoint != null) {
10419 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10420 }
10421 var events = [];
10422 syncPointTree.children.inorderTraversal(function (childName, childTree) {
10423 var childServerCache = serverCache
10424 ? serverCache.getImmediateChild(childName)
10425 : null;
10426 var childWritesCache = writeTreeRefChild(writesCache, childName);
10427 var childOperation = operation.operationForChild(childName);
10428 if (childOperation) {
10429 events = events.concat(syncTreeApplyOperationDescendantsHelper_(childOperation, childTree, childServerCache, childWritesCache));
10430 }
10431 });
10432 if (syncPoint) {
10433 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10434 }
10435 return events;
10436}
10437function syncTreeCreateListenerForView_(syncTree, view) {
10438 var query = view.query;
10439 var tag = syncTreeTagForQuery_(syncTree, query);
10440 return {
10441 hashFn: function () {
10442 var cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
10443 return cache.hash();
10444 },
10445 onComplete: function (status) {
10446 if (status === 'ok') {
10447 if (tag) {
10448 return syncTreeApplyTaggedListenComplete(syncTree, query._path, tag);
10449 }
10450 else {
10451 return syncTreeApplyListenComplete(syncTree, query._path);
10452 }
10453 }
10454 else {
10455 // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
10456 // Note that this may need to be scoped to just this listener if we change permissions on filtered children
10457 var error = errorForServerCode(status, query);
10458 return syncTreeRemoveEventRegistration(syncTree, query,
10459 /*eventRegistration*/ null, error);
10460 }
10461 }
10462 };
10463}
10464/**
10465 * Return the tag associated with the given query.
10466 */
10467function syncTreeTagForQuery_(syncTree, query) {
10468 var queryKey = syncTreeMakeQueryKey_(query);
10469 return syncTree.queryToTagMap.get(queryKey);
10470}
10471/**
10472 * Given a query, computes a "queryKey" suitable for use in our queryToTagMap_.
10473 */
10474function syncTreeMakeQueryKey_(query) {
10475 return query._path.toString() + '$' + query._queryIdentifier;
10476}
10477/**
10478 * Return the query associated with the given tag, if we have one
10479 */
10480function syncTreeQueryKeyForTag_(syncTree, tag) {
10481 return syncTree.tagToQueryMap.get(tag);
10482}
10483/**
10484 * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId.
10485 */
10486function syncTreeParseQueryKey_(queryKey) {
10487 var splitIndex = queryKey.indexOf('$');
10488 assert(splitIndex !== -1 && splitIndex < queryKey.length - 1, 'Bad queryKey.');
10489 return {
10490 queryId: queryKey.substr(splitIndex + 1),
10491 path: new Path(queryKey.substr(0, splitIndex))
10492 };
10493}
10494/**
10495 * A helper method to apply tagged operations
10496 */
10497function syncTreeApplyTaggedOperation_(syncTree, queryPath, operation) {
10498 var syncPoint = syncTree.syncPointTree_.get(queryPath);
10499 assert(syncPoint, "Missing sync point for query tag that we're tracking");
10500 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, queryPath);
10501 return syncPointApplyOperation(syncPoint, operation, writesCache, null);
10502}
10503/**
10504 * This collapses multiple unfiltered views into a single view, since we only need a single
10505 * listener for them.
10506 */
10507function syncTreeCollectDistinctViewsForSubTree_(subtree) {
10508 return subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10509 if (maybeChildSyncPoint && syncPointHasCompleteView(maybeChildSyncPoint)) {
10510 var completeView = syncPointGetCompleteView(maybeChildSyncPoint);
10511 return [completeView];
10512 }
10513 else {
10514 // No complete view here, flatten any deeper listens into an array
10515 var views_1 = [];
10516 if (maybeChildSyncPoint) {
10517 views_1 = syncPointGetQueryViews(maybeChildSyncPoint);
10518 }
10519 each(childMap, function (_key, childViews) {
10520 views_1 = views_1.concat(childViews);
10521 });
10522 return views_1;
10523 }
10524 });
10525}
10526/**
10527 * Normalizes a query to a query we send the server for listening
10528 *
10529 * @returns The normalized query
10530 */
10531function syncTreeQueryForListening_(query) {
10532 if (query._queryParams.loadsAllData() && !query._queryParams.isDefault()) {
10533 // We treat queries that load all data as default queries
10534 // Cast is necessary because ref() technically returns Firebase which is actually fb.api.Firebase which inherits
10535 // from Query
10536 return new (syncTreeGetReferenceConstructor())(query._repo, query._path);
10537 }
10538 else {
10539 return query;
10540 }
10541}
10542function syncTreeRemoveTags_(syncTree, queries) {
10543 for (var j = 0; j < queries.length; ++j) {
10544 var removedQuery = queries[j];
10545 if (!removedQuery._queryParams.loadsAllData()) {
10546 // We should have a tag for this
10547 var removedQueryKey = syncTreeMakeQueryKey_(removedQuery);
10548 var removedQueryTag = syncTree.queryToTagMap.get(removedQueryKey);
10549 syncTree.queryToTagMap.delete(removedQueryKey);
10550 syncTree.tagToQueryMap.delete(removedQueryTag);
10551 }
10552 }
10553}
10554/**
10555 * Static accessor for query tags.
10556 */
10557function syncTreeGetNextQueryTag_() {
10558 return syncTreeNextQueryTag_++;
10559}
10560/**
10561 * For a given new listen, manage the de-duplication of outstanding subscriptions.
10562 *
10563 * @returns This method can return events to support synchronous data sources
10564 */
10565function syncTreeSetupListener_(syncTree, query, view) {
10566 var path = query._path;
10567 var tag = syncTreeTagForQuery_(syncTree, query);
10568 var listener = syncTreeCreateListenerForView_(syncTree, view);
10569 var events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10570 var subtree = syncTree.syncPointTree_.subtree(path);
10571 // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
10572 // may need to shadow other listens as well.
10573 if (tag) {
10574 assert(!syncPointHasCompleteView(subtree.value), "If we're adding a query, it shouldn't be shadowed");
10575 }
10576 else {
10577 // Shadow everything at or below this location, this is a default listener.
10578 var queriesToStop = subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10579 if (!pathIsEmpty(relativePath) &&
10580 maybeChildSyncPoint &&
10581 syncPointHasCompleteView(maybeChildSyncPoint)) {
10582 return [syncPointGetCompleteView(maybeChildSyncPoint).query];
10583 }
10584 else {
10585 // No default listener here, flatten any deeper queries into an array
10586 var queries_1 = [];
10587 if (maybeChildSyncPoint) {
10588 queries_1 = queries_1.concat(syncPointGetQueryViews(maybeChildSyncPoint).map(function (view) { return view.query; }));
10589 }
10590 each(childMap, function (_key, childQueries) {
10591 queries_1 = queries_1.concat(childQueries);
10592 });
10593 return queries_1;
10594 }
10595 });
10596 for (var i = 0; i < queriesToStop.length; ++i) {
10597 var queryToStop = queriesToStop[i];
10598 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery_(syncTree, queryToStop));
10599 }
10600 }
10601 return events;
10602}
10603
10604/**
10605 * @license
10606 * Copyright 2017 Google LLC
10607 *
10608 * Licensed under the Apache License, Version 2.0 (the "License");
10609 * you may not use this file except in compliance with the License.
10610 * You may obtain a copy of the License at
10611 *
10612 * http://www.apache.org/licenses/LICENSE-2.0
10613 *
10614 * Unless required by applicable law or agreed to in writing, software
10615 * distributed under the License is distributed on an "AS IS" BASIS,
10616 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10617 * See the License for the specific language governing permissions and
10618 * limitations under the License.
10619 */
10620var ExistingValueProvider = /** @class */ (function () {
10621 function ExistingValueProvider(node_) {
10622 this.node_ = node_;
10623 }
10624 ExistingValueProvider.prototype.getImmediateChild = function (childName) {
10625 var child = this.node_.getImmediateChild(childName);
10626 return new ExistingValueProvider(child);
10627 };
10628 ExistingValueProvider.prototype.node = function () {
10629 return this.node_;
10630 };
10631 return ExistingValueProvider;
10632}());
10633var DeferredValueProvider = /** @class */ (function () {
10634 function DeferredValueProvider(syncTree, path) {
10635 this.syncTree_ = syncTree;
10636 this.path_ = path;
10637 }
10638 DeferredValueProvider.prototype.getImmediateChild = function (childName) {
10639 var childPath = pathChild(this.path_, childName);
10640 return new DeferredValueProvider(this.syncTree_, childPath);
10641 };
10642 DeferredValueProvider.prototype.node = function () {
10643 return syncTreeCalcCompleteEventCache(this.syncTree_, this.path_);
10644 };
10645 return DeferredValueProvider;
10646}());
10647/**
10648 * Generate placeholders for deferred values.
10649 */
10650var generateWithValues = function (values) {
10651 values = values || {};
10652 values['timestamp'] = values['timestamp'] || new Date().getTime();
10653 return values;
10654};
10655/**
10656 * Value to use when firing local events. When writing server values, fire
10657 * local events with an approximate value, otherwise return value as-is.
10658 */
10659var resolveDeferredLeafValue = function (value, existingVal, serverValues) {
10660 if (!value || typeof value !== 'object') {
10661 return value;
10662 }
10663 assert('.sv' in value, 'Unexpected leaf node or priority contents');
10664 if (typeof value['.sv'] === 'string') {
10665 return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
10666 }
10667 else if (typeof value['.sv'] === 'object') {
10668 return resolveComplexDeferredValue(value['.sv'], existingVal);
10669 }
10670 else {
10671 assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
10672 }
10673};
10674var resolveScalarDeferredValue = function (op, existing, serverValues) {
10675 switch (op) {
10676 case 'timestamp':
10677 return serverValues['timestamp'];
10678 default:
10679 assert(false, 'Unexpected server value: ' + op);
10680 }
10681};
10682var resolveComplexDeferredValue = function (op, existing, unused) {
10683 if (!op.hasOwnProperty('increment')) {
10684 assert(false, 'Unexpected server value: ' + JSON.stringify(op, null, 2));
10685 }
10686 var delta = op['increment'];
10687 if (typeof delta !== 'number') {
10688 assert(false, 'Unexpected increment value: ' + delta);
10689 }
10690 var existingNode = existing.node();
10691 assert(existingNode !== null && typeof existingNode !== 'undefined', 'Expected ChildrenNode.EMPTY_NODE for nulls');
10692 // Incrementing a non-number sets the value to the incremented amount
10693 if (!existingNode.isLeafNode()) {
10694 return delta;
10695 }
10696 var leaf = existingNode;
10697 var existingVal = leaf.getValue();
10698 if (typeof existingVal !== 'number') {
10699 return delta;
10700 }
10701 // No need to do over/underflow arithmetic here because JS only handles floats under the covers
10702 return existingVal + delta;
10703};
10704/**
10705 * Recursively replace all deferred values and priorities in the tree with the
10706 * specified generated replacement values.
10707 * @param path - path to which write is relative
10708 * @param node - new data written at path
10709 * @param syncTree - current data
10710 */
10711var resolveDeferredValueTree = function (path, node, syncTree, serverValues) {
10712 return resolveDeferredValue(node, new DeferredValueProvider(syncTree, path), serverValues);
10713};
10714/**
10715 * Recursively replace all deferred values and priorities in the node with the
10716 * specified generated replacement values. If there are no server values in the node,
10717 * it'll be returned as-is.
10718 */
10719var resolveDeferredValueSnapshot = function (node, existing, serverValues) {
10720 return resolveDeferredValue(node, new ExistingValueProvider(existing), serverValues);
10721};
10722function resolveDeferredValue(node, existingVal, serverValues) {
10723 var rawPri = node.getPriority().val();
10724 var priority = resolveDeferredLeafValue(rawPri, existingVal.getImmediateChild('.priority'), serverValues);
10725 var newNode;
10726 if (node.isLeafNode()) {
10727 var leafNode = node;
10728 var value = resolveDeferredLeafValue(leafNode.getValue(), existingVal, serverValues);
10729 if (value !== leafNode.getValue() ||
10730 priority !== leafNode.getPriority().val()) {
10731 return new LeafNode(value, nodeFromJSON(priority));
10732 }
10733 else {
10734 return node;
10735 }
10736 }
10737 else {
10738 var childrenNode = node;
10739 newNode = childrenNode;
10740 if (priority !== childrenNode.getPriority().val()) {
10741 newNode = newNode.updatePriority(new LeafNode(priority));
10742 }
10743 childrenNode.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
10744 var newChildNode = resolveDeferredValue(childNode, existingVal.getImmediateChild(childName), serverValues);
10745 if (newChildNode !== childNode) {
10746 newNode = newNode.updateImmediateChild(childName, newChildNode);
10747 }
10748 });
10749 return newNode;
10750 }
10751}
10752
10753/**
10754 * @license
10755 * Copyright 2017 Google LLC
10756 *
10757 * Licensed under the Apache License, Version 2.0 (the "License");
10758 * you may not use this file except in compliance with the License.
10759 * You may obtain a copy of the License at
10760 *
10761 * http://www.apache.org/licenses/LICENSE-2.0
10762 *
10763 * Unless required by applicable law or agreed to in writing, software
10764 * distributed under the License is distributed on an "AS IS" BASIS,
10765 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10766 * See the License for the specific language governing permissions and
10767 * limitations under the License.
10768 */
10769/**
10770 * A light-weight tree, traversable by path. Nodes can have both values and children.
10771 * Nodes are not enumerated (by forEachChild) unless they have a value or non-empty
10772 * children.
10773 */
10774var Tree = /** @class */ (function () {
10775 /**
10776 * @param name - Optional name of the node.
10777 * @param parent - Optional parent node.
10778 * @param node - Optional node to wrap.
10779 */
10780 function Tree(name, parent, node) {
10781 if (name === void 0) { name = ''; }
10782 if (parent === void 0) { parent = null; }
10783 if (node === void 0) { node = { children: {}, childCount: 0 }; }
10784 this.name = name;
10785 this.parent = parent;
10786 this.node = node;
10787 }
10788 return Tree;
10789}());
10790/**
10791 * Returns a sub-Tree for the given path.
10792 *
10793 * @param pathObj - Path to look up.
10794 * @returns Tree for path.
10795 */
10796function treeSubTree(tree, pathObj) {
10797 // TODO: Require pathObj to be Path?
10798 var path = pathObj instanceof Path ? pathObj : new Path(pathObj);
10799 var child = tree, next = pathGetFront(path);
10800 while (next !== null) {
10801 var childNode = safeGet(child.node.children, next) || {
10802 children: {},
10803 childCount: 0
10804 };
10805 child = new Tree(next, child, childNode);
10806 path = pathPopFront(path);
10807 next = pathGetFront(path);
10808 }
10809 return child;
10810}
10811/**
10812 * Returns the data associated with this tree node.
10813 *
10814 * @returns The data or null if no data exists.
10815 */
10816function treeGetValue(tree) {
10817 return tree.node.value;
10818}
10819/**
10820 * Sets data to this tree node.
10821 *
10822 * @param value - Value to set.
10823 */
10824function treeSetValue(tree, value) {
10825 tree.node.value = value;
10826 treeUpdateParents(tree);
10827}
10828/**
10829 * @returns Whether the tree has any children.
10830 */
10831function treeHasChildren(tree) {
10832 return tree.node.childCount > 0;
10833}
10834/**
10835 * @returns Whethe rthe tree is empty (no value or children).
10836 */
10837function treeIsEmpty(tree) {
10838 return treeGetValue(tree) === undefined && !treeHasChildren(tree);
10839}
10840/**
10841 * Calls action for each child of this tree node.
10842 *
10843 * @param action - Action to be called for each child.
10844 */
10845function treeForEachChild(tree, action) {
10846 each(tree.node.children, function (child, childTree) {
10847 action(new Tree(child, tree, childTree));
10848 });
10849}
10850/**
10851 * Does a depth-first traversal of this node's descendants, calling action for each one.
10852 *
10853 * @param action - Action to be called for each child.
10854 * @param includeSelf - Whether to call action on this node as well. Defaults to
10855 * false.
10856 * @param childrenFirst - Whether to call action on children before calling it on
10857 * parent.
10858 */
10859function treeForEachDescendant(tree, action, includeSelf, childrenFirst) {
10860 if (includeSelf && !childrenFirst) {
10861 action(tree);
10862 }
10863 treeForEachChild(tree, function (child) {
10864 treeForEachDescendant(child, action, true, childrenFirst);
10865 });
10866 if (includeSelf && childrenFirst) {
10867 action(tree);
10868 }
10869}
10870/**
10871 * Calls action on each ancestor node.
10872 *
10873 * @param action - Action to be called on each parent; return
10874 * true to abort.
10875 * @param includeSelf - Whether to call action on this node as well.
10876 * @returns true if the action callback returned true.
10877 */
10878function treeForEachAncestor(tree, action, includeSelf) {
10879 var node = includeSelf ? tree : tree.parent;
10880 while (node !== null) {
10881 if (action(node)) {
10882 return true;
10883 }
10884 node = node.parent;
10885 }
10886 return false;
10887}
10888/**
10889 * @returns The path of this tree node, as a Path.
10890 */
10891function treeGetPath(tree) {
10892 return new Path(tree.parent === null
10893 ? tree.name
10894 : treeGetPath(tree.parent) + '/' + tree.name);
10895}
10896/**
10897 * Adds or removes this child from its parent based on whether it's empty or not.
10898 */
10899function treeUpdateParents(tree) {
10900 if (tree.parent !== null) {
10901 treeUpdateChild(tree.parent, tree.name, tree);
10902 }
10903}
10904/**
10905 * Adds or removes the passed child to this tree node, depending on whether it's empty.
10906 *
10907 * @param childName - The name of the child to update.
10908 * @param child - The child to update.
10909 */
10910function treeUpdateChild(tree, childName, child) {
10911 var childEmpty = treeIsEmpty(child);
10912 var childExists = contains(tree.node.children, childName);
10913 if (childEmpty && childExists) {
10914 delete tree.node.children[childName];
10915 tree.node.childCount--;
10916 treeUpdateParents(tree);
10917 }
10918 else if (!childEmpty && !childExists) {
10919 tree.node.children[childName] = child.node;
10920 tree.node.childCount++;
10921 treeUpdateParents(tree);
10922 }
10923}
10924
10925/**
10926 * @license
10927 * Copyright 2017 Google LLC
10928 *
10929 * Licensed under the Apache License, Version 2.0 (the "License");
10930 * you may not use this file except in compliance with the License.
10931 * You may obtain a copy of the License at
10932 *
10933 * http://www.apache.org/licenses/LICENSE-2.0
10934 *
10935 * Unless required by applicable law or agreed to in writing, software
10936 * distributed under the License is distributed on an "AS IS" BASIS,
10937 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10938 * See the License for the specific language governing permissions and
10939 * limitations under the License.
10940 */
10941/**
10942 * True for invalid Firebase keys
10943 */
10944var INVALID_KEY_REGEX_ = /[\[\].#$\/\u0000-\u001F\u007F]/;
10945/**
10946 * True for invalid Firebase paths.
10947 * Allows '/' in paths.
10948 */
10949var INVALID_PATH_REGEX_ = /[\[\].#$\u0000-\u001F\u007F]/;
10950/**
10951 * Maximum number of characters to allow in leaf value
10952 */
10953var MAX_LEAF_SIZE_ = 10 * 1024 * 1024;
10954var isValidKey = function (key) {
10955 return (typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX_.test(key));
10956};
10957var isValidPathString = function (pathString) {
10958 return (typeof pathString === 'string' &&
10959 pathString.length !== 0 &&
10960 !INVALID_PATH_REGEX_.test(pathString));
10961};
10962var isValidRootPathString = function (pathString) {
10963 if (pathString) {
10964 // Allow '/.info/' at the beginning.
10965 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10966 }
10967 return isValidPathString(pathString);
10968};
10969var isValidPriority = function (priority) {
10970 return (priority === null ||
10971 typeof priority === 'string' ||
10972 (typeof priority === 'number' && !isInvalidJSONNumber(priority)) ||
10973 (priority &&
10974 typeof priority === 'object' &&
10975 // eslint-disable-next-line @typescript-eslint/no-explicit-any
10976 contains(priority, '.sv')));
10977};
10978/**
10979 * Pre-validate a datum passed as an argument to Firebase function.
10980 */
10981var validateFirebaseDataArg = function (fnName, value, path, optional) {
10982 if (optional && value === undefined) {
10983 return;
10984 }
10985 validateFirebaseData(errorPrefix(fnName, 'value'), value, path);
10986};
10987/**
10988 * Validate a data object client-side before sending to server.
10989 */
10990var validateFirebaseData = function (errorPrefix, data, path_) {
10991 var path = path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;
10992 if (data === undefined) {
10993 throw new Error(errorPrefix + 'contains undefined ' + validationPathToErrorString(path));
10994 }
10995 if (typeof data === 'function') {
10996 throw new Error(errorPrefix +
10997 'contains a function ' +
10998 validationPathToErrorString(path) +
10999 ' with contents = ' +
11000 data.toString());
11001 }
11002 if (isInvalidJSONNumber(data)) {
11003 throw new Error(errorPrefix +
11004 'contains ' +
11005 data.toString() +
11006 ' ' +
11007 validationPathToErrorString(path));
11008 }
11009 // Check max leaf size, but try to avoid the utf8 conversion if we can.
11010 if (typeof data === 'string' &&
11011 data.length > MAX_LEAF_SIZE_ / 3 &&
11012 stringLength(data) > MAX_LEAF_SIZE_) {
11013 throw new Error(errorPrefix +
11014 'contains a string greater than ' +
11015 MAX_LEAF_SIZE_ +
11016 ' utf8 bytes ' +
11017 validationPathToErrorString(path) +
11018 " ('" +
11019 data.substring(0, 50) +
11020 "...')");
11021 }
11022 // TODO = Perf = Consider combining the recursive validation of keys into NodeFromJSON
11023 // to save extra walking of large objects.
11024 if (data && typeof data === 'object') {
11025 var hasDotValue_1 = false;
11026 var hasActualChild_1 = false;
11027 each(data, function (key, value) {
11028 if (key === '.value') {
11029 hasDotValue_1 = true;
11030 }
11031 else if (key !== '.priority' && key !== '.sv') {
11032 hasActualChild_1 = true;
11033 if (!isValidKey(key)) {
11034 throw new Error(errorPrefix +
11035 ' contains an invalid key (' +
11036 key +
11037 ') ' +
11038 validationPathToErrorString(path) +
11039 '. Keys must be non-empty strings ' +
11040 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11041 }
11042 }
11043 validationPathPush(path, key);
11044 validateFirebaseData(errorPrefix, value, path);
11045 validationPathPop(path);
11046 });
11047 if (hasDotValue_1 && hasActualChild_1) {
11048 throw new Error(errorPrefix +
11049 ' contains ".value" child ' +
11050 validationPathToErrorString(path) +
11051 ' in addition to actual children.');
11052 }
11053 }
11054};
11055/**
11056 * Pre-validate paths passed in the firebase function.
11057 */
11058var validateFirebaseMergePaths = function (errorPrefix, mergePaths) {
11059 var i, curPath;
11060 for (i = 0; i < mergePaths.length; i++) {
11061 curPath = mergePaths[i];
11062 var keys = pathSlice(curPath);
11063 for (var j = 0; j < keys.length; j++) {
11064 if (keys[j] === '.priority' && j === keys.length - 1) ;
11065 else if (!isValidKey(keys[j])) {
11066 throw new Error(errorPrefix +
11067 'contains an invalid key (' +
11068 keys[j] +
11069 ') in path ' +
11070 curPath.toString() +
11071 '. Keys must be non-empty strings ' +
11072 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11073 }
11074 }
11075 }
11076 // Check that update keys are not descendants of each other.
11077 // We rely on the property that sorting guarantees that ancestors come
11078 // right before descendants.
11079 mergePaths.sort(pathCompare);
11080 var prevPath = null;
11081 for (i = 0; i < mergePaths.length; i++) {
11082 curPath = mergePaths[i];
11083 if (prevPath !== null && pathContains(prevPath, curPath)) {
11084 throw new Error(errorPrefix +
11085 'contains a path ' +
11086 prevPath.toString() +
11087 ' that is ancestor of another path ' +
11088 curPath.toString());
11089 }
11090 prevPath = curPath;
11091 }
11092};
11093/**
11094 * pre-validate an object passed as an argument to firebase function (
11095 * must be an object - e.g. for firebase.update()).
11096 */
11097var validateFirebaseMergeDataArg = function (fnName, data, path, optional) {
11098 if (optional && data === undefined) {
11099 return;
11100 }
11101 var errorPrefix$1 = errorPrefix(fnName, 'values');
11102 if (!(data && typeof data === 'object') || Array.isArray(data)) {
11103 throw new Error(errorPrefix$1 + ' must be an object containing the children to replace.');
11104 }
11105 var mergePaths = [];
11106 each(data, function (key, value) {
11107 var curPath = new Path(key);
11108 validateFirebaseData(errorPrefix$1, value, pathChild(path, curPath));
11109 if (pathGetBack(curPath) === '.priority') {
11110 if (!isValidPriority(value)) {
11111 throw new Error(errorPrefix$1 +
11112 "contains an invalid value for '" +
11113 curPath.toString() +
11114 "', which must be a valid " +
11115 'Firebase priority (a string, finite number, server value, or null).');
11116 }
11117 }
11118 mergePaths.push(curPath);
11119 });
11120 validateFirebaseMergePaths(errorPrefix$1, mergePaths);
11121};
11122var validatePriority = function (fnName, priority, optional) {
11123 if (optional && priority === undefined) {
11124 return;
11125 }
11126 if (isInvalidJSONNumber(priority)) {
11127 throw new Error(errorPrefix(fnName, 'priority') +
11128 'is ' +
11129 priority.toString() +
11130 ', but must be a valid Firebase priority (a string, finite number, ' +
11131 'server value, or null).');
11132 }
11133 // Special case to allow importing data with a .sv.
11134 if (!isValidPriority(priority)) {
11135 throw new Error(errorPrefix(fnName, 'priority') +
11136 'must be a valid Firebase priority ' +
11137 '(a string, finite number, server value, or null).');
11138 }
11139};
11140var validateKey = function (fnName, argumentName, key, optional) {
11141 if (optional && key === undefined) {
11142 return;
11143 }
11144 if (!isValidKey(key)) {
11145 throw new Error(errorPrefix(fnName, argumentName) +
11146 'was an invalid key = "' +
11147 key +
11148 '". Firebase keys must be non-empty strings and ' +
11149 'can\'t contain ".", "#", "$", "/", "[", or "]").');
11150 }
11151};
11152/**
11153 * @internal
11154 */
11155var validatePathString = function (fnName, argumentName, pathString, optional) {
11156 if (optional && pathString === undefined) {
11157 return;
11158 }
11159 if (!isValidPathString(pathString)) {
11160 throw new Error(errorPrefix(fnName, argumentName) +
11161 'was an invalid path = "' +
11162 pathString +
11163 '". Paths must be non-empty strings and ' +
11164 'can\'t contain ".", "#", "$", "[", or "]"');
11165 }
11166};
11167var validateRootPathString = function (fnName, argumentName, pathString, optional) {
11168 if (pathString) {
11169 // Allow '/.info/' at the beginning.
11170 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
11171 }
11172 validatePathString(fnName, argumentName, pathString, optional);
11173};
11174/**
11175 * @internal
11176 */
11177var validateWritablePath = function (fnName, path) {
11178 if (pathGetFront(path) === '.info') {
11179 throw new Error(fnName + " failed = Can't modify data under /.info/");
11180 }
11181};
11182var validateUrl = function (fnName, parsedUrl) {
11183 // TODO = Validate server better.
11184 var pathString = parsedUrl.path.toString();
11185 if (!(typeof parsedUrl.repoInfo.host === 'string') ||
11186 parsedUrl.repoInfo.host.length === 0 ||
11187 (!isValidKey(parsedUrl.repoInfo.namespace) &&
11188 parsedUrl.repoInfo.host.split(':')[0] !== 'localhost') ||
11189 (pathString.length !== 0 && !isValidRootPathString(pathString))) {
11190 throw new Error(errorPrefix(fnName, 'url') +
11191 'must be a valid firebase URL and ' +
11192 'the path can\'t contain ".", "#", "$", "[", or "]".');
11193 }
11194};
11195
11196/**
11197 * @license
11198 * Copyright 2017 Google LLC
11199 *
11200 * Licensed under the Apache License, Version 2.0 (the "License");
11201 * you may not use this file except in compliance with the License.
11202 * You may obtain a copy of the License at
11203 *
11204 * http://www.apache.org/licenses/LICENSE-2.0
11205 *
11206 * Unless required by applicable law or agreed to in writing, software
11207 * distributed under the License is distributed on an "AS IS" BASIS,
11208 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11209 * See the License for the specific language governing permissions and
11210 * limitations under the License.
11211 */
11212/**
11213 * The event queue serves a few purposes:
11214 * 1. It ensures we maintain event order in the face of event callbacks doing operations that result in more
11215 * events being queued.
11216 * 2. raiseQueuedEvents() handles being called reentrantly nicely. That is, if in the course of raising events,
11217 * raiseQueuedEvents() is called again, the "inner" call will pick up raising events where the "outer" call
11218 * left off, ensuring that the events are still raised synchronously and in order.
11219 * 3. You can use raiseEventsAtPath and raiseEventsForChangedPath to ensure only relevant previously-queued
11220 * events are raised synchronously.
11221 *
11222 * NOTE: This can all go away if/when we move to async events.
11223 *
11224 */
11225var EventQueue = /** @class */ (function () {
11226 function EventQueue() {
11227 this.eventLists_ = [];
11228 /**
11229 * Tracks recursion depth of raiseQueuedEvents_, for debugging purposes.
11230 */
11231 this.recursionDepth_ = 0;
11232 }
11233 return EventQueue;
11234}());
11235/**
11236 * @param eventDataList - The new events to queue.
11237 */
11238function eventQueueQueueEvents(eventQueue, eventDataList) {
11239 // We group events by path, storing them in a single EventList, to make it easier to skip over them quickly.
11240 var currList = null;
11241 for (var i = 0; i < eventDataList.length; i++) {
11242 var data = eventDataList[i];
11243 var path = data.getPath();
11244 if (currList !== null && !pathEquals(path, currList.path)) {
11245 eventQueue.eventLists_.push(currList);
11246 currList = null;
11247 }
11248 if (currList === null) {
11249 currList = { events: [], path: path };
11250 }
11251 currList.events.push(data);
11252 }
11253 if (currList) {
11254 eventQueue.eventLists_.push(currList);
11255 }
11256}
11257/**
11258 * Queues the specified events and synchronously raises all events (including previously queued ones)
11259 * for the specified path.
11260 *
11261 * It is assumed that the new events are all for the specified path.
11262 *
11263 * @param path - The path to raise events for.
11264 * @param eventDataList - The new events to raise.
11265 */
11266function eventQueueRaiseEventsAtPath(eventQueue, path, eventDataList) {
11267 eventQueueQueueEvents(eventQueue, eventDataList);
11268 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11269 return pathEquals(eventPath, path);
11270 });
11271}
11272/**
11273 * Queues the specified events and synchronously raises all events (including previously queued ones) for
11274 * locations related to the specified change path (i.e. all ancestors and descendants).
11275 *
11276 * It is assumed that the new events are all related (ancestor or descendant) to the specified path.
11277 *
11278 * @param changedPath - The path to raise events for.
11279 * @param eventDataList - The events to raise
11280 */
11281function eventQueueRaiseEventsForChangedPath(eventQueue, changedPath, eventDataList) {
11282 eventQueueQueueEvents(eventQueue, eventDataList);
11283 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11284 return pathContains(eventPath, changedPath) ||
11285 pathContains(changedPath, eventPath);
11286 });
11287}
11288function eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, predicate) {
11289 eventQueue.recursionDepth_++;
11290 var sentAll = true;
11291 for (var i = 0; i < eventQueue.eventLists_.length; i++) {
11292 var eventList = eventQueue.eventLists_[i];
11293 if (eventList) {
11294 var eventPath = eventList.path;
11295 if (predicate(eventPath)) {
11296 eventListRaise(eventQueue.eventLists_[i]);
11297 eventQueue.eventLists_[i] = null;
11298 }
11299 else {
11300 sentAll = false;
11301 }
11302 }
11303 }
11304 if (sentAll) {
11305 eventQueue.eventLists_ = [];
11306 }
11307 eventQueue.recursionDepth_--;
11308}
11309/**
11310 * Iterates through the list and raises each event
11311 */
11312function eventListRaise(eventList) {
11313 for (var i = 0; i < eventList.events.length; i++) {
11314 var eventData = eventList.events[i];
11315 if (eventData !== null) {
11316 eventList.events[i] = null;
11317 var eventFn = eventData.getEventRunner();
11318 if (logger) {
11319 log('event: ' + eventData.toString());
11320 }
11321 exceptionGuard(eventFn);
11322 }
11323 }
11324}
11325
11326/**
11327 * @license
11328 * Copyright 2017 Google LLC
11329 *
11330 * Licensed under the Apache License, Version 2.0 (the "License");
11331 * you may not use this file except in compliance with the License.
11332 * You may obtain a copy of the License at
11333 *
11334 * http://www.apache.org/licenses/LICENSE-2.0
11335 *
11336 * Unless required by applicable law or agreed to in writing, software
11337 * distributed under the License is distributed on an "AS IS" BASIS,
11338 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11339 * See the License for the specific language governing permissions and
11340 * limitations under the License.
11341 */
11342var INTERRUPT_REASON = 'repo_interrupt';
11343/**
11344 * If a transaction does not succeed after 25 retries, we abort it. Among other
11345 * things this ensure that if there's ever a bug causing a mismatch between
11346 * client / server hashes for some data, we won't retry indefinitely.
11347 */
11348var MAX_TRANSACTION_RETRIES = 25;
11349/**
11350 * A connection to a single data repository.
11351 */
11352var Repo = /** @class */ (function () {
11353 function Repo(repoInfo_, forceRestClient_, authTokenProvider_, appCheckProvider_) {
11354 this.repoInfo_ = repoInfo_;
11355 this.forceRestClient_ = forceRestClient_;
11356 this.authTokenProvider_ = authTokenProvider_;
11357 this.appCheckProvider_ = appCheckProvider_;
11358 this.dataUpdateCount = 0;
11359 this.statsListener_ = null;
11360 this.eventQueue_ = new EventQueue();
11361 this.nextWriteId_ = 1;
11362 this.interceptServerDataCallback_ = null;
11363 /** A list of data pieces and paths to be set when this client disconnects. */
11364 this.onDisconnect_ = newSparseSnapshotTree();
11365 /** Stores queues of outstanding transactions for Firebase locations. */
11366 this.transactionQueueTree_ = new Tree();
11367 // TODO: This should be @private but it's used by test_access.js and internal.js
11368 this.persistentConnection_ = null;
11369 // This key is intentionally not updated if RepoInfo is later changed or replaced
11370 this.key = this.repoInfo_.toURLString();
11371 }
11372 /**
11373 * @returns The URL corresponding to the root of this Firebase.
11374 */
11375 Repo.prototype.toString = function () {
11376 return ((this.repoInfo_.secure ? 'https://' : 'http://') + this.repoInfo_.host);
11377 };
11378 return Repo;
11379}());
11380function repoStart(repo, appId, authOverride) {
11381 repo.stats_ = statsManagerGetCollection(repo.repoInfo_);
11382 if (repo.forceRestClient_ || beingCrawled()) {
11383 repo.server_ = new ReadonlyRestClient(repo.repoInfo_, function (pathString, data, isMerge, tag) {
11384 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11385 }, repo.authTokenProvider_, repo.appCheckProvider_);
11386 // Minor hack: Fire onConnect immediately, since there's no actual connection.
11387 setTimeout(function () { return repoOnConnectStatus(repo, /* connectStatus= */ true); }, 0);
11388 }
11389 else {
11390 // Validate authOverride
11391 if (typeof authOverride !== 'undefined' && authOverride !== null) {
11392 if (typeof authOverride !== 'object') {
11393 throw new Error('Only objects are supported for option databaseAuthVariableOverride');
11394 }
11395 try {
11396 stringify(authOverride);
11397 }
11398 catch (e) {
11399 throw new Error('Invalid authOverride provided: ' + e);
11400 }
11401 }
11402 repo.persistentConnection_ = new PersistentConnection(repo.repoInfo_, appId, function (pathString, data, isMerge, tag) {
11403 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11404 }, function (connectStatus) {
11405 repoOnConnectStatus(repo, connectStatus);
11406 }, function (updates) {
11407 repoOnServerInfoUpdate(repo, updates);
11408 }, repo.authTokenProvider_, repo.appCheckProvider_, authOverride);
11409 repo.server_ = repo.persistentConnection_;
11410 }
11411 repo.authTokenProvider_.addTokenChangeListener(function (token) {
11412 repo.server_.refreshAuthToken(token);
11413 });
11414 repo.appCheckProvider_.addTokenChangeListener(function (result) {
11415 repo.server_.refreshAppCheckToken(result.token);
11416 });
11417 // In the case of multiple Repos for the same repoInfo (i.e. there are multiple Firebase.Contexts being used),
11418 // we only want to create one StatsReporter. As such, we'll report stats over the first Repo created.
11419 repo.statsReporter_ = statsManagerGetOrCreateReporter(repo.repoInfo_, function () { return new StatsReporter(repo.stats_, repo.server_); });
11420 // Used for .info.
11421 repo.infoData_ = new SnapshotHolder();
11422 repo.infoSyncTree_ = new SyncTree({
11423 startListening: function (query, tag, currentHashFn, onComplete) {
11424 var infoEvents = [];
11425 var node = repo.infoData_.getNode(query._path);
11426 // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
11427 // on initial data...
11428 if (!node.isEmpty()) {
11429 infoEvents = syncTreeApplyServerOverwrite(repo.infoSyncTree_, query._path, node);
11430 setTimeout(function () {
11431 onComplete('ok');
11432 }, 0);
11433 }
11434 return infoEvents;
11435 },
11436 stopListening: function () { }
11437 });
11438 repoUpdateInfo(repo, 'connected', false);
11439 repo.serverSyncTree_ = new SyncTree({
11440 startListening: function (query, tag, currentHashFn, onComplete) {
11441 repo.server_.listen(query, currentHashFn, tag, function (status, data) {
11442 var events = onComplete(status, data);
11443 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11444 });
11445 // No synchronous events for network-backed sync trees
11446 return [];
11447 },
11448 stopListening: function (query, tag) {
11449 repo.server_.unlisten(query, tag);
11450 }
11451 });
11452}
11453/**
11454 * @returns The time in milliseconds, taking the server offset into account if we have one.
11455 */
11456function repoServerTime(repo) {
11457 var offsetNode = repo.infoData_.getNode(new Path('.info/serverTimeOffset'));
11458 var offset = offsetNode.val() || 0;
11459 return new Date().getTime() + offset;
11460}
11461/**
11462 * Generate ServerValues using some variables from the repo object.
11463 */
11464function repoGenerateServerValues(repo) {
11465 return generateWithValues({
11466 timestamp: repoServerTime(repo)
11467 });
11468}
11469/**
11470 * Called by realtime when we get new messages from the server.
11471 */
11472function repoOnDataUpdate(repo, pathString, data, isMerge, tag) {
11473 // For testing.
11474 repo.dataUpdateCount++;
11475 var path = new Path(pathString);
11476 data = repo.interceptServerDataCallback_
11477 ? repo.interceptServerDataCallback_(pathString, data)
11478 : data;
11479 var events = [];
11480 if (tag) {
11481 if (isMerge) {
11482 var taggedChildren = map(data, function (raw) { return nodeFromJSON(raw); });
11483 events = syncTreeApplyTaggedQueryMerge(repo.serverSyncTree_, path, taggedChildren, tag);
11484 }
11485 else {
11486 var taggedSnap = nodeFromJSON(data);
11487 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, path, taggedSnap, tag);
11488 }
11489 }
11490 else if (isMerge) {
11491 var changedChildren = map(data, function (raw) { return nodeFromJSON(raw); });
11492 events = syncTreeApplyServerMerge(repo.serverSyncTree_, path, changedChildren);
11493 }
11494 else {
11495 var snap = nodeFromJSON(data);
11496 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap);
11497 }
11498 var affectedPath = path;
11499 if (events.length > 0) {
11500 // Since we have a listener outstanding for each transaction, receiving any events
11501 // is a proxy for some change having occurred.
11502 affectedPath = repoRerunTransactions(repo, path);
11503 }
11504 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, events);
11505}
11506function repoOnConnectStatus(repo, connectStatus) {
11507 repoUpdateInfo(repo, 'connected', connectStatus);
11508 if (connectStatus === false) {
11509 repoRunOnDisconnectEvents(repo);
11510 }
11511}
11512function repoOnServerInfoUpdate(repo, updates) {
11513 each(updates, function (key, value) {
11514 repoUpdateInfo(repo, key, value);
11515 });
11516}
11517function repoUpdateInfo(repo, pathString, value) {
11518 var path = new Path('/.info/' + pathString);
11519 var newNode = nodeFromJSON(value);
11520 repo.infoData_.updateSnapshot(path, newNode);
11521 var events = syncTreeApplyServerOverwrite(repo.infoSyncTree_, path, newNode);
11522 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11523}
11524function repoGetNextWriteId(repo) {
11525 return repo.nextWriteId_++;
11526}
11527/**
11528 * The purpose of `getValue` is to return the latest known value
11529 * satisfying `query`.
11530 *
11531 * This method will first check for in-memory cached values
11532 * belonging to active listeners. If they are found, such values
11533 * are considered to be the most up-to-date.
11534 *
11535 * If the client is not connected, this method will try to
11536 * establish a connection and request the value for `query`. If
11537 * the client is not able to retrieve the query result, it reports
11538 * an error.
11539 *
11540 * @param query - The query to surface a value for.
11541 */
11542function repoGetValue(repo, query) {
11543 // Only active queries are cached. There is no persisted cache.
11544 var cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11545 if (cached != null) {
11546 return Promise.resolve(cached);
11547 }
11548 return repo.server_.get(query).then(function (payload) {
11549 var node = nodeFromJSON(payload).withIndex(query._queryParams.getIndex());
11550 // if this is a filtered query, then overwrite at path
11551 if (query._queryParams.loadsAllData()) {
11552 syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11553 }
11554 else {
11555 // Simulate `syncTreeAddEventRegistration` without events/listener setup.
11556 // We do this (along with the syncTreeRemoveEventRegistration` below) so that
11557 // `repoGetValue` results have the same cache effects as initial listener(s)
11558 // updates.
11559 var tag = syncTreeRegisterQuery(repo.serverSyncTree_, query);
11560 syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, query._path, node, tag);
11561 // Call `syncTreeRemoveEventRegistration` with a null event registration, since there is none.
11562 // Note: The below code essentially unregisters the query and cleans up any views/syncpoints temporarily created above.
11563 }
11564 var cancels = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, null);
11565 if (cancels.length > 0) {
11566 repoLog(repo, 'unexpected cancel events in repoGetValue');
11567 }
11568 return node;
11569 }, function (err) {
11570 repoLog(repo, 'get for query ' + stringify(query) + ' failed: ' + err);
11571 return Promise.reject(new Error(err));
11572 });
11573}
11574function repoSetWithPriority(repo, path, newVal, newPriority, onComplete) {
11575 repoLog(repo, 'set', {
11576 path: path.toString(),
11577 value: newVal,
11578 priority: newPriority
11579 });
11580 // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
11581 // (b) store unresolved paths on JSON parse
11582 var serverValues = repoGenerateServerValues(repo);
11583 var newNodeUnresolved = nodeFromJSON(newVal, newPriority);
11584 var existing = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path);
11585 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, existing, serverValues);
11586 var writeId = repoGetNextWriteId(repo);
11587 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, writeId, true);
11588 eventQueueQueueEvents(repo.eventQueue_, events);
11589 repo.server_.put(path.toString(), newNodeUnresolved.val(/*export=*/ true), function (status, errorReason) {
11590 var success = status === 'ok';
11591 if (!success) {
11592 warn('set at ' + path + ' failed: ' + status);
11593 }
11594 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId, !success);
11595 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, clearEvents);
11596 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11597 });
11598 var affectedPath = repoAbortTransactions(repo, path);
11599 repoRerunTransactions(repo, affectedPath);
11600 // We queued the events above, so just flush the queue here
11601 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, []);
11602}
11603function repoUpdate(repo, path, childrenToMerge, onComplete) {
11604 repoLog(repo, 'update', { path: path.toString(), value: childrenToMerge });
11605 // Start with our existing data and merge each child into it.
11606 var empty = true;
11607 var serverValues = repoGenerateServerValues(repo);
11608 var changedChildren = {};
11609 each(childrenToMerge, function (changedKey, changedValue) {
11610 empty = false;
11611 changedChildren[changedKey] = resolveDeferredValueTree(pathChild(path, changedKey), nodeFromJSON(changedValue), repo.serverSyncTree_, serverValues);
11612 });
11613 if (!empty) {
11614 var writeId_1 = repoGetNextWriteId(repo);
11615 var events = syncTreeApplyUserMerge(repo.serverSyncTree_, path, changedChildren, writeId_1);
11616 eventQueueQueueEvents(repo.eventQueue_, events);
11617 repo.server_.merge(path.toString(), childrenToMerge, function (status, errorReason) {
11618 var success = status === 'ok';
11619 if (!success) {
11620 warn('update at ' + path + ' failed: ' + status);
11621 }
11622 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId_1, !success);
11623 var affectedPath = clearEvents.length > 0 ? repoRerunTransactions(repo, path) : path;
11624 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, clearEvents);
11625 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11626 });
11627 each(childrenToMerge, function (changedPath) {
11628 var affectedPath = repoAbortTransactions(repo, pathChild(path, changedPath));
11629 repoRerunTransactions(repo, affectedPath);
11630 });
11631 // We queued the events above, so just flush the queue here
11632 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, []);
11633 }
11634 else {
11635 log("update() called with empty data. Don't do anything.");
11636 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11637 }
11638}
11639/**
11640 * Applies all of the changes stored up in the onDisconnect_ tree.
11641 */
11642function repoRunOnDisconnectEvents(repo) {
11643 repoLog(repo, 'onDisconnectEvents');
11644 var serverValues = repoGenerateServerValues(repo);
11645 var resolvedOnDisconnectTree = newSparseSnapshotTree();
11646 sparseSnapshotTreeForEachTree(repo.onDisconnect_, newEmptyPath(), function (path, node) {
11647 var resolved = resolveDeferredValueTree(path, node, repo.serverSyncTree_, serverValues);
11648 sparseSnapshotTreeRemember(resolvedOnDisconnectTree, path, resolved);
11649 });
11650 var events = [];
11651 sparseSnapshotTreeForEachTree(resolvedOnDisconnectTree, newEmptyPath(), function (path, snap) {
11652 events = events.concat(syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap));
11653 var affectedPath = repoAbortTransactions(repo, path);
11654 repoRerunTransactions(repo, affectedPath);
11655 });
11656 repo.onDisconnect_ = newSparseSnapshotTree();
11657 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, newEmptyPath(), events);
11658}
11659function repoOnDisconnectCancel(repo, path, onComplete) {
11660 repo.server_.onDisconnectCancel(path.toString(), function (status, errorReason) {
11661 if (status === 'ok') {
11662 sparseSnapshotTreeForget(repo.onDisconnect_, path);
11663 }
11664 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11665 });
11666}
11667function repoOnDisconnectSet(repo, path, value, onComplete) {
11668 var newNode = nodeFromJSON(value);
11669 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11670 if (status === 'ok') {
11671 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11672 }
11673 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11674 });
11675}
11676function repoOnDisconnectSetWithPriority(repo, path, value, priority, onComplete) {
11677 var newNode = nodeFromJSON(value, priority);
11678 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11679 if (status === 'ok') {
11680 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11681 }
11682 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11683 });
11684}
11685function repoOnDisconnectUpdate(repo, path, childrenToMerge, onComplete) {
11686 if (isEmpty(childrenToMerge)) {
11687 log("onDisconnect().update() called with empty data. Don't do anything.");
11688 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11689 return;
11690 }
11691 repo.server_.onDisconnectMerge(path.toString(), childrenToMerge, function (status, errorReason) {
11692 if (status === 'ok') {
11693 each(childrenToMerge, function (childName, childNode) {
11694 var newChildNode = nodeFromJSON(childNode);
11695 sparseSnapshotTreeRemember(repo.onDisconnect_, pathChild(path, childName), newChildNode);
11696 });
11697 }
11698 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11699 });
11700}
11701function repoAddEventCallbackForQuery(repo, query, eventRegistration) {
11702 var events;
11703 if (pathGetFront(query._path) === '.info') {
11704 events = syncTreeAddEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11705 }
11706 else {
11707 events = syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11708 }
11709 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11710}
11711function repoRemoveEventCallbackForQuery(repo, query, eventRegistration) {
11712 // These are guaranteed not to raise events, since we're not passing in a cancelError. However, we can future-proof
11713 // a little bit by handling the return values anyways.
11714 var events;
11715 if (pathGetFront(query._path) === '.info') {
11716 events = syncTreeRemoveEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11717 }
11718 else {
11719 events = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11720 }
11721 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11722}
11723function repoInterrupt(repo) {
11724 if (repo.persistentConnection_) {
11725 repo.persistentConnection_.interrupt(INTERRUPT_REASON);
11726 }
11727}
11728function repoResume(repo) {
11729 if (repo.persistentConnection_) {
11730 repo.persistentConnection_.resume(INTERRUPT_REASON);
11731 }
11732}
11733function repoLog(repo) {
11734 var varArgs = [];
11735 for (var _i = 1; _i < arguments.length; _i++) {
11736 varArgs[_i - 1] = arguments[_i];
11737 }
11738 var prefix = '';
11739 if (repo.persistentConnection_) {
11740 prefix = repo.persistentConnection_.id + ':';
11741 }
11742 log.apply(void 0, __spreadArray([prefix], __read(varArgs)));
11743}
11744function repoCallOnCompleteCallback(repo, callback, status, errorReason) {
11745 if (callback) {
11746 exceptionGuard(function () {
11747 if (status === 'ok') {
11748 callback(null);
11749 }
11750 else {
11751 var code = (status || 'error').toUpperCase();
11752 var message = code;
11753 if (errorReason) {
11754 message += ': ' + errorReason;
11755 }
11756 var error = new Error(message);
11757 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11758 error.code = code;
11759 callback(error);
11760 }
11761 });
11762 }
11763}
11764/**
11765 * Creates a new transaction, adds it to the transactions we're tracking, and
11766 * sends it to the server if possible.
11767 *
11768 * @param path - Path at which to do transaction.
11769 * @param transactionUpdate - Update callback.
11770 * @param onComplete - Completion callback.
11771 * @param unwatcher - Function that will be called when the transaction no longer
11772 * need data updates for `path`.
11773 * @param applyLocally - Whether or not to make intermediate results visible
11774 */
11775function repoStartTransaction(repo, path, transactionUpdate, onComplete, unwatcher, applyLocally) {
11776 repoLog(repo, 'transaction on ' + path);
11777 // Initialize transaction.
11778 var transaction = {
11779 path: path,
11780 update: transactionUpdate,
11781 onComplete: onComplete,
11782 // One of TransactionStatus enums.
11783 status: null,
11784 // Used when combining transactions at different locations to figure out
11785 // which one goes first.
11786 order: LUIDGenerator(),
11787 // Whether to raise local events for this transaction.
11788 applyLocally: applyLocally,
11789 // Count of how many times we've retried the transaction.
11790 retryCount: 0,
11791 // Function to call to clean up our .on() listener.
11792 unwatcher: unwatcher,
11793 // Stores why a transaction was aborted.
11794 abortReason: null,
11795 currentWriteId: null,
11796 currentInputSnapshot: null,
11797 currentOutputSnapshotRaw: null,
11798 currentOutputSnapshotResolved: null
11799 };
11800 // Run transaction initially.
11801 var currentState = repoGetLatestState(repo, path, undefined);
11802 transaction.currentInputSnapshot = currentState;
11803 var newVal = transaction.update(currentState.val());
11804 if (newVal === undefined) {
11805 // Abort transaction.
11806 transaction.unwatcher();
11807 transaction.currentOutputSnapshotRaw = null;
11808 transaction.currentOutputSnapshotResolved = null;
11809 if (transaction.onComplete) {
11810 transaction.onComplete(null, false, transaction.currentInputSnapshot);
11811 }
11812 }
11813 else {
11814 validateFirebaseData('transaction failed: Data returned ', newVal, transaction.path);
11815 // Mark as run and add to our queue.
11816 transaction.status = 0 /* RUN */;
11817 var queueNode = treeSubTree(repo.transactionQueueTree_, path);
11818 var nodeQueue = treeGetValue(queueNode) || [];
11819 nodeQueue.push(transaction);
11820 treeSetValue(queueNode, nodeQueue);
11821 // Update visibleData and raise events
11822 // Note: We intentionally raise events after updating all of our
11823 // transaction state, since the user could start new transactions from the
11824 // event callbacks.
11825 var priorityForNode = void 0;
11826 if (typeof newVal === 'object' &&
11827 newVal !== null &&
11828 contains(newVal, '.priority')) {
11829 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11830 priorityForNode = safeGet(newVal, '.priority');
11831 assert(isValidPriority(priorityForNode), 'Invalid priority returned by transaction. ' +
11832 'Priority must be a valid string, finite number, server value, or null.');
11833 }
11834 else {
11835 var currentNode = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path) ||
11836 ChildrenNode.EMPTY_NODE;
11837 priorityForNode = currentNode.getPriority().val();
11838 }
11839 var serverValues = repoGenerateServerValues(repo);
11840 var newNodeUnresolved = nodeFromJSON(newVal, priorityForNode);
11841 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, currentState, serverValues);
11842 transaction.currentOutputSnapshotRaw = newNodeUnresolved;
11843 transaction.currentOutputSnapshotResolved = newNode;
11844 transaction.currentWriteId = repoGetNextWriteId(repo);
11845 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, transaction.currentWriteId, transaction.applyLocally);
11846 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11847 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11848 }
11849}
11850/**
11851 * @param excludeSets - A specific set to exclude
11852 */
11853function repoGetLatestState(repo, path, excludeSets) {
11854 return (syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path, excludeSets) ||
11855 ChildrenNode.EMPTY_NODE);
11856}
11857/**
11858 * Sends any already-run transactions that aren't waiting for outstanding
11859 * transactions to complete.
11860 *
11861 * Externally it's called with no arguments, but it calls itself recursively
11862 * with a particular transactionQueueTree node to recurse through the tree.
11863 *
11864 * @param node - transactionQueueTree node to start at.
11865 */
11866function repoSendReadyTransactions(repo, node) {
11867 if (node === void 0) { node = repo.transactionQueueTree_; }
11868 // Before recursing, make sure any completed transactions are removed.
11869 if (!node) {
11870 repoPruneCompletedTransactionsBelowNode(repo, node);
11871 }
11872 if (treeGetValue(node)) {
11873 var queue = repoBuildTransactionQueue(repo, node);
11874 assert(queue.length > 0, 'Sending zero length transaction queue');
11875 var allRun = queue.every(function (transaction) { return transaction.status === 0 /* RUN */; });
11876 // If they're all run (and not sent), we can send them. Else, we must wait.
11877 if (allRun) {
11878 repoSendTransactionQueue(repo, treeGetPath(node), queue);
11879 }
11880 }
11881 else if (treeHasChildren(node)) {
11882 treeForEachChild(node, function (childNode) {
11883 repoSendReadyTransactions(repo, childNode);
11884 });
11885 }
11886}
11887/**
11888 * Given a list of run transactions, send them to the server and then handle
11889 * the result (success or failure).
11890 *
11891 * @param path - The location of the queue.
11892 * @param queue - Queue of transactions under the specified location.
11893 */
11894function repoSendTransactionQueue(repo, path, queue) {
11895 // Mark transactions as sent and increment retry count!
11896 var setsToIgnore = queue.map(function (txn) {
11897 return txn.currentWriteId;
11898 });
11899 var latestState = repoGetLatestState(repo, path, setsToIgnore);
11900 var snapToSend = latestState;
11901 var latestHash = latestState.hash();
11902 for (var i = 0; i < queue.length; i++) {
11903 var txn = queue[i];
11904 assert(txn.status === 0 /* RUN */, 'tryToSendTransactionQueue_: items in queue should all be run.');
11905 txn.status = 1 /* SENT */;
11906 txn.retryCount++;
11907 var relativePath = newRelativePath(path, txn.path);
11908 // If we've gotten to this point, the output snapshot must be defined.
11909 snapToSend = snapToSend.updateChild(relativePath /** @type {!Node} */, txn.currentOutputSnapshotRaw);
11910 }
11911 var dataToSend = snapToSend.val(true);
11912 var pathToSend = path;
11913 // Send the put.
11914 repo.server_.put(pathToSend.toString(), dataToSend, function (status) {
11915 repoLog(repo, 'transaction put response', {
11916 path: pathToSend.toString(),
11917 status: status
11918 });
11919 var events = [];
11920 if (status === 'ok') {
11921 // Queue up the callbacks and fire them after cleaning up all of our
11922 // transaction state, since the callback could trigger more
11923 // transactions or sets.
11924 var callbacks = [];
11925 var _loop_1 = function (i) {
11926 queue[i].status = 2 /* COMPLETED */;
11927 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId));
11928 if (queue[i].onComplete) {
11929 // We never unset the output snapshot, and given that this
11930 // transaction is complete, it should be set
11931 callbacks.push(function () {
11932 return queue[i].onComplete(null, true, queue[i].currentOutputSnapshotResolved);
11933 });
11934 }
11935 queue[i].unwatcher();
11936 };
11937 for (var i = 0; i < queue.length; i++) {
11938 _loop_1(i);
11939 }
11940 // Now remove the completed transactions.
11941 repoPruneCompletedTransactionsBelowNode(repo, treeSubTree(repo.transactionQueueTree_, path));
11942 // There may be pending transactions that we can now send.
11943 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11944 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11945 // Finally, trigger onComplete callbacks.
11946 for (var i = 0; i < callbacks.length; i++) {
11947 exceptionGuard(callbacks[i]);
11948 }
11949 }
11950 else {
11951 // transactions are no longer sent. Update their status appropriately.
11952 if (status === 'datastale') {
11953 for (var i = 0; i < queue.length; i++) {
11954 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) {
11955 queue[i].status = 4 /* NEEDS_ABORT */;
11956 }
11957 else {
11958 queue[i].status = 0 /* RUN */;
11959 }
11960 }
11961 }
11962 else {
11963 warn('transaction at ' + pathToSend.toString() + ' failed: ' + status);
11964 for (var i = 0; i < queue.length; i++) {
11965 queue[i].status = 4 /* NEEDS_ABORT */;
11966 queue[i].abortReason = status;
11967 }
11968 }
11969 repoRerunTransactions(repo, path);
11970 }
11971 }, latestHash);
11972}
11973/**
11974 * Finds all transactions dependent on the data at changedPath and reruns them.
11975 *
11976 * Should be called any time cached data changes.
11977 *
11978 * Return the highest path that was affected by rerunning transactions. This
11979 * is the path at which events need to be raised for.
11980 *
11981 * @param changedPath - The path in mergedData that changed.
11982 * @returns The rootmost path that was affected by rerunning transactions.
11983 */
11984function repoRerunTransactions(repo, changedPath) {
11985 var rootMostTransactionNode = repoGetAncestorTransactionNode(repo, changedPath);
11986 var path = treeGetPath(rootMostTransactionNode);
11987 var queue = repoBuildTransactionQueue(repo, rootMostTransactionNode);
11988 repoRerunTransactionQueue(repo, queue, path);
11989 return path;
11990}
11991/**
11992 * Does all the work of rerunning transactions (as well as cleans up aborted
11993 * transactions and whatnot).
11994 *
11995 * @param queue - The queue of transactions to run.
11996 * @param path - The path the queue is for.
11997 */
11998function repoRerunTransactionQueue(repo, queue, path) {
11999 if (queue.length === 0) {
12000 return; // Nothing to do!
12001 }
12002 // Queue up the callbacks and fire them after cleaning up all of our
12003 // transaction state, since the callback could trigger more transactions or
12004 // sets.
12005 var callbacks = [];
12006 var events = [];
12007 // Ignore all of the sets we're going to re-run.
12008 var txnsToRerun = queue.filter(function (q) {
12009 return q.status === 0 /* RUN */;
12010 });
12011 var setsToIgnore = txnsToRerun.map(function (q) {
12012 return q.currentWriteId;
12013 });
12014 var _loop_2 = function (i) {
12015 var transaction = queue[i];
12016 var relativePath = newRelativePath(path, transaction.path);
12017 var abortTransaction = false, abortReason;
12018 assert(relativePath !== null, 'rerunTransactionsUnderNode_: relativePath should not be null.');
12019 if (transaction.status === 4 /* NEEDS_ABORT */) {
12020 abortTransaction = true;
12021 abortReason = transaction.abortReason;
12022 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12023 }
12024 else if (transaction.status === 0 /* RUN */) {
12025 if (transaction.retryCount >= MAX_TRANSACTION_RETRIES) {
12026 abortTransaction = true;
12027 abortReason = 'maxretry';
12028 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12029 }
12030 else {
12031 // This code reruns a transaction
12032 var currentNode = repoGetLatestState(repo, transaction.path, setsToIgnore);
12033 transaction.currentInputSnapshot = currentNode;
12034 var newData = queue[i].update(currentNode.val());
12035 if (newData !== undefined) {
12036 validateFirebaseData('transaction failed: Data returned ', newData, transaction.path);
12037 var newDataNode = nodeFromJSON(newData);
12038 var hasExplicitPriority = typeof newData === 'object' &&
12039 newData != null &&
12040 contains(newData, '.priority');
12041 if (!hasExplicitPriority) {
12042 // Keep the old priority if there wasn't a priority explicitly specified.
12043 newDataNode = newDataNode.updatePriority(currentNode.getPriority());
12044 }
12045 var oldWriteId = transaction.currentWriteId;
12046 var serverValues = repoGenerateServerValues(repo);
12047 var newNodeResolved = resolveDeferredValueSnapshot(newDataNode, currentNode, serverValues);
12048 transaction.currentOutputSnapshotRaw = newDataNode;
12049 transaction.currentOutputSnapshotResolved = newNodeResolved;
12050 transaction.currentWriteId = repoGetNextWriteId(repo);
12051 // Mutates setsToIgnore in place
12052 setsToIgnore.splice(setsToIgnore.indexOf(oldWriteId), 1);
12053 events = events.concat(syncTreeApplyUserOverwrite(repo.serverSyncTree_, transaction.path, newNodeResolved, transaction.currentWriteId, transaction.applyLocally));
12054 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, oldWriteId, true));
12055 }
12056 else {
12057 abortTransaction = true;
12058 abortReason = 'nodata';
12059 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
12060 }
12061 }
12062 }
12063 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
12064 events = [];
12065 if (abortTransaction) {
12066 // Abort.
12067 queue[i].status = 2 /* COMPLETED */;
12068 // Removing a listener can trigger pruning which can muck with
12069 // mergedData/visibleData (as it prunes data). So defer the unwatcher
12070 // until we're done.
12071 (function (unwatcher) {
12072 setTimeout(unwatcher, Math.floor(0));
12073 })(queue[i].unwatcher);
12074 if (queue[i].onComplete) {
12075 if (abortReason === 'nodata') {
12076 callbacks.push(function () {
12077 return queue[i].onComplete(null, false, queue[i].currentInputSnapshot);
12078 });
12079 }
12080 else {
12081 callbacks.push(function () {
12082 return queue[i].onComplete(new Error(abortReason), false, null);
12083 });
12084 }
12085 }
12086 }
12087 };
12088 for (var i = 0; i < queue.length; i++) {
12089 _loop_2(i);
12090 }
12091 // Clean up completed transactions.
12092 repoPruneCompletedTransactionsBelowNode(repo, repo.transactionQueueTree_);
12093 // Now fire callbacks, now that we're in a good, known state.
12094 for (var i = 0; i < callbacks.length; i++) {
12095 exceptionGuard(callbacks[i]);
12096 }
12097 // Try to send the transaction result to the server.
12098 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
12099}
12100/**
12101 * Returns the rootmost ancestor node of the specified path that has a pending
12102 * transaction on it, or just returns the node for the given path if there are
12103 * no pending transactions on any ancestor.
12104 *
12105 * @param path - The location to start at.
12106 * @returns The rootmost node with a transaction.
12107 */
12108function repoGetAncestorTransactionNode(repo, path) {
12109 var front;
12110 // Start at the root and walk deeper into the tree towards path until we
12111 // find a node with pending transactions.
12112 var transactionNode = repo.transactionQueueTree_;
12113 front = pathGetFront(path);
12114 while (front !== null && treeGetValue(transactionNode) === undefined) {
12115 transactionNode = treeSubTree(transactionNode, front);
12116 path = pathPopFront(path);
12117 front = pathGetFront(path);
12118 }
12119 return transactionNode;
12120}
12121/**
12122 * Builds the queue of all transactions at or below the specified
12123 * transactionNode.
12124 *
12125 * @param transactionNode
12126 * @returns The generated queue.
12127 */
12128function repoBuildTransactionQueue(repo, transactionNode) {
12129 // Walk any child transaction queues and aggregate them into a single queue.
12130 var transactionQueue = [];
12131 repoAggregateTransactionQueuesForNode(repo, transactionNode, transactionQueue);
12132 // Sort them by the order the transactions were created.
12133 transactionQueue.sort(function (a, b) { return a.order - b.order; });
12134 return transactionQueue;
12135}
12136function repoAggregateTransactionQueuesForNode(repo, node, queue) {
12137 var nodeQueue = treeGetValue(node);
12138 if (nodeQueue) {
12139 for (var i = 0; i < nodeQueue.length; i++) {
12140 queue.push(nodeQueue[i]);
12141 }
12142 }
12143 treeForEachChild(node, function (child) {
12144 repoAggregateTransactionQueuesForNode(repo, child, queue);
12145 });
12146}
12147/**
12148 * Remove COMPLETED transactions at or below this node in the transactionQueueTree_.
12149 */
12150function repoPruneCompletedTransactionsBelowNode(repo, node) {
12151 var queue = treeGetValue(node);
12152 if (queue) {
12153 var to = 0;
12154 for (var from = 0; from < queue.length; from++) {
12155 if (queue[from].status !== 2 /* COMPLETED */) {
12156 queue[to] = queue[from];
12157 to++;
12158 }
12159 }
12160 queue.length = to;
12161 treeSetValue(node, queue.length > 0 ? queue : undefined);
12162 }
12163 treeForEachChild(node, function (childNode) {
12164 repoPruneCompletedTransactionsBelowNode(repo, childNode);
12165 });
12166}
12167/**
12168 * Aborts all transactions on ancestors or descendants of the specified path.
12169 * Called when doing a set() or update() since we consider them incompatible
12170 * with transactions.
12171 *
12172 * @param path - Path for which we want to abort related transactions.
12173 */
12174function repoAbortTransactions(repo, path) {
12175 var affectedPath = treeGetPath(repoGetAncestorTransactionNode(repo, path));
12176 var transactionNode = treeSubTree(repo.transactionQueueTree_, path);
12177 treeForEachAncestor(transactionNode, function (node) {
12178 repoAbortTransactionsOnNode(repo, node);
12179 });
12180 repoAbortTransactionsOnNode(repo, transactionNode);
12181 treeForEachDescendant(transactionNode, function (node) {
12182 repoAbortTransactionsOnNode(repo, node);
12183 });
12184 return affectedPath;
12185}
12186/**
12187 * Abort transactions stored in this transaction queue node.
12188 *
12189 * @param node - Node to abort transactions for.
12190 */
12191function repoAbortTransactionsOnNode(repo, node) {
12192 var queue = treeGetValue(node);
12193 if (queue) {
12194 // Queue up the callbacks and fire them after cleaning up all of our
12195 // transaction state, since the callback could trigger more transactions
12196 // or sets.
12197 var callbacks = [];
12198 // Go through queue. Any already-sent transactions must be marked for
12199 // abort, while the unsent ones can be immediately aborted and removed.
12200 var events = [];
12201 var lastSent = -1;
12202 for (var i = 0; i < queue.length; i++) {
12203 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) ;
12204 else if (queue[i].status === 1 /* SENT */) {
12205 assert(lastSent === i - 1, 'All SENT items should be at beginning of queue.');
12206 lastSent = i;
12207 // Mark transaction for abort when it comes back.
12208 queue[i].status = 3 /* SENT_NEEDS_ABORT */;
12209 queue[i].abortReason = 'set';
12210 }
12211 else {
12212 assert(queue[i].status === 0 /* RUN */, 'Unexpected transaction status in abort');
12213 // We can abort it immediately.
12214 queue[i].unwatcher();
12215 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId, true));
12216 if (queue[i].onComplete) {
12217 callbacks.push(queue[i].onComplete.bind(null, new Error('set'), false, null));
12218 }
12219 }
12220 }
12221 if (lastSent === -1) {
12222 // We're not waiting for any sent transactions. We can clear the queue.
12223 treeSetValue(node, undefined);
12224 }
12225 else {
12226 // Remove the transactions we aborted.
12227 queue.length = lastSent + 1;
12228 }
12229 // Now fire the callbacks.
12230 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, treeGetPath(node), events);
12231 for (var i = 0; i < callbacks.length; i++) {
12232 exceptionGuard(callbacks[i]);
12233 }
12234 }
12235}
12236
12237/**
12238 * @license
12239 * Copyright 2017 Google LLC
12240 *
12241 * Licensed under the Apache License, Version 2.0 (the "License");
12242 * you may not use this file except in compliance with the License.
12243 * You may obtain a copy of the License at
12244 *
12245 * http://www.apache.org/licenses/LICENSE-2.0
12246 *
12247 * Unless required by applicable law or agreed to in writing, software
12248 * distributed under the License is distributed on an "AS IS" BASIS,
12249 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12250 * See the License for the specific language governing permissions and
12251 * limitations under the License.
12252 */
12253function decodePath(pathString) {
12254 var pathStringDecoded = '';
12255 var pieces = pathString.split('/');
12256 for (var i = 0; i < pieces.length; i++) {
12257 if (pieces[i].length > 0) {
12258 var piece = pieces[i];
12259 try {
12260 piece = decodeURIComponent(piece.replace(/\+/g, ' '));
12261 }
12262 catch (e) { }
12263 pathStringDecoded += '/' + piece;
12264 }
12265 }
12266 return pathStringDecoded;
12267}
12268/**
12269 * @returns key value hash
12270 */
12271function decodeQuery(queryString) {
12272 var e_1, _a;
12273 var results = {};
12274 if (queryString.charAt(0) === '?') {
12275 queryString = queryString.substring(1);
12276 }
12277 try {
12278 for (var _b = __values(queryString.split('&')), _c = _b.next(); !_c.done; _c = _b.next()) {
12279 var segment = _c.value;
12280 if (segment.length === 0) {
12281 continue;
12282 }
12283 var kv = segment.split('=');
12284 if (kv.length === 2) {
12285 results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
12286 }
12287 else {
12288 warn("Invalid query segment '" + segment + "' in query '" + queryString + "'");
12289 }
12290 }
12291 }
12292 catch (e_1_1) { e_1 = { error: e_1_1 }; }
12293 finally {
12294 try {
12295 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
12296 }
12297 finally { if (e_1) throw e_1.error; }
12298 }
12299 return results;
12300}
12301var parseRepoInfo = function (dataURL, nodeAdmin) {
12302 var parsedUrl = parseDatabaseURL(dataURL), namespace = parsedUrl.namespace;
12303 if (parsedUrl.domain === 'firebase.com') {
12304 fatal(parsedUrl.host +
12305 ' is no longer supported. ' +
12306 'Please use <YOUR FIREBASE>.firebaseio.com instead');
12307 }
12308 // Catch common error of uninitialized namespace value.
12309 if ((!namespace || namespace === 'undefined') &&
12310 parsedUrl.domain !== 'localhost') {
12311 fatal('Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com');
12312 }
12313 if (!parsedUrl.secure) {
12314 warnIfPageIsSecure();
12315 }
12316 var webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
12317 return {
12318 repoInfo: new RepoInfo(parsedUrl.host, parsedUrl.secure, namespace, webSocketOnly, nodeAdmin,
12319 /*persistenceKey=*/ '',
12320 /*includeNamespaceInQueryParams=*/ namespace !== parsedUrl.subdomain),
12321 path: new Path(parsedUrl.pathString)
12322 };
12323};
12324var parseDatabaseURL = function (dataURL) {
12325 // Default to empty strings in the event of a malformed string.
12326 var host = '', domain = '', subdomain = '', pathString = '', namespace = '';
12327 // Always default to SSL, unless otherwise specified.
12328 var secure = true, scheme = 'https', port = 443;
12329 // Don't do any validation here. The caller is responsible for validating the result of parsing.
12330 if (typeof dataURL === 'string') {
12331 // Parse scheme.
12332 var colonInd = dataURL.indexOf('//');
12333 if (colonInd >= 0) {
12334 scheme = dataURL.substring(0, colonInd - 1);
12335 dataURL = dataURL.substring(colonInd + 2);
12336 }
12337 // Parse host, path, and query string.
12338 var slashInd = dataURL.indexOf('/');
12339 if (slashInd === -1) {
12340 slashInd = dataURL.length;
12341 }
12342 var questionMarkInd = dataURL.indexOf('?');
12343 if (questionMarkInd === -1) {
12344 questionMarkInd = dataURL.length;
12345 }
12346 host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
12347 if (slashInd < questionMarkInd) {
12348 // For pathString, questionMarkInd will always come after slashInd
12349 pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
12350 }
12351 var queryParams = decodeQuery(dataURL.substring(Math.min(dataURL.length, questionMarkInd)));
12352 // If we have a port, use scheme for determining if it's secure.
12353 colonInd = host.indexOf(':');
12354 if (colonInd >= 0) {
12355 secure = scheme === 'https' || scheme === 'wss';
12356 port = parseInt(host.substring(colonInd + 1), 10);
12357 }
12358 else {
12359 colonInd = host.length;
12360 }
12361 var hostWithoutPort = host.slice(0, colonInd);
12362 if (hostWithoutPort.toLowerCase() === 'localhost') {
12363 domain = 'localhost';
12364 }
12365 else if (hostWithoutPort.split('.').length <= 2) {
12366 domain = hostWithoutPort;
12367 }
12368 else {
12369 // Interpret the subdomain of a 3 or more component URL as the namespace name.
12370 var dotInd = host.indexOf('.');
12371 subdomain = host.substring(0, dotInd).toLowerCase();
12372 domain = host.substring(dotInd + 1);
12373 // Normalize namespaces to lowercase to share storage / connection.
12374 namespace = subdomain;
12375 }
12376 // Always treat the value of the `ns` as the namespace name if it is present.
12377 if ('ns' in queryParams) {
12378 namespace = queryParams['ns'];
12379 }
12380 }
12381 return {
12382 host: host,
12383 port: port,
12384 domain: domain,
12385 subdomain: subdomain,
12386 secure: secure,
12387 scheme: scheme,
12388 pathString: pathString,
12389 namespace: namespace
12390 };
12391};
12392
12393/**
12394 * @license
12395 * Copyright 2017 Google LLC
12396 *
12397 * Licensed under the Apache License, Version 2.0 (the "License");
12398 * you may not use this file except in compliance with the License.
12399 * You may obtain a copy of the License at
12400 *
12401 * http://www.apache.org/licenses/LICENSE-2.0
12402 *
12403 * Unless required by applicable law or agreed to in writing, software
12404 * distributed under the License is distributed on an "AS IS" BASIS,
12405 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12406 * See the License for the specific language governing permissions and
12407 * limitations under the License.
12408 */
12409/**
12410 * Encapsulates the data needed to raise an event
12411 */
12412var DataEvent = /** @class */ (function () {
12413 /**
12414 * @param eventType - One of: value, child_added, child_changed, child_moved, child_removed
12415 * @param eventRegistration - The function to call to with the event data. User provided
12416 * @param snapshot - The data backing the event
12417 * @param prevName - Optional, the name of the previous child for child_* events.
12418 */
12419 function DataEvent(eventType, eventRegistration, snapshot, prevName) {
12420 this.eventType = eventType;
12421 this.eventRegistration = eventRegistration;
12422 this.snapshot = snapshot;
12423 this.prevName = prevName;
12424 }
12425 DataEvent.prototype.getPath = function () {
12426 var ref = this.snapshot.ref;
12427 if (this.eventType === 'value') {
12428 return ref._path;
12429 }
12430 else {
12431 return ref.parent._path;
12432 }
12433 };
12434 DataEvent.prototype.getEventType = function () {
12435 return this.eventType;
12436 };
12437 DataEvent.prototype.getEventRunner = function () {
12438 return this.eventRegistration.getEventRunner(this);
12439 };
12440 DataEvent.prototype.toString = function () {
12441 return (this.getPath().toString() +
12442 ':' +
12443 this.eventType +
12444 ':' +
12445 stringify(this.snapshot.exportVal()));
12446 };
12447 return DataEvent;
12448}());
12449var CancelEvent = /** @class */ (function () {
12450 function CancelEvent(eventRegistration, error, path) {
12451 this.eventRegistration = eventRegistration;
12452 this.error = error;
12453 this.path = path;
12454 }
12455 CancelEvent.prototype.getPath = function () {
12456 return this.path;
12457 };
12458 CancelEvent.prototype.getEventType = function () {
12459 return 'cancel';
12460 };
12461 CancelEvent.prototype.getEventRunner = function () {
12462 return this.eventRegistration.getEventRunner(this);
12463 };
12464 CancelEvent.prototype.toString = function () {
12465 return this.path.toString() + ':cancel';
12466 };
12467 return CancelEvent;
12468}());
12469
12470/**
12471 * @license
12472 * Copyright 2017 Google LLC
12473 *
12474 * Licensed under the Apache License, Version 2.0 (the "License");
12475 * you may not use this file except in compliance with the License.
12476 * You may obtain a copy of the License at
12477 *
12478 * http://www.apache.org/licenses/LICENSE-2.0
12479 *
12480 * Unless required by applicable law or agreed to in writing, software
12481 * distributed under the License is distributed on an "AS IS" BASIS,
12482 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12483 * See the License for the specific language governing permissions and
12484 * limitations under the License.
12485 */
12486/**
12487 * A wrapper class that converts events from the database@exp SDK to the legacy
12488 * Database SDK. Events are not converted directly as event registration relies
12489 * on reference comparison of the original user callback (see `matches()`) and
12490 * relies on equality of the legacy SDK's `context` object.
12491 */
12492var CallbackContext = /** @class */ (function () {
12493 function CallbackContext(snapshotCallback, cancelCallback) {
12494 this.snapshotCallback = snapshotCallback;
12495 this.cancelCallback = cancelCallback;
12496 }
12497 CallbackContext.prototype.onValue = function (expDataSnapshot, previousChildName) {
12498 this.snapshotCallback.call(null, expDataSnapshot, previousChildName);
12499 };
12500 CallbackContext.prototype.onCancel = function (error) {
12501 assert(this.hasCancelCallback, 'Raising a cancel event on a listener with no cancel callback');
12502 return this.cancelCallback.call(null, error);
12503 };
12504 Object.defineProperty(CallbackContext.prototype, "hasCancelCallback", {
12505 get: function () {
12506 return !!this.cancelCallback;
12507 },
12508 enumerable: false,
12509 configurable: true
12510 });
12511 CallbackContext.prototype.matches = function (other) {
12512 return (this.snapshotCallback === other.snapshotCallback ||
12513 (this.snapshotCallback.userCallback !== undefined &&
12514 this.snapshotCallback.userCallback ===
12515 other.snapshotCallback.userCallback &&
12516 this.snapshotCallback.context === other.snapshotCallback.context));
12517 };
12518 return CallbackContext;
12519}());
12520
12521/**
12522 * @license
12523 * Copyright 2021 Google LLC
12524 *
12525 * Licensed under the Apache License, Version 2.0 (the "License");
12526 * you may not use this file except in compliance with the License.
12527 * You may obtain a copy of the License at
12528 *
12529 * http://www.apache.org/licenses/LICENSE-2.0
12530 *
12531 * Unless required by applicable law or agreed to in writing, software
12532 * distributed under the License is distributed on an "AS IS" BASIS,
12533 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12534 * See the License for the specific language governing permissions and
12535 * limitations under the License.
12536 */
12537/**
12538 * The `onDisconnect` class allows you to write or clear data when your client
12539 * disconnects from the Database server. These updates occur whether your
12540 * client disconnects cleanly or not, so you can rely on them to clean up data
12541 * even if a connection is dropped or a client crashes.
12542 *
12543 * The `onDisconnect` class is most commonly used to manage presence in
12544 * applications where it is useful to detect how many clients are connected and
12545 * when other clients disconnect. See
12546 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12547 * for more information.
12548 *
12549 * To avoid problems when a connection is dropped before the requests can be
12550 * transferred to the Database server, these functions should be called before
12551 * writing any data.
12552 *
12553 * Note that `onDisconnect` operations are only triggered once. If you want an
12554 * operation to occur each time a disconnect occurs, you'll need to re-establish
12555 * the `onDisconnect` operations each time you reconnect.
12556 */
12557var OnDisconnect = /** @class */ (function () {
12558 /** @hideconstructor */
12559 function OnDisconnect(_repo, _path) {
12560 this._repo = _repo;
12561 this._path = _path;
12562 }
12563 /**
12564 * Cancels all previously queued `onDisconnect()` set or update events for this
12565 * location and all children.
12566 *
12567 * If a write has been queued for this location via a `set()` or `update()` at a
12568 * parent location, the write at this location will be canceled, though writes
12569 * to sibling locations will still occur.
12570 *
12571 * @returns Resolves when synchronization to the server is complete.
12572 */
12573 OnDisconnect.prototype.cancel = function () {
12574 var deferred = new Deferred();
12575 repoOnDisconnectCancel(this._repo, this._path, deferred.wrapCallback(function () { }));
12576 return deferred.promise;
12577 };
12578 /**
12579 * Ensures the data at this location is deleted when the client is disconnected
12580 * (due to closing the browser, navigating to a new page, or network issues).
12581 *
12582 * @returns Resolves when synchronization to the server is complete.
12583 */
12584 OnDisconnect.prototype.remove = function () {
12585 validateWritablePath('OnDisconnect.remove', this._path);
12586 var deferred = new Deferred();
12587 repoOnDisconnectSet(this._repo, this._path, null, deferred.wrapCallback(function () { }));
12588 return deferred.promise;
12589 };
12590 /**
12591 * Ensures the data at this location is set to the specified value when the
12592 * client is disconnected (due to closing the browser, navigating to a new page,
12593 * or network issues).
12594 *
12595 * `set()` is especially useful for implementing "presence" systems, where a
12596 * value should be changed or cleared when a user disconnects so that they
12597 * appear "offline" to other users. See
12598 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12599 * for more information.
12600 *
12601 * Note that `onDisconnect` operations are only triggered once. If you want an
12602 * operation to occur each time a disconnect occurs, you'll need to re-establish
12603 * the `onDisconnect` operations each time.
12604 *
12605 * @param value - The value to be written to this location on disconnect (can
12606 * be an object, array, string, number, boolean, or null).
12607 * @returns Resolves when synchronization to the Database is complete.
12608 */
12609 OnDisconnect.prototype.set = function (value) {
12610 validateWritablePath('OnDisconnect.set', this._path);
12611 validateFirebaseDataArg('OnDisconnect.set', value, this._path, false);
12612 var deferred = new Deferred();
12613 repoOnDisconnectSet(this._repo, this._path, value, deferred.wrapCallback(function () { }));
12614 return deferred.promise;
12615 };
12616 /**
12617 * Ensures the data at this location is set to the specified value and priority
12618 * when the client is disconnected (due to closing the browser, navigating to a
12619 * new page, or network issues).
12620 *
12621 * @param value - The value to be written to this location on disconnect (can
12622 * be an object, array, string, number, boolean, or null).
12623 * @param priority - The priority to be written (string, number, or null).
12624 * @returns Resolves when synchronization to the Database is complete.
12625 */
12626 OnDisconnect.prototype.setWithPriority = function (value, priority) {
12627 validateWritablePath('OnDisconnect.setWithPriority', this._path);
12628 validateFirebaseDataArg('OnDisconnect.setWithPriority', value, this._path, false);
12629 validatePriority('OnDisconnect.setWithPriority', priority, false);
12630 var deferred = new Deferred();
12631 repoOnDisconnectSetWithPriority(this._repo, this._path, value, priority, deferred.wrapCallback(function () { }));
12632 return deferred.promise;
12633 };
12634 /**
12635 * Writes multiple values at this location when the client is disconnected (due
12636 * to closing the browser, navigating to a new page, or network issues).
12637 *
12638 * The `values` argument contains multiple property-value pairs that will be
12639 * written to the Database together. Each child property can either be a simple
12640 * property (for example, "name") or a relative path (for example, "name/first")
12641 * from the current location to the data to update.
12642 *
12643 * As opposed to the `set()` method, `update()` can be use to selectively update
12644 * only the referenced properties at the current location (instead of replacing
12645 * all the child properties at the current location).
12646 *
12647 * @param values - Object containing multiple values.
12648 * @returns Resolves when synchronization to the Database is complete.
12649 */
12650 OnDisconnect.prototype.update = function (values) {
12651 validateWritablePath('OnDisconnect.update', this._path);
12652 validateFirebaseMergeDataArg('OnDisconnect.update', values, this._path, false);
12653 var deferred = new Deferred();
12654 repoOnDisconnectUpdate(this._repo, this._path, values, deferred.wrapCallback(function () { }));
12655 return deferred.promise;
12656 };
12657 return OnDisconnect;
12658}());
12659
12660/**
12661 * @license
12662 * Copyright 2020 Google LLC
12663 *
12664 * Licensed under the Apache License, Version 2.0 (the "License");
12665 * you may not use this file except in compliance with the License.
12666 * You may obtain a copy of the License at
12667 *
12668 * http://www.apache.org/licenses/LICENSE-2.0
12669 *
12670 * Unless required by applicable law or agreed to in writing, software
12671 * distributed under the License is distributed on an "AS IS" BASIS,
12672 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12673 * See the License for the specific language governing permissions and
12674 * limitations under the License.
12675 */
12676/**
12677 * @internal
12678 */
12679var QueryImpl = /** @class */ (function () {
12680 /**
12681 * @hideconstructor
12682 */
12683 function QueryImpl(_repo, _path, _queryParams, _orderByCalled) {
12684 this._repo = _repo;
12685 this._path = _path;
12686 this._queryParams = _queryParams;
12687 this._orderByCalled = _orderByCalled;
12688 }
12689 Object.defineProperty(QueryImpl.prototype, "key", {
12690 get: function () {
12691 if (pathIsEmpty(this._path)) {
12692 return null;
12693 }
12694 else {
12695 return pathGetBack(this._path);
12696 }
12697 },
12698 enumerable: false,
12699 configurable: true
12700 });
12701 Object.defineProperty(QueryImpl.prototype, "ref", {
12702 get: function () {
12703 return new ReferenceImpl(this._repo, this._path);
12704 },
12705 enumerable: false,
12706 configurable: true
12707 });
12708 Object.defineProperty(QueryImpl.prototype, "_queryIdentifier", {
12709 get: function () {
12710 var obj = queryParamsGetQueryObject(this._queryParams);
12711 var id = ObjectToUniqueKey(obj);
12712 return id === '{}' ? 'default' : id;
12713 },
12714 enumerable: false,
12715 configurable: true
12716 });
12717 Object.defineProperty(QueryImpl.prototype, "_queryObject", {
12718 /**
12719 * An object representation of the query parameters used by this Query.
12720 */
12721 get: function () {
12722 return queryParamsGetQueryObject(this._queryParams);
12723 },
12724 enumerable: false,
12725 configurable: true
12726 });
12727 QueryImpl.prototype.isEqual = function (other) {
12728 other = getModularInstance(other);
12729 if (!(other instanceof QueryImpl)) {
12730 return false;
12731 }
12732 var sameRepo = this._repo === other._repo;
12733 var samePath = pathEquals(this._path, other._path);
12734 var sameQueryIdentifier = this._queryIdentifier === other._queryIdentifier;
12735 return sameRepo && samePath && sameQueryIdentifier;
12736 };
12737 QueryImpl.prototype.toJSON = function () {
12738 return this.toString();
12739 };
12740 QueryImpl.prototype.toString = function () {
12741 return this._repo.toString() + pathToUrlEncodedString(this._path);
12742 };
12743 return QueryImpl;
12744}());
12745/**
12746 * Validates that no other order by call has been made
12747 */
12748function validateNoPreviousOrderByCall(query, fnName) {
12749 if (query._orderByCalled === true) {
12750 throw new Error(fnName + ": You can't combine multiple orderBy calls.");
12751 }
12752}
12753/**
12754 * Validates start/end values for queries.
12755 */
12756function validateQueryEndpoints(params) {
12757 var startNode = null;
12758 var endNode = null;
12759 if (params.hasStart()) {
12760 startNode = params.getIndexStartValue();
12761 }
12762 if (params.hasEnd()) {
12763 endNode = params.getIndexEndValue();
12764 }
12765 if (params.getIndex() === KEY_INDEX) {
12766 var tooManyArgsError = 'Query: When ordering by key, you may only pass one argument to ' +
12767 'startAt(), endAt(), or equalTo().';
12768 var wrongArgTypeError = 'Query: When ordering by key, the argument passed to startAt(), startAfter(), ' +
12769 'endAt(), endBefore(), or equalTo() must be a string.';
12770 if (params.hasStart()) {
12771 var startName = params.getIndexStartName();
12772 if (startName !== MIN_NAME) {
12773 throw new Error(tooManyArgsError);
12774 }
12775 else if (typeof startNode !== 'string') {
12776 throw new Error(wrongArgTypeError);
12777 }
12778 }
12779 if (params.hasEnd()) {
12780 var endName = params.getIndexEndName();
12781 if (endName !== MAX_NAME) {
12782 throw new Error(tooManyArgsError);
12783 }
12784 else if (typeof endNode !== 'string') {
12785 throw new Error(wrongArgTypeError);
12786 }
12787 }
12788 }
12789 else if (params.getIndex() === PRIORITY_INDEX) {
12790 if ((startNode != null && !isValidPriority(startNode)) ||
12791 (endNode != null && !isValidPriority(endNode))) {
12792 throw new Error('Query: When ordering by priority, the first argument passed to startAt(), ' +
12793 'startAfter() endAt(), endBefore(), or equalTo() must be a valid priority value ' +
12794 '(null, a number, or a string).');
12795 }
12796 }
12797 else {
12798 assert(params.getIndex() instanceof PathIndex ||
12799 params.getIndex() === VALUE_INDEX, 'unknown index type.');
12800 if ((startNode != null && typeof startNode === 'object') ||
12801 (endNode != null && typeof endNode === 'object')) {
12802 throw new Error('Query: First argument passed to startAt(), startAfter(), endAt(), endBefore(), or ' +
12803 'equalTo() cannot be an object.');
12804 }
12805 }
12806}
12807/**
12808 * Validates that limit* has been called with the correct combination of parameters
12809 */
12810function validateLimit(params) {
12811 if (params.hasStart() &&
12812 params.hasEnd() &&
12813 params.hasLimit() &&
12814 !params.hasAnchoredLimit()) {
12815 throw new Error("Query: Can't combine startAt(), startAfter(), endAt(), endBefore(), and limit(). Use " +
12816 'limitToFirst() or limitToLast() instead.');
12817 }
12818}
12819/**
12820 * @internal
12821 */
12822var ReferenceImpl = /** @class */ (function (_super) {
12823 __extends(ReferenceImpl, _super);
12824 /** @hideconstructor */
12825 function ReferenceImpl(repo, path) {
12826 return _super.call(this, repo, path, new QueryParams(), false) || this;
12827 }
12828 Object.defineProperty(ReferenceImpl.prototype, "parent", {
12829 get: function () {
12830 var parentPath = pathParent(this._path);
12831 return parentPath === null
12832 ? null
12833 : new ReferenceImpl(this._repo, parentPath);
12834 },
12835 enumerable: false,
12836 configurable: true
12837 });
12838 Object.defineProperty(ReferenceImpl.prototype, "root", {
12839 get: function () {
12840 var ref = this;
12841 while (ref.parent !== null) {
12842 ref = ref.parent;
12843 }
12844 return ref;
12845 },
12846 enumerable: false,
12847 configurable: true
12848 });
12849 return ReferenceImpl;
12850}(QueryImpl));
12851/**
12852 * A `DataSnapshot` contains data from a Database location.
12853 *
12854 * Any time you read data from the Database, you receive the data as a
12855 * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach
12856 * with `on()` or `once()`. You can extract the contents of the snapshot as a
12857 * JavaScript object by calling the `val()` method. Alternatively, you can
12858 * traverse into the snapshot by calling `child()` to return child snapshots
12859 * (which you could then call `val()` on).
12860 *
12861 * A `DataSnapshot` is an efficiently generated, immutable copy of the data at
12862 * a Database location. It cannot be modified and will never change (to modify
12863 * data, you always call the `set()` method on a `Reference` directly).
12864 */
12865var DataSnapshot = /** @class */ (function () {
12866 /**
12867 * @param _node - A SnapshotNode to wrap.
12868 * @param ref - The location this snapshot came from.
12869 * @param _index - The iteration order for this snapshot
12870 * @hideconstructor
12871 */
12872 function DataSnapshot(_node,
12873 /**
12874 * The location of this DataSnapshot.
12875 */
12876 ref, _index) {
12877 this._node = _node;
12878 this.ref = ref;
12879 this._index = _index;
12880 }
12881 Object.defineProperty(DataSnapshot.prototype, "priority", {
12882 /**
12883 * Gets the priority value of the data in this `DataSnapshot`.
12884 *
12885 * Applications need not use priority but can order collections by
12886 * ordinary properties (see
12887 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data |Sorting and filtering data}
12888 * ).
12889 */
12890 get: function () {
12891 // typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
12892 return this._node.getPriority().val();
12893 },
12894 enumerable: false,
12895 configurable: true
12896 });
12897 Object.defineProperty(DataSnapshot.prototype, "key", {
12898 /**
12899 * The key (last part of the path) of the location of this `DataSnapshot`.
12900 *
12901 * The last token in a Database location is considered its key. For example,
12902 * "ada" is the key for the /users/ada/ node. Accessing the key on any
12903 * `DataSnapshot` will return the key for the location that generated it.
12904 * However, accessing the key on the root URL of a Database will return
12905 * `null`.
12906 */
12907 get: function () {
12908 return this.ref.key;
12909 },
12910 enumerable: false,
12911 configurable: true
12912 });
12913 Object.defineProperty(DataSnapshot.prototype, "size", {
12914 /** Returns the number of child properties of this `DataSnapshot`. */
12915 get: function () {
12916 return this._node.numChildren();
12917 },
12918 enumerable: false,
12919 configurable: true
12920 });
12921 /**
12922 * Gets another `DataSnapshot` for the location at the specified relative path.
12923 *
12924 * Passing a relative path to the `child()` method of a DataSnapshot returns
12925 * another `DataSnapshot` for the location at the specified relative path. The
12926 * relative path can either be a simple child name (for example, "ada") or a
12927 * deeper, slash-separated path (for example, "ada/name/first"). If the child
12928 * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot`
12929 * whose value is `null`) is returned.
12930 *
12931 * @param path - A relative path to the location of child data.
12932 */
12933 DataSnapshot.prototype.child = function (path) {
12934 var childPath = new Path(path);
12935 var childRef = child(this.ref, path);
12936 return new DataSnapshot(this._node.getChild(childPath), childRef, PRIORITY_INDEX);
12937 };
12938 /**
12939 * Returns true if this `DataSnapshot` contains any data. It is slightly more
12940 * efficient than using `snapshot.val() !== null`.
12941 */
12942 DataSnapshot.prototype.exists = function () {
12943 return !this._node.isEmpty();
12944 };
12945 /**
12946 * Exports the entire contents of the DataSnapshot as a JavaScript object.
12947 *
12948 * The `exportVal()` method is similar to `val()`, except priority information
12949 * is included (if available), making it suitable for backing up your data.
12950 *
12951 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12952 * Array, string, number, boolean, or `null`).
12953 */
12954 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12955 DataSnapshot.prototype.exportVal = function () {
12956 return this._node.val(true);
12957 };
12958 /**
12959 * Enumerates the top-level children in the `DataSnapshot`.
12960 *
12961 * Because of the way JavaScript objects work, the ordering of data in the
12962 * JavaScript object returned by `val()` is not guaranteed to match the
12963 * ordering on the server nor the ordering of `onChildAdded()` events. That is
12964 * where `forEach()` comes in handy. It guarantees the children of a
12965 * `DataSnapshot` will be iterated in their query order.
12966 *
12967 * If no explicit `orderBy*()` method is used, results are returned
12968 * ordered by key (unless priorities are used, in which case, results are
12969 * returned by priority).
12970 *
12971 * @param action - A function that will be called for each child DataSnapshot.
12972 * The callback can return true to cancel further enumeration.
12973 * @returns true if enumeration was canceled due to your callback returning
12974 * true.
12975 */
12976 DataSnapshot.prototype.forEach = function (action) {
12977 var _this = this;
12978 if (this._node.isLeafNode()) {
12979 return false;
12980 }
12981 var childrenNode = this._node;
12982 // Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
12983 return !!childrenNode.forEachChild(this._index, function (key, node) {
12984 return action(new DataSnapshot(node, child(_this.ref, key), PRIORITY_INDEX));
12985 });
12986 };
12987 /**
12988 * Returns true if the specified child path has (non-null) data.
12989 *
12990 * @param path - A relative path to the location of a potential child.
12991 * @returns `true` if data exists at the specified child path; else
12992 * `false`.
12993 */
12994 DataSnapshot.prototype.hasChild = function (path) {
12995 var childPath = new Path(path);
12996 return !this._node.getChild(childPath).isEmpty();
12997 };
12998 /**
12999 * Returns whether or not the `DataSnapshot` has any non-`null` child
13000 * properties.
13001 *
13002 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
13003 * children. If it does, you can enumerate them using `forEach()`. If it
13004 * doesn't, then either this snapshot contains a primitive value (which can be
13005 * retrieved with `val()`) or it is empty (in which case, `val()` will return
13006 * `null`).
13007 *
13008 * @returns true if this snapshot has any children; else false.
13009 */
13010 DataSnapshot.prototype.hasChildren = function () {
13011 if (this._node.isLeafNode()) {
13012 return false;
13013 }
13014 else {
13015 return !this._node.isEmpty();
13016 }
13017 };
13018 /**
13019 * Returns a JSON-serializable representation of this object.
13020 */
13021 DataSnapshot.prototype.toJSON = function () {
13022 return this.exportVal();
13023 };
13024 /**
13025 * Extracts a JavaScript value from a `DataSnapshot`.
13026 *
13027 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
13028 * scalar type (string, number, or boolean), an array, or an object. It may
13029 * also return null, indicating that the `DataSnapshot` is empty (contains no
13030 * data).
13031 *
13032 * @returns The DataSnapshot's contents as a JavaScript value (Object,
13033 * Array, string, number, boolean, or `null`).
13034 */
13035 // eslint-disable-next-line @typescript-eslint/no-explicit-any
13036 DataSnapshot.prototype.val = function () {
13037 return this._node.val();
13038 };
13039 return DataSnapshot;
13040}());
13041/**
13042 *
13043 * Returns a `Reference` representing the location in the Database
13044 * corresponding to the provided path. If no path is provided, the `Reference`
13045 * will point to the root of the Database.
13046 *
13047 * @param db - The database instance to obtain a reference for.
13048 * @param path - Optional path representing the location the returned
13049 * `Reference` will point. If not provided, the returned `Reference` will
13050 * point to the root of the Database.
13051 * @returns If a path is provided, a `Reference`
13052 * pointing to the provided path. Otherwise, a `Reference` pointing to the
13053 * root of the Database.
13054 */
13055function ref(db, path) {
13056 db = getModularInstance(db);
13057 db._checkNotDeleted('ref');
13058 return path !== undefined ? child(db._root, path) : db._root;
13059}
13060/**
13061 * Returns a `Reference` representing the location in the Database
13062 * corresponding to the provided Firebase URL.
13063 *
13064 * An exception is thrown if the URL is not a valid Firebase Database URL or it
13065 * has a different domain than the current `Database` instance.
13066 *
13067 * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored
13068 * and are not applied to the returned `Reference`.
13069 *
13070 * @param db - The database instance to obtain a reference for.
13071 * @param url - The Firebase URL at which the returned `Reference` will
13072 * point.
13073 * @returns A `Reference` pointing to the provided
13074 * Firebase URL.
13075 */
13076function refFromURL(db, url) {
13077 db = getModularInstance(db);
13078 db._checkNotDeleted('refFromURL');
13079 var parsedURL = parseRepoInfo(url, db._repo.repoInfo_.nodeAdmin);
13080 validateUrl('refFromURL', parsedURL);
13081 var repoInfo = parsedURL.repoInfo;
13082 if (!db._repo.repoInfo_.isCustomHost() &&
13083 repoInfo.host !== db._repo.repoInfo_.host) {
13084 fatal('refFromURL' +
13085 ': Host name does not match the current database: ' +
13086 '(found ' +
13087 repoInfo.host +
13088 ' but expected ' +
13089 db._repo.repoInfo_.host +
13090 ')');
13091 }
13092 return ref(db, parsedURL.path.toString());
13093}
13094/**
13095 * Gets a `Reference` for the location at the specified relative path.
13096 *
13097 * The relative path can either be a simple child name (for example, "ada") or
13098 * a deeper slash-separated path (for example, "ada/name/first").
13099 *
13100 * @param parent - The parent location.
13101 * @param path - A relative path from this location to the desired child
13102 * location.
13103 * @returns The specified child location.
13104 */
13105function child(parent, path) {
13106 parent = getModularInstance(parent);
13107 if (pathGetFront(parent._path) === null) {
13108 validateRootPathString('child', 'path', path, false);
13109 }
13110 else {
13111 validatePathString('child', 'path', path, false);
13112 }
13113 return new ReferenceImpl(parent._repo, pathChild(parent._path, path));
13114}
13115/**
13116 * Returns an `OnDisconnect` object - see
13117 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
13118 * for more information on how to use it.
13119 *
13120 * @param ref - The reference to add OnDisconnect triggers for.
13121 */
13122function onDisconnect(ref) {
13123 ref = getModularInstance(ref);
13124 return new OnDisconnect(ref._repo, ref._path);
13125}
13126/**
13127 * Generates a new child location using a unique key and returns its
13128 * `Reference`.
13129 *
13130 * This is the most common pattern for adding data to a collection of items.
13131 *
13132 * If you provide a value to `push()`, the value is written to the
13133 * generated location. If you don't pass a value, nothing is written to the
13134 * database and the child remains empty (but you can use the `Reference`
13135 * elsewhere).
13136 *
13137 * The unique keys generated by `push()` are ordered by the current time, so the
13138 * resulting list of items is chronologically sorted. The keys are also
13139 * designed to be unguessable (they contain 72 random bits of entropy).
13140 *
13141 * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}
13142 * </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}
13143 *
13144 * @param parent - The parent location.
13145 * @param value - Optional value to be written at the generated location.
13146 * @returns Combined `Promise` and `Reference`; resolves when write is complete,
13147 * but can be used immediately as the `Reference` to the child location.
13148 */
13149function push(parent, value) {
13150 parent = getModularInstance(parent);
13151 validateWritablePath('push', parent._path);
13152 validateFirebaseDataArg('push', value, parent._path, true);
13153 var now = repoServerTime(parent._repo);
13154 var name = nextPushId(now);
13155 // push() returns a ThennableReference whose promise is fulfilled with a
13156 // regular Reference. We use child() to create handles to two different
13157 // references. The first is turned into a ThennableReference below by adding
13158 // then() and catch() methods and is used as the return value of push(). The
13159 // second remains a regular Reference and is used as the fulfilled value of
13160 // the first ThennableReference.
13161 var thennablePushRef = child(parent, name);
13162 var pushRef = child(parent, name);
13163 var promise;
13164 if (value != null) {
13165 promise = set(pushRef, value).then(function () { return pushRef; });
13166 }
13167 else {
13168 promise = Promise.resolve(pushRef);
13169 }
13170 thennablePushRef.then = promise.then.bind(promise);
13171 thennablePushRef.catch = promise.then.bind(promise, undefined);
13172 return thennablePushRef;
13173}
13174/**
13175 * Removes the data at this Database location.
13176 *
13177 * Any data at child locations will also be deleted.
13178 *
13179 * The effect of the remove will be visible immediately and the corresponding
13180 * event 'value' will be triggered. Synchronization of the remove to the
13181 * Firebase servers will also be started, and the returned Promise will resolve
13182 * when complete. If provided, the onComplete callback will be called
13183 * asynchronously after synchronization has finished.
13184 *
13185 * @param ref - The location to remove.
13186 * @returns Resolves when remove on server is complete.
13187 */
13188function remove(ref) {
13189 validateWritablePath('remove', ref._path);
13190 return set(ref, null);
13191}
13192/**
13193 * Writes data to this Database location.
13194 *
13195 * This will overwrite any data at this location and all child locations.
13196 *
13197 * The effect of the write will be visible immediately, and the corresponding
13198 * events ("value", "child_added", etc.) will be triggered. Synchronization of
13199 * the data to the Firebase servers will also be started, and the returned
13200 * Promise will resolve when complete. If provided, the `onComplete` callback
13201 * will be called asynchronously after synchronization has finished.
13202 *
13203 * Passing `null` for the new value is equivalent to calling `remove()`; namely,
13204 * all data at this location and all child locations will be deleted.
13205 *
13206 * `set()` will remove any priority stored at this location, so if priority is
13207 * meant to be preserved, you need to use `setWithPriority()` instead.
13208 *
13209 * Note that modifying data with `set()` will cancel any pending transactions
13210 * at that location, so extreme care should be taken if mixing `set()` and
13211 * `transaction()` to modify the same data.
13212 *
13213 * A single `set()` will generate a single "value" event at the location where
13214 * the `set()` was performed.
13215 *
13216 * @param ref - The location to write to.
13217 * @param value - The value to be written (string, number, boolean, object,
13218 * array, or null).
13219 * @returns Resolves when write to server is complete.
13220 */
13221function set(ref, value) {
13222 ref = getModularInstance(ref);
13223 validateWritablePath('set', ref._path);
13224 validateFirebaseDataArg('set', value, ref._path, false);
13225 var deferred = new Deferred();
13226 repoSetWithPriority(ref._repo, ref._path, value,
13227 /*priority=*/ null, deferred.wrapCallback(function () { }));
13228 return deferred.promise;
13229}
13230/**
13231 * Sets a priority for the data at this Database location.
13232 *
13233 * Applications need not use priority but can order collections by
13234 * ordinary properties (see
13235 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13236 * ).
13237 *
13238 * @param ref - The location to write to.
13239 * @param priority - The priority to be written (string, number, or null).
13240 * @returns Resolves when write to server is complete.
13241 */
13242function setPriority(ref, priority) {
13243 ref = getModularInstance(ref);
13244 validateWritablePath('setPriority', ref._path);
13245 validatePriority('setPriority', priority, false);
13246 var deferred = new Deferred();
13247 repoSetWithPriority(ref._repo, pathChild(ref._path, '.priority'), priority, null, deferred.wrapCallback(function () { }));
13248 return deferred.promise;
13249}
13250/**
13251 * Writes data the Database location. Like `set()` but also specifies the
13252 * priority for that data.
13253 *
13254 * Applications need not use priority but can order collections by
13255 * ordinary properties (see
13256 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13257 * ).
13258 *
13259 * @param ref - The location to write to.
13260 * @param value - The value to be written (string, number, boolean, object,
13261 * array, or null).
13262 * @param priority - The priority to be written (string, number, or null).
13263 * @returns Resolves when write to server is complete.
13264 */
13265function setWithPriority(ref, value, priority) {
13266 validateWritablePath('setWithPriority', ref._path);
13267 validateFirebaseDataArg('setWithPriority', value, ref._path, false);
13268 validatePriority('setWithPriority', priority, false);
13269 if (ref.key === '.length' || ref.key === '.keys') {
13270 throw 'setWithPriority failed: ' + ref.key + ' is a read-only object.';
13271 }
13272 var deferred = new Deferred();
13273 repoSetWithPriority(ref._repo, ref._path, value, priority, deferred.wrapCallback(function () { }));
13274 return deferred.promise;
13275}
13276/**
13277 * Writes multiple values to the Database at once.
13278 *
13279 * The `values` argument contains multiple property-value pairs that will be
13280 * written to the Database together. Each child property can either be a simple
13281 * property (for example, "name") or a relative path (for example,
13282 * "name/first") from the current location to the data to update.
13283 *
13284 * As opposed to the `set()` method, `update()` can be use to selectively update
13285 * only the referenced properties at the current location (instead of replacing
13286 * all the child properties at the current location).
13287 *
13288 * The effect of the write will be visible immediately, and the corresponding
13289 * events ('value', 'child_added', etc.) will be triggered. Synchronization of
13290 * the data to the Firebase servers will also be started, and the returned
13291 * Promise will resolve when complete. If provided, the `onComplete` callback
13292 * will be called asynchronously after synchronization has finished.
13293 *
13294 * A single `update()` will generate a single "value" event at the location
13295 * where the `update()` was performed, regardless of how many children were
13296 * modified.
13297 *
13298 * Note that modifying data with `update()` will cancel any pending
13299 * transactions at that location, so extreme care should be taken if mixing
13300 * `update()` and `transaction()` to modify the same data.
13301 *
13302 * Passing `null` to `update()` will remove the data at this location.
13303 *
13304 * See
13305 * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}.
13306 *
13307 * @param ref - The location to write to.
13308 * @param values - Object containing multiple values.
13309 * @returns Resolves when update on server is complete.
13310 */
13311function update(ref, values) {
13312 validateFirebaseMergeDataArg('update', values, ref._path, false);
13313 var deferred = new Deferred();
13314 repoUpdate(ref._repo, ref._path, values, deferred.wrapCallback(function () { }));
13315 return deferred.promise;
13316}
13317/**
13318 * Gets the most up-to-date result for this query.
13319 *
13320 * @param query - The query to run.
13321 * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is
13322 * available, or rejects if the client is unable to return a value (e.g., if the
13323 * server is unreachable and there is nothing cached).
13324 */
13325function get(query) {
13326 query = getModularInstance(query);
13327 return repoGetValue(query._repo, query).then(function (node) {
13328 return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
13329 });
13330}
13331/**
13332 * Represents registration for 'value' events.
13333 */
13334var ValueEventRegistration = /** @class */ (function () {
13335 function ValueEventRegistration(callbackContext) {
13336 this.callbackContext = callbackContext;
13337 }
13338 ValueEventRegistration.prototype.respondsTo = function (eventType) {
13339 return eventType === 'value';
13340 };
13341 ValueEventRegistration.prototype.createEvent = function (change, query) {
13342 var index = query._queryParams.getIndex();
13343 return new DataEvent('value', this, new DataSnapshot(change.snapshotNode, new ReferenceImpl(query._repo, query._path), index));
13344 };
13345 ValueEventRegistration.prototype.getEventRunner = function (eventData) {
13346 var _this = this;
13347 if (eventData.getEventType() === 'cancel') {
13348 return function () {
13349 return _this.callbackContext.onCancel(eventData.error);
13350 };
13351 }
13352 else {
13353 return function () {
13354 return _this.callbackContext.onValue(eventData.snapshot, null);
13355 };
13356 }
13357 };
13358 ValueEventRegistration.prototype.createCancelEvent = function (error, path) {
13359 if (this.callbackContext.hasCancelCallback) {
13360 return new CancelEvent(this, error, path);
13361 }
13362 else {
13363 return null;
13364 }
13365 };
13366 ValueEventRegistration.prototype.matches = function (other) {
13367 if (!(other instanceof ValueEventRegistration)) {
13368 return false;
13369 }
13370 else if (!other.callbackContext || !this.callbackContext) {
13371 // If no callback specified, we consider it to match any callback.
13372 return true;
13373 }
13374 else {
13375 return other.callbackContext.matches(this.callbackContext);
13376 }
13377 };
13378 ValueEventRegistration.prototype.hasAnyCallback = function () {
13379 return this.callbackContext !== null;
13380 };
13381 return ValueEventRegistration;
13382}());
13383/**
13384 * Represents the registration of a child_x event.
13385 */
13386var ChildEventRegistration = /** @class */ (function () {
13387 function ChildEventRegistration(eventType, callbackContext) {
13388 this.eventType = eventType;
13389 this.callbackContext = callbackContext;
13390 }
13391 ChildEventRegistration.prototype.respondsTo = function (eventType) {
13392 var eventToCheck = eventType === 'children_added' ? 'child_added' : eventType;
13393 eventToCheck =
13394 eventToCheck === 'children_removed' ? 'child_removed' : eventToCheck;
13395 return this.eventType === eventToCheck;
13396 };
13397 ChildEventRegistration.prototype.createCancelEvent = function (error, path) {
13398 if (this.callbackContext.hasCancelCallback) {
13399 return new CancelEvent(this, error, path);
13400 }
13401 else {
13402 return null;
13403 }
13404 };
13405 ChildEventRegistration.prototype.createEvent = function (change, query) {
13406 assert(change.childName != null, 'Child events should have a childName.');
13407 var childRef = child(new ReferenceImpl(query._repo, query._path), change.childName);
13408 var index = query._queryParams.getIndex();
13409 return new DataEvent(change.type, this, new DataSnapshot(change.snapshotNode, childRef, index), change.prevName);
13410 };
13411 ChildEventRegistration.prototype.getEventRunner = function (eventData) {
13412 var _this = this;
13413 if (eventData.getEventType() === 'cancel') {
13414 return function () {
13415 return _this.callbackContext.onCancel(eventData.error);
13416 };
13417 }
13418 else {
13419 return function () {
13420 return _this.callbackContext.onValue(eventData.snapshot, eventData.prevName);
13421 };
13422 }
13423 };
13424 ChildEventRegistration.prototype.matches = function (other) {
13425 if (other instanceof ChildEventRegistration) {
13426 return (this.eventType === other.eventType &&
13427 (!this.callbackContext ||
13428 !other.callbackContext ||
13429 this.callbackContext.matches(other.callbackContext)));
13430 }
13431 return false;
13432 };
13433 ChildEventRegistration.prototype.hasAnyCallback = function () {
13434 return !!this.callbackContext;
13435 };
13436 return ChildEventRegistration;
13437}());
13438function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) {
13439 var cancelCallback;
13440 if (typeof cancelCallbackOrListenOptions === 'object') {
13441 cancelCallback = undefined;
13442 options = cancelCallbackOrListenOptions;
13443 }
13444 if (typeof cancelCallbackOrListenOptions === 'function') {
13445 cancelCallback = cancelCallbackOrListenOptions;
13446 }
13447 if (options && options.onlyOnce) {
13448 var userCallback_1 = callback;
13449 var onceCallback = function (dataSnapshot, previousChildName) {
13450 repoRemoveEventCallbackForQuery(query._repo, query, container);
13451 userCallback_1(dataSnapshot, previousChildName);
13452 };
13453 onceCallback.userCallback = callback.userCallback;
13454 onceCallback.context = callback.context;
13455 callback = onceCallback;
13456 }
13457 var callbackContext = new CallbackContext(callback, cancelCallback || undefined);
13458 var container = eventType === 'value'
13459 ? new ValueEventRegistration(callbackContext)
13460 : new ChildEventRegistration(eventType, callbackContext);
13461 repoAddEventCallbackForQuery(query._repo, query, container);
13462 return function () { return repoRemoveEventCallbackForQuery(query._repo, query, container); };
13463}
13464function onValue(query, callback, cancelCallbackOrListenOptions, options) {
13465 return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options);
13466}
13467function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) {
13468 return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options);
13469}
13470function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) {
13471 return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options);
13472}
13473function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) {
13474 return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options);
13475}
13476function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) {
13477 return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options);
13478}
13479/**
13480 * Detaches a callback previously attached with `on()`.
13481 *
13482 * Detach a callback previously attached with `on()`. Note that if `on()` was
13483 * called multiple times with the same eventType and callback, the callback
13484 * will be called multiple times for each event, and `off()` must be called
13485 * multiple times to remove the callback. Calling `off()` on a parent listener
13486 * will not automatically remove listeners registered on child nodes, `off()`
13487 * must also be called on any child listeners to remove the callback.
13488 *
13489 * If a callback is not specified, all callbacks for the specified eventType
13490 * will be removed. Similarly, if no eventType is specified, all callbacks
13491 * for the `Reference` will be removed.
13492 *
13493 * Individual listeners can also be removed by invoking their unsubscribe
13494 * callbacks.
13495 *
13496 * @param query - The query that the listener was registered with.
13497 * @param eventType - One of the following strings: "value", "child_added",
13498 * "child_changed", "child_removed", or "child_moved." If omitted, all callbacks
13499 * for the `Reference` will be removed.
13500 * @param callback - The callback function that was passed to `on()` or
13501 * `undefined` to remove all callbacks.
13502 */
13503function off(query, eventType, callback) {
13504 var container = null;
13505 var expCallback = callback ? new CallbackContext(callback) : null;
13506 if (eventType === 'value') {
13507 container = new ValueEventRegistration(expCallback);
13508 }
13509 else if (eventType) {
13510 container = new ChildEventRegistration(eventType, expCallback);
13511 }
13512 repoRemoveEventCallbackForQuery(query._repo, query, container);
13513}
13514/**
13515 * A `QueryConstraint` is used to narrow the set of documents returned by a
13516 * Database query. `QueryConstraint`s are created by invoking {@link endAt},
13517 * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link
13518 * limitToFirst}, {@link limitToLast}, {@link orderByChild},
13519 * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} ,
13520 * {@link orderByValue} or {@link equalTo} and
13521 * can then be passed to {@link query} to create a new query instance that
13522 * also contains this `QueryConstraint`.
13523 */
13524var QueryConstraint = /** @class */ (function () {
13525 function QueryConstraint() {
13526 }
13527 return QueryConstraint;
13528}());
13529var QueryEndAtConstraint = /** @class */ (function (_super) {
13530 __extends(QueryEndAtConstraint, _super);
13531 function QueryEndAtConstraint(_value, _key) {
13532 var _this = _super.call(this) || this;
13533 _this._value = _value;
13534 _this._key = _key;
13535 return _this;
13536 }
13537 QueryEndAtConstraint.prototype._apply = function (query) {
13538 validateFirebaseDataArg('endAt', this._value, query._path, true);
13539 var newParams = queryParamsEndAt(query._queryParams, this._value, this._key);
13540 validateLimit(newParams);
13541 validateQueryEndpoints(newParams);
13542 if (query._queryParams.hasEnd()) {
13543 throw new Error('endAt: Starting point was already set (by another call to endAt, ' +
13544 'endBefore or equalTo).');
13545 }
13546 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13547 };
13548 return QueryEndAtConstraint;
13549}(QueryConstraint));
13550/**
13551 * Creates a `QueryConstraint` with the specified ending point.
13552 *
13553 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13554 * allows you to choose arbitrary starting and ending points for your queries.
13555 *
13556 * The ending point is inclusive, so children with exactly the specified value
13557 * will be included in the query. The optional key argument can be used to
13558 * further limit the range of the query. If it is specified, then children that
13559 * have exactly the specified value must also have a key name less than or equal
13560 * to the specified key.
13561 *
13562 * You can read more about `endAt()` in
13563 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13564 *
13565 * @param value - The value to end at. The argument type depends on which
13566 * `orderBy*()` function was used in this query. Specify a value that matches
13567 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13568 * value must be a string.
13569 * @param key - The child key to end at, among the children with the previously
13570 * specified priority. This argument is only allowed if ordering by child,
13571 * value, or priority.
13572 */
13573function endAt(value, key) {
13574 validateKey('endAt', 'key', key, true);
13575 return new QueryEndAtConstraint(value, key);
13576}
13577var QueryEndBeforeConstraint = /** @class */ (function (_super) {
13578 __extends(QueryEndBeforeConstraint, _super);
13579 function QueryEndBeforeConstraint(_value, _key) {
13580 var _this = _super.call(this) || this;
13581 _this._value = _value;
13582 _this._key = _key;
13583 return _this;
13584 }
13585 QueryEndBeforeConstraint.prototype._apply = function (query) {
13586 validateFirebaseDataArg('endBefore', this._value, query._path, false);
13587 var newParams = queryParamsEndBefore(query._queryParams, this._value, this._key);
13588 validateLimit(newParams);
13589 validateQueryEndpoints(newParams);
13590 if (query._queryParams.hasEnd()) {
13591 throw new Error('endBefore: Starting point was already set (by another call to endAt, ' +
13592 'endBefore or equalTo).');
13593 }
13594 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13595 };
13596 return QueryEndBeforeConstraint;
13597}(QueryConstraint));
13598/**
13599 * Creates a `QueryConstraint` with the specified ending point (exclusive).
13600 *
13601 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13602 * allows you to choose arbitrary starting and ending points for your queries.
13603 *
13604 * The ending point is exclusive. If only a value is provided, children
13605 * with a value less than the specified value will be included in the query.
13606 * If a key is specified, then children must have a value lesss than or equal
13607 * to the specified value and a a key name less than the specified key.
13608 *
13609 * @param value - The value to end before. The argument type depends on which
13610 * `orderBy*()` function was used in this query. Specify a value that matches
13611 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13612 * value must be a string.
13613 * @param key - The child key to end before, among the children with the
13614 * previously specified priority. This argument is only allowed if ordering by
13615 * child, value, or priority.
13616 */
13617function endBefore(value, key) {
13618 validateKey('endBefore', 'key', key, true);
13619 return new QueryEndBeforeConstraint(value, key);
13620}
13621var QueryStartAtConstraint = /** @class */ (function (_super) {
13622 __extends(QueryStartAtConstraint, _super);
13623 function QueryStartAtConstraint(_value, _key) {
13624 var _this = _super.call(this) || this;
13625 _this._value = _value;
13626 _this._key = _key;
13627 return _this;
13628 }
13629 QueryStartAtConstraint.prototype._apply = function (query) {
13630 validateFirebaseDataArg('startAt', this._value, query._path, true);
13631 var newParams = queryParamsStartAt(query._queryParams, this._value, this._key);
13632 validateLimit(newParams);
13633 validateQueryEndpoints(newParams);
13634 if (query._queryParams.hasStart()) {
13635 throw new Error('startAt: Starting point was already set (by another call to startAt, ' +
13636 'startBefore or equalTo).');
13637 }
13638 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13639 };
13640 return QueryStartAtConstraint;
13641}(QueryConstraint));
13642/**
13643 * Creates a `QueryConstraint` with the specified starting point.
13644 *
13645 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13646 * allows you to choose arbitrary starting and ending points for your queries.
13647 *
13648 * The starting point is inclusive, so children with exactly the specified value
13649 * will be included in the query. The optional key argument can be used to
13650 * further limit the range of the query. If it is specified, then children that
13651 * have exactly the specified value must also have a key name greater than or
13652 * equal to the specified key.
13653 *
13654 * You can read more about `startAt()` in
13655 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13656 *
13657 * @param value - The value to start at. The argument type depends on which
13658 * `orderBy*()` function was used in this query. Specify a value that matches
13659 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13660 * value must be a string.
13661 * @param key - The child key to start at. This argument is only allowed if
13662 * ordering by child, value, or priority.
13663 */
13664function startAt(value, key) {
13665 if (value === void 0) { value = null; }
13666 validateKey('startAt', 'key', key, true);
13667 return new QueryStartAtConstraint(value, key);
13668}
13669var QueryStartAfterConstraint = /** @class */ (function (_super) {
13670 __extends(QueryStartAfterConstraint, _super);
13671 function QueryStartAfterConstraint(_value, _key) {
13672 var _this = _super.call(this) || this;
13673 _this._value = _value;
13674 _this._key = _key;
13675 return _this;
13676 }
13677 QueryStartAfterConstraint.prototype._apply = function (query) {
13678 validateFirebaseDataArg('startAfter', this._value, query._path, false);
13679 var newParams = queryParamsStartAfter(query._queryParams, this._value, this._key);
13680 validateLimit(newParams);
13681 validateQueryEndpoints(newParams);
13682 if (query._queryParams.hasStart()) {
13683 throw new Error('startAfter: Starting point was already set (by another call to startAt, ' +
13684 'startAfter, or equalTo).');
13685 }
13686 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13687 };
13688 return QueryStartAfterConstraint;
13689}(QueryConstraint));
13690/**
13691 * Creates a `QueryConstraint` with the specified starting point (exclusive).
13692 *
13693 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13694 * allows you to choose arbitrary starting and ending points for your queries.
13695 *
13696 * The starting point is exclusive. If only a value is provided, children
13697 * with a value greater than the specified value will be included in the query.
13698 * If a key is specified, then children must have a value greater than or equal
13699 * to the specified value and a a key name greater than the specified key.
13700 *
13701 * @param value - The value to start after. The argument type depends on which
13702 * `orderBy*()` function was used in this query. Specify a value that matches
13703 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13704 * value must be a string.
13705 * @param key - The child key to start after. This argument is only allowed if
13706 * ordering by child, value, or priority.
13707 */
13708function startAfter(value, key) {
13709 validateKey('startAfter', 'key', key, true);
13710 return new QueryStartAfterConstraint(value, key);
13711}
13712var QueryLimitToFirstConstraint = /** @class */ (function (_super) {
13713 __extends(QueryLimitToFirstConstraint, _super);
13714 function QueryLimitToFirstConstraint(_limit) {
13715 var _this = _super.call(this) || this;
13716 _this._limit = _limit;
13717 return _this;
13718 }
13719 QueryLimitToFirstConstraint.prototype._apply = function (query) {
13720 if (query._queryParams.hasLimit()) {
13721 throw new Error('limitToFirst: Limit was already set (by another call to limitToFirst ' +
13722 'or limitToLast).');
13723 }
13724 return new QueryImpl(query._repo, query._path, queryParamsLimitToFirst(query._queryParams, this._limit), query._orderByCalled);
13725 };
13726 return QueryLimitToFirstConstraint;
13727}(QueryConstraint));
13728/**
13729 * Creates a new `QueryConstraint` that if limited to the first specific number
13730 * of children.
13731 *
13732 * The `limitToFirst()` method is used to set a maximum number of children to be
13733 * synced for a given callback. If we set a limit of 100, we will initially only
13734 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13735 * stored in our Database, a `child_added` event will fire for each message.
13736 * However, if we have over 100 messages, we will only receive a `child_added`
13737 * event for the first 100 ordered messages. As items change, we will receive
13738 * `child_removed` events for each item that drops out of the active list so
13739 * that the total number stays at 100.
13740 *
13741 * You can read more about `limitToFirst()` in
13742 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13743 *
13744 * @param limit - The maximum number of nodes to include in this query.
13745 */
13746function limitToFirst(limit) {
13747 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13748 throw new Error('limitToFirst: First argument must be a positive integer.');
13749 }
13750 return new QueryLimitToFirstConstraint(limit);
13751}
13752var QueryLimitToLastConstraint = /** @class */ (function (_super) {
13753 __extends(QueryLimitToLastConstraint, _super);
13754 function QueryLimitToLastConstraint(_limit) {
13755 var _this = _super.call(this) || this;
13756 _this._limit = _limit;
13757 return _this;
13758 }
13759 QueryLimitToLastConstraint.prototype._apply = function (query) {
13760 if (query._queryParams.hasLimit()) {
13761 throw new Error('limitToLast: Limit was already set (by another call to limitToFirst ' +
13762 'or limitToLast).');
13763 }
13764 return new QueryImpl(query._repo, query._path, queryParamsLimitToLast(query._queryParams, this._limit), query._orderByCalled);
13765 };
13766 return QueryLimitToLastConstraint;
13767}(QueryConstraint));
13768/**
13769 * Creates a new `QueryConstraint` that is limited to return only the last
13770 * specified number of children.
13771 *
13772 * The `limitToLast()` method is used to set a maximum number of children to be
13773 * synced for a given callback. If we set a limit of 100, we will initially only
13774 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13775 * stored in our Database, a `child_added` event will fire for each message.
13776 * However, if we have over 100 messages, we will only receive a `child_added`
13777 * event for the last 100 ordered messages. As items change, we will receive
13778 * `child_removed` events for each item that drops out of the active list so
13779 * that the total number stays at 100.
13780 *
13781 * You can read more about `limitToLast()` in
13782 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13783 *
13784 * @param limit - The maximum number of nodes to include in this query.
13785 */
13786function limitToLast(limit) {
13787 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13788 throw new Error('limitToLast: First argument must be a positive integer.');
13789 }
13790 return new QueryLimitToLastConstraint(limit);
13791}
13792var QueryOrderByChildConstraint = /** @class */ (function (_super) {
13793 __extends(QueryOrderByChildConstraint, _super);
13794 function QueryOrderByChildConstraint(_path) {
13795 var _this = _super.call(this) || this;
13796 _this._path = _path;
13797 return _this;
13798 }
13799 QueryOrderByChildConstraint.prototype._apply = function (query) {
13800 validateNoPreviousOrderByCall(query, 'orderByChild');
13801 var parsedPath = new Path(this._path);
13802 if (pathIsEmpty(parsedPath)) {
13803 throw new Error('orderByChild: cannot pass in empty path. Use orderByValue() instead.');
13804 }
13805 var index = new PathIndex(parsedPath);
13806 var newParams = queryParamsOrderBy(query._queryParams, index);
13807 validateQueryEndpoints(newParams);
13808 return new QueryImpl(query._repo, query._path, newParams,
13809 /*orderByCalled=*/ true);
13810 };
13811 return QueryOrderByChildConstraint;
13812}(QueryConstraint));
13813/**
13814 * Creates a new `QueryConstraint` that orders by the specified child key.
13815 *
13816 * Queries can only order by one key at a time. Calling `orderByChild()`
13817 * multiple times on the same query is an error.
13818 *
13819 * Firebase queries allow you to order your data by any child key on the fly.
13820 * However, if you know in advance what your indexes will be, you can define
13821 * them via the .indexOn rule in your Security Rules for better performance. See
13822 * the{@link https://firebase.google.com/docs/database/security/indexing-data}
13823 * rule for more information.
13824 *
13825 * You can read more about `orderByChild()` in
13826 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13827 *
13828 * @param path - The path to order by.
13829 */
13830function orderByChild(path) {
13831 if (path === '$key') {
13832 throw new Error('orderByChild: "$key" is invalid. Use orderByKey() instead.');
13833 }
13834 else if (path === '$priority') {
13835 throw new Error('orderByChild: "$priority" is invalid. Use orderByPriority() instead.');
13836 }
13837 else if (path === '$value') {
13838 throw new Error('orderByChild: "$value" is invalid. Use orderByValue() instead.');
13839 }
13840 validatePathString('orderByChild', 'path', path, false);
13841 return new QueryOrderByChildConstraint(path);
13842}
13843var QueryOrderByKeyConstraint = /** @class */ (function (_super) {
13844 __extends(QueryOrderByKeyConstraint, _super);
13845 function QueryOrderByKeyConstraint() {
13846 return _super !== null && _super.apply(this, arguments) || this;
13847 }
13848 QueryOrderByKeyConstraint.prototype._apply = function (query) {
13849 validateNoPreviousOrderByCall(query, 'orderByKey');
13850 var newParams = queryParamsOrderBy(query._queryParams, KEY_INDEX);
13851 validateQueryEndpoints(newParams);
13852 return new QueryImpl(query._repo, query._path, newParams,
13853 /*orderByCalled=*/ true);
13854 };
13855 return QueryOrderByKeyConstraint;
13856}(QueryConstraint));
13857/**
13858 * Creates a new `QueryConstraint` that orders by the key.
13859 *
13860 * Sorts the results of a query by their (ascending) key values.
13861 *
13862 * You can read more about `orderByKey()` in
13863 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13864 */
13865function orderByKey() {
13866 return new QueryOrderByKeyConstraint();
13867}
13868var QueryOrderByPriorityConstraint = /** @class */ (function (_super) {
13869 __extends(QueryOrderByPriorityConstraint, _super);
13870 function QueryOrderByPriorityConstraint() {
13871 return _super !== null && _super.apply(this, arguments) || this;
13872 }
13873 QueryOrderByPriorityConstraint.prototype._apply = function (query) {
13874 validateNoPreviousOrderByCall(query, 'orderByPriority');
13875 var newParams = queryParamsOrderBy(query._queryParams, PRIORITY_INDEX);
13876 validateQueryEndpoints(newParams);
13877 return new QueryImpl(query._repo, query._path, newParams,
13878 /*orderByCalled=*/ true);
13879 };
13880 return QueryOrderByPriorityConstraint;
13881}(QueryConstraint));
13882/**
13883 * Creates a new `QueryConstraint` that orders by priority.
13884 *
13885 * Applications need not use priority but can order collections by
13886 * ordinary properties (see
13887 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}
13888 * for alternatives to priority.
13889 */
13890function orderByPriority() {
13891 return new QueryOrderByPriorityConstraint();
13892}
13893var QueryOrderByValueConstraint = /** @class */ (function (_super) {
13894 __extends(QueryOrderByValueConstraint, _super);
13895 function QueryOrderByValueConstraint() {
13896 return _super !== null && _super.apply(this, arguments) || this;
13897 }
13898 QueryOrderByValueConstraint.prototype._apply = function (query) {
13899 validateNoPreviousOrderByCall(query, 'orderByValue');
13900 var newParams = queryParamsOrderBy(query._queryParams, VALUE_INDEX);
13901 validateQueryEndpoints(newParams);
13902 return new QueryImpl(query._repo, query._path, newParams,
13903 /*orderByCalled=*/ true);
13904 };
13905 return QueryOrderByValueConstraint;
13906}(QueryConstraint));
13907/**
13908 * Creates a new `QueryConstraint` that orders by value.
13909 *
13910 * If the children of a query are all scalar values (string, number, or
13911 * boolean), you can order the results by their (ascending) values.
13912 *
13913 * You can read more about `orderByValue()` in
13914 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13915 */
13916function orderByValue() {
13917 return new QueryOrderByValueConstraint();
13918}
13919var QueryEqualToValueConstraint = /** @class */ (function (_super) {
13920 __extends(QueryEqualToValueConstraint, _super);
13921 function QueryEqualToValueConstraint(_value, _key) {
13922 var _this = _super.call(this) || this;
13923 _this._value = _value;
13924 _this._key = _key;
13925 return _this;
13926 }
13927 QueryEqualToValueConstraint.prototype._apply = function (query) {
13928 validateFirebaseDataArg('equalTo', this._value, query._path, false);
13929 if (query._queryParams.hasStart()) {
13930 throw new Error('equalTo: Starting point was already set (by another call to startAt/startAfter or ' +
13931 'equalTo).');
13932 }
13933 if (query._queryParams.hasEnd()) {
13934 throw new Error('equalTo: Ending point was already set (by another call to endAt/endBefore or ' +
13935 'equalTo).');
13936 }
13937 return new QueryEndAtConstraint(this._value, this._key)._apply(new QueryStartAtConstraint(this._value, this._key)._apply(query));
13938 };
13939 return QueryEqualToValueConstraint;
13940}(QueryConstraint));
13941/**
13942 * Creates a `QueryConstraint` that includes children that match the specified
13943 * value.
13944 *
13945 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13946 * allows you to choose arbitrary starting and ending points for your queries.
13947 *
13948 * The optional key argument can be used to further limit the range of the
13949 * query. If it is specified, then children that have exactly the specified
13950 * value must also have exactly the specified key as their key name. This can be
13951 * used to filter result sets with many matches for the same value.
13952 *
13953 * You can read more about `equalTo()` in
13954 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13955 *
13956 * @param value - The value to match for. The argument type depends on which
13957 * `orderBy*()` function was used in this query. Specify a value that matches
13958 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13959 * value must be a string.
13960 * @param key - The child key to start at, among the children with the
13961 * previously specified priority. This argument is only allowed if ordering by
13962 * child, value, or priority.
13963 */
13964function equalTo(value, key) {
13965 validateKey('equalTo', 'key', key, true);
13966 return new QueryEqualToValueConstraint(value, key);
13967}
13968/**
13969 * Creates a new immutable instance of `Query` that is extended to also include
13970 * additional query constraints.
13971 *
13972 * @param query - The Query instance to use as a base for the new constraints.
13973 * @param queryConstraints - The list of `QueryConstraint`s to apply.
13974 * @throws if any of the provided query constraints cannot be combined with the
13975 * existing or new constraints.
13976 */
13977function query(query) {
13978 var e_1, _a;
13979 var queryConstraints = [];
13980 for (var _i = 1; _i < arguments.length; _i++) {
13981 queryConstraints[_i - 1] = arguments[_i];
13982 }
13983 var queryImpl = getModularInstance(query);
13984 try {
13985 for (var queryConstraints_1 = __values(queryConstraints), queryConstraints_1_1 = queryConstraints_1.next(); !queryConstraints_1_1.done; queryConstraints_1_1 = queryConstraints_1.next()) {
13986 var constraint = queryConstraints_1_1.value;
13987 queryImpl = constraint._apply(queryImpl);
13988 }
13989 }
13990 catch (e_1_1) { e_1 = { error: e_1_1 }; }
13991 finally {
13992 try {
13993 if (queryConstraints_1_1 && !queryConstraints_1_1.done && (_a = queryConstraints_1.return)) _a.call(queryConstraints_1);
13994 }
13995 finally { if (e_1) throw e_1.error; }
13996 }
13997 return queryImpl;
13998}
13999/**
14000 * Define reference constructor in various modules
14001 *
14002 * We are doing this here to avoid several circular
14003 * dependency issues
14004 */
14005syncPointSetReferenceConstructor(ReferenceImpl);
14006syncTreeSetReferenceConstructor(ReferenceImpl);
14007
14008/**
14009 * @license
14010 * Copyright 2020 Google LLC
14011 *
14012 * Licensed under the Apache License, Version 2.0 (the "License");
14013 * you may not use this file except in compliance with the License.
14014 * You may obtain a copy of the License at
14015 *
14016 * http://www.apache.org/licenses/LICENSE-2.0
14017 *
14018 * Unless required by applicable law or agreed to in writing, software
14019 * distributed under the License is distributed on an "AS IS" BASIS,
14020 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14021 * See the License for the specific language governing permissions and
14022 * limitations under the License.
14023 */
14024/**
14025 * This variable is also defined in the firebase Node.js Admin SDK. Before
14026 * modifying this definition, consult the definition in:
14027 *
14028 * https://github.com/firebase/firebase-admin-node
14029 *
14030 * and make sure the two are consistent.
14031 */
14032var FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST';
14033/**
14034 * Creates and caches `Repo` instances.
14035 */
14036var repos = {};
14037/**
14038 * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes).
14039 */
14040var useRestClient = false;
14041/**
14042 * Update an existing `Repo` in place to point to a new host/port.
14043 */
14044function repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider) {
14045 repo.repoInfo_ = new RepoInfo(host + ":" + port,
14046 /* secure= */ false, repo.repoInfo_.namespace, repo.repoInfo_.webSocketOnly, repo.repoInfo_.nodeAdmin, repo.repoInfo_.persistenceKey, repo.repoInfo_.includeNamespaceInQueryParams);
14047 if (tokenProvider) {
14048 repo.authTokenProvider_ = tokenProvider;
14049 }
14050}
14051/**
14052 * This function should only ever be called to CREATE a new database instance.
14053 * @internal
14054 */
14055function repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url, nodeAdmin) {
14056 var dbUrl = url || app.options.databaseURL;
14057 if (dbUrl === undefined) {
14058 if (!app.options.projectId) {
14059 fatal("Can't determine Firebase Database URL. Be sure to include " +
14060 ' a Project ID when calling firebase.initializeApp().');
14061 }
14062 log('Using default host for project ', app.options.projectId);
14063 dbUrl = app.options.projectId + "-default-rtdb.firebaseio.com";
14064 }
14065 var parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14066 var repoInfo = parsedUrl.repoInfo;
14067 var isEmulator;
14068 var dbEmulatorHost = undefined;
14069 if (typeof process !== 'undefined' && process.env) {
14070 dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR];
14071 }
14072 if (dbEmulatorHost) {
14073 isEmulator = true;
14074 dbUrl = "http://" + dbEmulatorHost + "?ns=" + repoInfo.namespace;
14075 parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14076 repoInfo = parsedUrl.repoInfo;
14077 }
14078 else {
14079 isEmulator = !parsedUrl.repoInfo.secure;
14080 }
14081 var authTokenProvider = nodeAdmin && isEmulator
14082 ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER)
14083 : new FirebaseAuthTokenProvider(app.name, app.options, authProvider);
14084 validateUrl('Invalid Firebase Database URL', parsedUrl);
14085 if (!pathIsEmpty(parsedUrl.path)) {
14086 fatal('Database URL must point to the root of a Firebase Database ' +
14087 '(not including a child path).');
14088 }
14089 var repo = repoManagerCreateRepo(repoInfo, app, authTokenProvider, new AppCheckTokenProvider(app.name, appCheckProvider));
14090 return new Database(repo, app);
14091}
14092/**
14093 * Remove the repo and make sure it is disconnected.
14094 *
14095 */
14096function repoManagerDeleteRepo(repo, appName) {
14097 var appRepos = repos[appName];
14098 // This should never happen...
14099 if (!appRepos || appRepos[repo.key] !== repo) {
14100 fatal("Database " + appName + "(" + repo.repoInfo_ + ") has already been deleted.");
14101 }
14102 repoInterrupt(repo);
14103 delete appRepos[repo.key];
14104}
14105/**
14106 * Ensures a repo doesn't already exist and then creates one using the
14107 * provided app.
14108 *
14109 * @param repoInfo - The metadata about the Repo
14110 * @returns The Repo object for the specified server / repoName.
14111 */
14112function repoManagerCreateRepo(repoInfo, app, authTokenProvider, appCheckProvider) {
14113 var appRepos = repos[app.name];
14114 if (!appRepos) {
14115 appRepos = {};
14116 repos[app.name] = appRepos;
14117 }
14118 var repo = appRepos[repoInfo.toURLString()];
14119 if (repo) {
14120 fatal('Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.');
14121 }
14122 repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider);
14123 appRepos[repoInfo.toURLString()] = repo;
14124 return repo;
14125}
14126/**
14127 * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos.
14128 */
14129function repoManagerForceRestClient(forceRestClient) {
14130 useRestClient = forceRestClient;
14131}
14132/**
14133 * Class representing a Firebase Realtime Database.
14134 */
14135var Database = /** @class */ (function () {
14136 /** @hideconstructor */
14137 function Database(_repoInternal,
14138 /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */
14139 app) {
14140 this._repoInternal = _repoInternal;
14141 this.app = app;
14142 /** Represents a `Database` instance. */
14143 this['type'] = 'database';
14144 /** Track if the instance has been used (root or repo accessed) */
14145 this._instanceStarted = false;
14146 }
14147 Object.defineProperty(Database.prototype, "_repo", {
14148 get: function () {
14149 if (!this._instanceStarted) {
14150 repoStart(this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride']);
14151 this._instanceStarted = true;
14152 }
14153 return this._repoInternal;
14154 },
14155 enumerable: false,
14156 configurable: true
14157 });
14158 Object.defineProperty(Database.prototype, "_root", {
14159 get: function () {
14160 if (!this._rootInternal) {
14161 this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath());
14162 }
14163 return this._rootInternal;
14164 },
14165 enumerable: false,
14166 configurable: true
14167 });
14168 Database.prototype._delete = function () {
14169 if (this._rootInternal !== null) {
14170 repoManagerDeleteRepo(this._repo, this.app.name);
14171 this._repoInternal = null;
14172 this._rootInternal = null;
14173 }
14174 return Promise.resolve();
14175 };
14176 Database.prototype._checkNotDeleted = function (apiName) {
14177 if (this._rootInternal === null) {
14178 fatal('Cannot call ' + apiName + ' on a deleted database.');
14179 }
14180 };
14181 return Database;
14182}());
14183function checkTransportInit() {
14184 if (TransportManager.IS_TRANSPORT_INITIALIZED) {
14185 warn('Transport has already been initialized. Please call this function before calling ref or setting up a listener');
14186 }
14187}
14188/**
14189 * Force the use of websockets instead of longPolling.
14190 */
14191function forceWebSockets() {
14192 checkTransportInit();
14193 BrowserPollConnection.forceDisallow();
14194}
14195/**
14196 * Force the use of longPolling instead of websockets. This will be ignored if websocket protocol is used in databaseURL.
14197 */
14198function forceLongPolling() {
14199 checkTransportInit();
14200 WebSocketConnection.forceDisallow();
14201 BrowserPollConnection.forceAllow();
14202}
14203/**
14204 * Returns the instance of the Realtime Database SDK that is associated
14205 * with the provided {@link @firebase/app#FirebaseApp}. Initializes a new instance with
14206 * with default settings if no instance exists or if the existing instance uses
14207 * a custom database URL.
14208 *
14209 * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned Realtime
14210 * Database instance is associated with.
14211 * @param url - The URL of the Realtime Database instance to connect to. If not
14212 * provided, the SDK connects to the default instance of the Firebase App.
14213 * @returns The `Database` instance of the provided app.
14214 */
14215function getDatabase(app, url) {
14216 if (app === void 0) { app = getApp(); }
14217 return _getProvider(app, 'database').getImmediate({
14218 identifier: url
14219 });
14220}
14221/**
14222 * Modify the provided instance to communicate with the Realtime Database
14223 * emulator.
14224 *
14225 * <p>Note: This method must be called before performing any other operation.
14226 *
14227 * @param db - The instance to modify.
14228 * @param host - The emulator host (ex: localhost)
14229 * @param port - The emulator port (ex: 8080)
14230 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
14231 */
14232function connectDatabaseEmulator(db, host, port, options) {
14233 if (options === void 0) { options = {}; }
14234 db = getModularInstance(db);
14235 db._checkNotDeleted('useEmulator');
14236 if (db._instanceStarted) {
14237 fatal('Cannot call useEmulator() after instance has already been initialized.');
14238 }
14239 var repo = db._repoInternal;
14240 var tokenProvider = undefined;
14241 if (repo.repoInfo_.nodeAdmin) {
14242 if (options.mockUserToken) {
14243 fatal('mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".');
14244 }
14245 tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
14246 }
14247 else if (options.mockUserToken) {
14248 var token = typeof options.mockUserToken === 'string'
14249 ? options.mockUserToken
14250 : createMockUserToken(options.mockUserToken, db.app.options.projectId);
14251 tokenProvider = new EmulatorTokenProvider(token);
14252 }
14253 // Modify the repo to apply emulator settings
14254 repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider);
14255}
14256/**
14257 * Disconnects from the server (all Database operations will be completed
14258 * offline).
14259 *
14260 * The client automatically maintains a persistent connection to the Database
14261 * server, which will remain active indefinitely and reconnect when
14262 * disconnected. However, the `goOffline()` and `goOnline()` methods may be used
14263 * to control the client connection in cases where a persistent connection is
14264 * undesirable.
14265 *
14266 * While offline, the client will no longer receive data updates from the
14267 * Database. However, all Database operations performed locally will continue to
14268 * immediately fire events, allowing your application to continue behaving
14269 * normally. Additionally, each operation performed locally will automatically
14270 * be queued and retried upon reconnection to the Database server.
14271 *
14272 * To reconnect to the Database and begin receiving remote events, see
14273 * `goOnline()`.
14274 *
14275 * @param db - The instance to disconnect.
14276 */
14277function goOffline(db) {
14278 db = getModularInstance(db);
14279 db._checkNotDeleted('goOffline');
14280 repoInterrupt(db._repo);
14281}
14282/**
14283 * Reconnects to the server and synchronizes the offline Database state
14284 * with the server state.
14285 *
14286 * This method should be used after disabling the active connection with
14287 * `goOffline()`. Once reconnected, the client will transmit the proper data
14288 * and fire the appropriate events so that your client "catches up"
14289 * automatically.
14290 *
14291 * @param db - The instance to reconnect.
14292 */
14293function goOnline(db) {
14294 db = getModularInstance(db);
14295 db._checkNotDeleted('goOnline');
14296 repoResume(db._repo);
14297}
14298function enableLogging(logger, persistent) {
14299 enableLogging$1(logger, persistent);
14300}
14301
14302/**
14303 * @license
14304 * Copyright 2021 Google LLC
14305 *
14306 * Licensed under the Apache License, Version 2.0 (the "License");
14307 * you may not use this file except in compliance with the License.
14308 * You may obtain a copy of the License at
14309 *
14310 * http://www.apache.org/licenses/LICENSE-2.0
14311 *
14312 * Unless required by applicable law or agreed to in writing, software
14313 * distributed under the License is distributed on an "AS IS" BASIS,
14314 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14315 * See the License for the specific language governing permissions and
14316 * limitations under the License.
14317 */
14318function registerDatabase(variant) {
14319 setSDKVersion(SDK_VERSION$1);
14320 _registerComponent(new Component('database', function (container, _a) {
14321 var url = _a.instanceIdentifier;
14322 var app = container.getProvider('app').getImmediate();
14323 var authProvider = container.getProvider('auth-internal');
14324 var appCheckProvider = container.getProvider('app-check-internal');
14325 return repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url);
14326 }, "PUBLIC" /* PUBLIC */).setMultipleInstances(true));
14327 registerVersion(name, version, variant);
14328 // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
14329 registerVersion(name, version, 'esm5');
14330}
14331
14332/**
14333 * @license
14334 * Copyright 2020 Google LLC
14335 *
14336 * Licensed under the Apache License, Version 2.0 (the "License");
14337 * you may not use this file except in compliance with the License.
14338 * You may obtain a copy of the License at
14339 *
14340 * http://www.apache.org/licenses/LICENSE-2.0
14341 *
14342 * Unless required by applicable law or agreed to in writing, software
14343 * distributed under the License is distributed on an "AS IS" BASIS,
14344 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14345 * See the License for the specific language governing permissions and
14346 * limitations under the License.
14347 */
14348var SERVER_TIMESTAMP = {
14349 '.sv': 'timestamp'
14350};
14351/**
14352 * Returns a placeholder value for auto-populating the current timestamp (time
14353 * since the Unix epoch, in milliseconds) as determined by the Firebase
14354 * servers.
14355 */
14356function serverTimestamp() {
14357 return SERVER_TIMESTAMP;
14358}
14359/**
14360 * Returns a placeholder value that can be used to atomically increment the
14361 * current database value by the provided delta.
14362 *
14363 * @param delta - the amount to modify the current value atomically.
14364 * @returns A placeholder value for modifying data atomically server-side.
14365 */
14366function increment(delta) {
14367 return {
14368 '.sv': {
14369 'increment': delta
14370 }
14371 };
14372}
14373
14374/**
14375 * @license
14376 * Copyright 2020 Google LLC
14377 *
14378 * Licensed under the Apache License, Version 2.0 (the "License");
14379 * you may not use this file except in compliance with the License.
14380 * You may obtain a copy of the License at
14381 *
14382 * http://www.apache.org/licenses/LICENSE-2.0
14383 *
14384 * Unless required by applicable law or agreed to in writing, software
14385 * distributed under the License is distributed on an "AS IS" BASIS,
14386 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14387 * See the License for the specific language governing permissions and
14388 * limitations under the License.
14389 */
14390/**
14391 * A type for the resolve value of {@link runTransaction}.
14392 */
14393var TransactionResult = /** @class */ (function () {
14394 /** @hideconstructor */
14395 function TransactionResult(
14396 /** Whether the transaction was successfully committed. */
14397 committed,
14398 /** The resulting data snapshot. */
14399 snapshot) {
14400 this.committed = committed;
14401 this.snapshot = snapshot;
14402 }
14403 /** Returns a JSON-serializable representation of this object. */
14404 TransactionResult.prototype.toJSON = function () {
14405 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14406 };
14407 return TransactionResult;
14408}());
14409/**
14410 * Atomically modifies the data at this location.
14411 *
14412 * Atomically modify the data at this location. Unlike a normal `set()`, which
14413 * just overwrites the data regardless of its previous value, `runTransaction()` is
14414 * used to modify the existing value to a new value, ensuring there are no
14415 * conflicts with other clients writing to the same location at the same time.
14416 *
14417 * To accomplish this, you pass `runTransaction()` an update function which is
14418 * used to transform the current value into a new value. If another client
14419 * writes to the location before your new value is successfully written, your
14420 * update function will be called again with the new current value, and the
14421 * write will be retried. This will happen repeatedly until your write succeeds
14422 * without conflict or you abort the transaction by not returning a value from
14423 * your update function.
14424 *
14425 * Note: Modifying data with `set()` will cancel any pending transactions at
14426 * that location, so extreme care should be taken if mixing `set()` and
14427 * `runTransaction()` to update the same data.
14428 *
14429 * Note: When using transactions with Security and Firebase Rules in place, be
14430 * aware that a client needs `.read` access in addition to `.write` access in
14431 * order to perform a transaction. This is because the client-side nature of
14432 * transactions requires the client to read the data in order to transactionally
14433 * update it.
14434 *
14435 * @param ref - The location to atomically modify.
14436 * @param transactionUpdate - A developer-supplied function which will be passed
14437 * the current data stored at this location (as a JavaScript object). The
14438 * function should return the new value it would like written (as a JavaScript
14439 * object). If `undefined` is returned (i.e. you return with no arguments) the
14440 * transaction will be aborted and the data at this location will not be
14441 * modified.
14442 * @param options - An options object to configure transactions.
14443 * @returns A `Promise` that can optionally be used instead of the `onComplete`
14444 * callback to handle success and failure.
14445 */
14446function runTransaction(ref,
14447// eslint-disable-next-line @typescript-eslint/no-explicit-any
14448transactionUpdate, options) {
14449 var _a;
14450 ref = getModularInstance(ref);
14451 validateWritablePath('Reference.transaction', ref._path);
14452 if (ref.key === '.length' || ref.key === '.keys') {
14453 throw ('Reference.transaction failed: ' + ref.key + ' is a read-only object.');
14454 }
14455 var applyLocally = (_a = options === null || options === void 0 ? void 0 : options.applyLocally) !== null && _a !== void 0 ? _a : true;
14456 var deferred = new Deferred();
14457 var promiseComplete = function (error, committed, node) {
14458 var dataSnapshot = null;
14459 if (error) {
14460 deferred.reject(error);
14461 }
14462 else {
14463 dataSnapshot = new DataSnapshot(node, new ReferenceImpl(ref._repo, ref._path), PRIORITY_INDEX);
14464 deferred.resolve(new TransactionResult(committed, dataSnapshot));
14465 }
14466 };
14467 // Add a watch to make sure we get server updates.
14468 var unwatcher = onValue(ref, function () { });
14469 repoStartTransaction(ref._repo, ref._path, transactionUpdate, promiseComplete, unwatcher, applyLocally);
14470 return deferred.promise;
14471}
14472
14473/**
14474 * @license
14475 * Copyright 2017 Google LLC
14476 *
14477 * Licensed under the Apache License, Version 2.0 (the "License");
14478 * you may not use this file except in compliance with the License.
14479 * You may obtain a copy of the License at
14480 *
14481 * http://www.apache.org/licenses/LICENSE-2.0
14482 *
14483 * Unless required by applicable law or agreed to in writing, software
14484 * distributed under the License is distributed on an "AS IS" BASIS,
14485 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14486 * See the License for the specific language governing permissions and
14487 * limitations under the License.
14488 */
14489// eslint-disable-next-line @typescript-eslint/no-explicit-any
14490PersistentConnection.prototype.simpleListen = function (pathString, onComplete) {
14491 this.sendRequest('q', { p: pathString }, onComplete);
14492};
14493// eslint-disable-next-line @typescript-eslint/no-explicit-any
14494PersistentConnection.prototype.echo = function (data, onEcho) {
14495 this.sendRequest('echo', { d: data }, onEcho);
14496};
14497/**
14498 * @internal
14499 */
14500var hijackHash = function (newHash) {
14501 var oldPut = PersistentConnection.prototype.put;
14502 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
14503 if (hash !== undefined) {
14504 hash = newHash();
14505 }
14506 oldPut.call(this, pathString, data, onComplete, hash);
14507 };
14508 return function () {
14509 PersistentConnection.prototype.put = oldPut;
14510 };
14511};
14512/**
14513 * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection.
14514 * @internal
14515 */
14516var forceRestClient = function (forceRestClient) {
14517 repoManagerForceRestClient(forceRestClient);
14518};
14519
14520/**
14521 * Firebase Realtime Database
14522 *
14523 * @packageDocumentation
14524 */
14525registerDatabase();
14526
14527export { DataSnapshot, Database, OnDisconnect, QueryConstraint, TransactionResult, QueryImpl as _QueryImpl, QueryParams as _QueryParams, ReferenceImpl as _ReferenceImpl, forceRestClient as _TEST_ACCESS_forceRestClient, hijackHash as _TEST_ACCESS_hijackHash, repoManagerDatabaseFromApp as _repoManagerDatabaseFromApp, setSDKVersion as _setSDKVersion, validatePathString as _validatePathString, validateWritablePath as _validateWritablePath, child, connectDatabaseEmulator, enableLogging, endAt, endBefore, equalTo, forceLongPolling, forceWebSockets, get, getDatabase, goOffline, goOnline, increment, limitToFirst, limitToLast, off, onChildAdded, onChildChanged, onChildMoved, onChildRemoved, onDisconnect, onValue, orderByChild, orderByKey, orderByPriority, orderByValue, push, query, ref, refFromURL, remove, runTransaction, serverTimestamp, set, setPriority, setWithPriority, startAfter, startAt, update };
14528//# sourceMappingURL=index.esm5.js.map