UNPKG

597 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, base64, stringToByteArray, Sha1, isNodeSdk, 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.12.0";
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);
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) {
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 return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
1905 };
1906 /**
1907 * @param onMessage - Callback when messages arrive
1908 * @param onDisconnect - Callback with connection lost.
1909 */
1910 WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
1911 var _this = this;
1912 this.onDisconnect = onDisconnect;
1913 this.onMessage = onMessage;
1914 this.log_('Websocket connecting to ' + this.connURL);
1915 this.everConnected_ = false;
1916 // Assume failure until proven otherwise.
1917 PersistentStorage.set('previous_websocket_failure', true);
1918 try {
1919 if (isNodeSdk()) {
1920 var device = this.nodeAdmin ? 'AdminNode' : 'Node';
1921 // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
1922 var options = {
1923 headers: {
1924 'User-Agent': "Firebase/" + PROTOCOL_VERSION + "/" + SDK_VERSION + "/" + process.platform + "/" + device,
1925 'X-Firebase-GMPID': this.applicationId || ''
1926 }
1927 };
1928 // If using Node with admin creds, AppCheck-related checks are unnecessary.
1929 // Note that we send the credentials here even if they aren't admin credentials, which is
1930 // not a problem.
1931 // Note that this header is just used to bypass appcheck, and the token should still be sent
1932 // through the websocket connection once it is established.
1933 if (this.authToken) {
1934 options.headers['Authorization'] = "Bearer " + this.authToken;
1935 }
1936 if (this.appCheckToken) {
1937 options.headers['X-Firebase-AppCheck'] = this.appCheckToken;
1938 }
1939 // Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
1940 var env = process['env'];
1941 var proxy = this.connURL.indexOf('wss://') === 0
1942 ? env['HTTPS_PROXY'] || env['https_proxy']
1943 : env['HTTP_PROXY'] || env['http_proxy'];
1944 if (proxy) {
1945 options['proxy'] = { origin: proxy };
1946 }
1947 this.mySock = new WebSocketImpl(this.connURL, [], options);
1948 }
1949 else {
1950 var options = {
1951 headers: {
1952 'X-Firebase-GMPID': this.applicationId || '',
1953 'X-Firebase-AppCheck': this.appCheckToken || ''
1954 }
1955 };
1956 this.mySock = new WebSocketImpl(this.connURL, [], options);
1957 }
1958 }
1959 catch (e) {
1960 this.log_('Error instantiating WebSocket.');
1961 var error = e.message || e.data;
1962 if (error) {
1963 this.log_(error);
1964 }
1965 this.onClosed_();
1966 return;
1967 }
1968 this.mySock.onopen = function () {
1969 _this.log_('Websocket connected.');
1970 _this.everConnected_ = true;
1971 };
1972 this.mySock.onclose = function () {
1973 _this.log_('Websocket connection was disconnected.');
1974 _this.mySock = null;
1975 _this.onClosed_();
1976 };
1977 this.mySock.onmessage = function (m) {
1978 _this.handleIncomingFrame(m);
1979 };
1980 this.mySock.onerror = function (e) {
1981 _this.log_('WebSocket error. Closing connection.');
1982 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1983 var error = e.message || e.data;
1984 if (error) {
1985 _this.log_(error);
1986 }
1987 _this.onClosed_();
1988 };
1989 };
1990 /**
1991 * No-op for websockets, we don't need to do anything once the connection is confirmed as open
1992 */
1993 WebSocketConnection.prototype.start = function () { };
1994 WebSocketConnection.forceDisallow = function () {
1995 WebSocketConnection.forceDisallow_ = true;
1996 };
1997 WebSocketConnection.isAvailable = function () {
1998 var isOldAndroid = false;
1999 if (typeof navigator !== 'undefined' && navigator.userAgent) {
2000 var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
2001 var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
2002 if (oldAndroidMatch && oldAndroidMatch.length > 1) {
2003 if (parseFloat(oldAndroidMatch[1]) < 4.4) {
2004 isOldAndroid = true;
2005 }
2006 }
2007 }
2008 return (!isOldAndroid &&
2009 WebSocketImpl !== null &&
2010 !WebSocketConnection.forceDisallow_);
2011 };
2012 /**
2013 * Returns true if we previously failed to connect with this transport.
2014 */
2015 WebSocketConnection.previouslyFailed = function () {
2016 // If our persistent storage is actually only in-memory storage,
2017 // we default to assuming that it previously failed to be safe.
2018 return (PersistentStorage.isInMemoryStorage ||
2019 PersistentStorage.get('previous_websocket_failure') === true);
2020 };
2021 WebSocketConnection.prototype.markConnectionHealthy = function () {
2022 PersistentStorage.remove('previous_websocket_failure');
2023 };
2024 WebSocketConnection.prototype.appendFrame_ = function (data) {
2025 this.frames.push(data);
2026 if (this.frames.length === this.totalFrames) {
2027 var fullMess = this.frames.join('');
2028 this.frames = null;
2029 var jsonMess = jsonEval(fullMess);
2030 //handle the message
2031 this.onMessage(jsonMess);
2032 }
2033 };
2034 /**
2035 * @param frameCount - The number of frames we are expecting from the server
2036 */
2037 WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
2038 this.totalFrames = frameCount;
2039 this.frames = [];
2040 };
2041 /**
2042 * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
2043 * @returns Any remaining data to be process, or null if there is none
2044 */
2045 WebSocketConnection.prototype.extractFrameCount_ = function (data) {
2046 assert(this.frames === null, 'We already have a frame buffer');
2047 // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
2048 // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
2049 if (data.length <= 6) {
2050 var frameCount = Number(data);
2051 if (!isNaN(frameCount)) {
2052 this.handleNewFrameCount_(frameCount);
2053 return null;
2054 }
2055 }
2056 this.handleNewFrameCount_(1);
2057 return data;
2058 };
2059 /**
2060 * Process a websocket frame that has arrived from the server.
2061 * @param mess - The frame data
2062 */
2063 WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
2064 if (this.mySock === null) {
2065 return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
2066 }
2067 var data = mess['data'];
2068 this.bytesReceived += data.length;
2069 this.stats_.incrementCounter('bytes_received', data.length);
2070 this.resetKeepAlive();
2071 if (this.frames !== null) {
2072 // we're buffering
2073 this.appendFrame_(data);
2074 }
2075 else {
2076 // try to parse out a frame count, otherwise, assume 1 and process it
2077 var remainingData = this.extractFrameCount_(data);
2078 if (remainingData !== null) {
2079 this.appendFrame_(remainingData);
2080 }
2081 }
2082 };
2083 /**
2084 * Send a message to the server
2085 * @param data - The JSON object to transmit
2086 */
2087 WebSocketConnection.prototype.send = function (data) {
2088 this.resetKeepAlive();
2089 var dataStr = stringify(data);
2090 this.bytesSent += dataStr.length;
2091 this.stats_.incrementCounter('bytes_sent', dataStr.length);
2092 //We can only fit a certain amount in each websocket frame, so we need to split this request
2093 //up into multiple pieces if it doesn't fit in one request.
2094 var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
2095 //Send the length header
2096 if (dataSegs.length > 1) {
2097 this.sendString_(String(dataSegs.length));
2098 }
2099 //Send the actual data in segments.
2100 for (var i = 0; i < dataSegs.length; i++) {
2101 this.sendString_(dataSegs[i]);
2102 }
2103 };
2104 WebSocketConnection.prototype.shutdown_ = function () {
2105 this.isClosed_ = true;
2106 if (this.keepaliveTimer) {
2107 clearInterval(this.keepaliveTimer);
2108 this.keepaliveTimer = null;
2109 }
2110 if (this.mySock) {
2111 this.mySock.close();
2112 this.mySock = null;
2113 }
2114 };
2115 WebSocketConnection.prototype.onClosed_ = function () {
2116 if (!this.isClosed_) {
2117 this.log_('WebSocket is closing itself');
2118 this.shutdown_();
2119 // since this is an internal close, trigger the close listener
2120 if (this.onDisconnect) {
2121 this.onDisconnect(this.everConnected_);
2122 this.onDisconnect = null;
2123 }
2124 }
2125 };
2126 /**
2127 * External-facing close handler.
2128 * Close the websocket and kill the connection.
2129 */
2130 WebSocketConnection.prototype.close = function () {
2131 if (!this.isClosed_) {
2132 this.log_('WebSocket is being closed');
2133 this.shutdown_();
2134 }
2135 };
2136 /**
2137 * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
2138 * the last activity.
2139 */
2140 WebSocketConnection.prototype.resetKeepAlive = function () {
2141 var _this = this;
2142 clearInterval(this.keepaliveTimer);
2143 this.keepaliveTimer = setInterval(function () {
2144 //If there has been no websocket activity for a while, send a no-op
2145 if (_this.mySock) {
2146 _this.sendString_('0');
2147 }
2148 _this.resetKeepAlive();
2149 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2150 }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
2151 };
2152 /**
2153 * Send a string over the websocket.
2154 *
2155 * @param str - String to send.
2156 */
2157 WebSocketConnection.prototype.sendString_ = function (str) {
2158 // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
2159 // calls for some unknown reason. We treat these as an error and disconnect.
2160 // See https://app.asana.com/0/58926111402292/68021340250410
2161 try {
2162 this.mySock.send(str);
2163 }
2164 catch (e) {
2165 this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
2166 setTimeout(this.onClosed_.bind(this), 0);
2167 }
2168 };
2169 /**
2170 * Number of response before we consider the connection "healthy."
2171 */
2172 WebSocketConnection.responsesRequiredToBeHealthy = 2;
2173 /**
2174 * Time to wait for the connection te become healthy before giving up.
2175 */
2176 WebSocketConnection.healthyTimeout = 30000;
2177 return WebSocketConnection;
2178}());
2179
2180/**
2181 * @license
2182 * Copyright 2017 Google LLC
2183 *
2184 * Licensed under the Apache License, Version 2.0 (the "License");
2185 * you may not use this file except in compliance with the License.
2186 * You may obtain a copy of the License at
2187 *
2188 * http://www.apache.org/licenses/LICENSE-2.0
2189 *
2190 * Unless required by applicable law or agreed to in writing, software
2191 * distributed under the License is distributed on an "AS IS" BASIS,
2192 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2193 * See the License for the specific language governing permissions and
2194 * limitations under the License.
2195 */
2196/**
2197 * Currently simplistic, this class manages what transport a Connection should use at various stages of its
2198 * lifecycle.
2199 *
2200 * It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
2201 * they are available.
2202 */
2203var TransportManager = /** @class */ (function () {
2204 /**
2205 * @param repoInfo - Metadata around the namespace we're connecting to
2206 */
2207 function TransportManager(repoInfo) {
2208 this.initTransports_(repoInfo);
2209 }
2210 Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
2211 get: function () {
2212 return [BrowserPollConnection, WebSocketConnection];
2213 },
2214 enumerable: false,
2215 configurable: true
2216 });
2217 TransportManager.prototype.initTransports_ = function (repoInfo) {
2218 var e_1, _a;
2219 var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
2220 var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
2221 if (repoInfo.webSocketOnly) {
2222 if (!isWebSocketsAvailable) {
2223 warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
2224 }
2225 isSkipPollConnection = true;
2226 }
2227 if (isSkipPollConnection) {
2228 this.transports_ = [WebSocketConnection];
2229 }
2230 else {
2231 var transports = (this.transports_ = []);
2232 try {
2233 for (var _b = __values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
2234 var transport = _c.value;
2235 if (transport && transport['isAvailable']()) {
2236 transports.push(transport);
2237 }
2238 }
2239 }
2240 catch (e_1_1) { e_1 = { error: e_1_1 }; }
2241 finally {
2242 try {
2243 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
2244 }
2245 finally { if (e_1) throw e_1.error; }
2246 }
2247 }
2248 };
2249 /**
2250 * @returns The constructor for the initial transport to use
2251 */
2252 TransportManager.prototype.initialTransport = function () {
2253 if (this.transports_.length > 0) {
2254 return this.transports_[0];
2255 }
2256 else {
2257 throw new Error('No transports available');
2258 }
2259 };
2260 /**
2261 * @returns The constructor for the next transport, or null
2262 */
2263 TransportManager.prototype.upgradeTransport = function () {
2264 if (this.transports_.length > 1) {
2265 return this.transports_[1];
2266 }
2267 else {
2268 return null;
2269 }
2270 };
2271 return TransportManager;
2272}());
2273
2274/**
2275 * @license
2276 * Copyright 2017 Google LLC
2277 *
2278 * Licensed under the Apache License, Version 2.0 (the "License");
2279 * you may not use this file except in compliance with the License.
2280 * You may obtain a copy of the License at
2281 *
2282 * http://www.apache.org/licenses/LICENSE-2.0
2283 *
2284 * Unless required by applicable law or agreed to in writing, software
2285 * distributed under the License is distributed on an "AS IS" BASIS,
2286 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2287 * See the License for the specific language governing permissions and
2288 * limitations under the License.
2289 */
2290// Abort upgrade attempt if it takes longer than 60s.
2291var UPGRADE_TIMEOUT = 60000;
2292// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
2293// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
2294var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
2295// 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)
2296// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
2297// but we've sent/received enough bytes, we don't cancel the connection.
2298var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
2299var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
2300var MESSAGE_TYPE = 't';
2301var MESSAGE_DATA = 'd';
2302var CONTROL_SHUTDOWN = 's';
2303var CONTROL_RESET = 'r';
2304var CONTROL_ERROR = 'e';
2305var CONTROL_PONG = 'o';
2306var SWITCH_ACK = 'a';
2307var END_TRANSMISSION = 'n';
2308var PING = 'p';
2309var SERVER_HELLO = 'h';
2310/**
2311 * Creates a new real-time connection to the server using whichever method works
2312 * best in the current browser.
2313 */
2314var Connection = /** @class */ (function () {
2315 /**
2316 * @param id - an id for this connection
2317 * @param repoInfo_ - the info for the endpoint to connect to
2318 * @param applicationId_ - the Firebase App ID for this project
2319 * @param appCheckToken_ - The App Check Token for this device.
2320 * @param authToken_ - The auth token for this session.
2321 * @param onMessage_ - the callback to be triggered when a server-push message arrives
2322 * @param onReady_ - the callback to be triggered when this connection is ready to send messages.
2323 * @param onDisconnect_ - the callback to be triggered when a connection was lost
2324 * @param onKill_ - the callback to be triggered when this connection has permanently shut down.
2325 * @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
2326 */
2327 function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
2328 this.id = id;
2329 this.repoInfo_ = repoInfo_;
2330 this.applicationId_ = applicationId_;
2331 this.appCheckToken_ = appCheckToken_;
2332 this.authToken_ = authToken_;
2333 this.onMessage_ = onMessage_;
2334 this.onReady_ = onReady_;
2335 this.onDisconnect_ = onDisconnect_;
2336 this.onKill_ = onKill_;
2337 this.lastSessionId = lastSessionId;
2338 this.connectionCount = 0;
2339 this.pendingDataMessages = [];
2340 this.state_ = 0 /* CONNECTING */;
2341 this.log_ = logWrapper('c:' + this.id + ':');
2342 this.transportManager_ = new TransportManager(repoInfo_);
2343 this.log_('Connection created');
2344 this.start_();
2345 }
2346 /**
2347 * Starts a connection attempt
2348 */
2349 Connection.prototype.start_ = function () {
2350 var _this = this;
2351 var conn = this.transportManager_.initialTransport();
2352 this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, null, this.lastSessionId);
2353 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2354 // can consider the transport healthy.
2355 this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
2356 var onMessageReceived = this.connReceiver_(this.conn_);
2357 var onConnectionLost = this.disconnReceiver_(this.conn_);
2358 this.tx_ = this.conn_;
2359 this.rx_ = this.conn_;
2360 this.secondaryConn_ = null;
2361 this.isHealthy_ = false;
2362 /*
2363 * Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
2364 * This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
2365 * Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
2366 * still have the context of your originating frame.
2367 */
2368 setTimeout(function () {
2369 // this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
2370 _this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
2371 }, Math.floor(0));
2372 var healthyTimeoutMS = conn['healthyTimeout'] || 0;
2373 if (healthyTimeoutMS > 0) {
2374 this.healthyTimeout_ = setTimeoutNonBlocking(function () {
2375 _this.healthyTimeout_ = null;
2376 if (!_this.isHealthy_) {
2377 if (_this.conn_ &&
2378 _this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
2379 _this.log_('Connection exceeded healthy timeout but has received ' +
2380 _this.conn_.bytesReceived +
2381 ' bytes. Marking connection healthy.');
2382 _this.isHealthy_ = true;
2383 _this.conn_.markConnectionHealthy();
2384 }
2385 else if (_this.conn_ &&
2386 _this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
2387 _this.log_('Connection exceeded healthy timeout but has sent ' +
2388 _this.conn_.bytesSent +
2389 ' bytes. Leaving connection alive.');
2390 // NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
2391 // the server.
2392 }
2393 else {
2394 _this.log_('Closing unhealthy connection after timeout.');
2395 _this.close();
2396 }
2397 }
2398 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2399 }, Math.floor(healthyTimeoutMS));
2400 }
2401 };
2402 Connection.prototype.nextTransportId_ = function () {
2403 return 'c:' + this.id + ':' + this.connectionCount++;
2404 };
2405 Connection.prototype.disconnReceiver_ = function (conn) {
2406 var _this = this;
2407 return function (everConnected) {
2408 if (conn === _this.conn_) {
2409 _this.onConnectionLost_(everConnected);
2410 }
2411 else if (conn === _this.secondaryConn_) {
2412 _this.log_('Secondary connection lost.');
2413 _this.onSecondaryConnectionLost_();
2414 }
2415 else {
2416 _this.log_('closing an old connection');
2417 }
2418 };
2419 };
2420 Connection.prototype.connReceiver_ = function (conn) {
2421 var _this = this;
2422 return function (message) {
2423 if (_this.state_ !== 2 /* DISCONNECTED */) {
2424 if (conn === _this.rx_) {
2425 _this.onPrimaryMessageReceived_(message);
2426 }
2427 else if (conn === _this.secondaryConn_) {
2428 _this.onSecondaryMessageReceived_(message);
2429 }
2430 else {
2431 _this.log_('message on old connection');
2432 }
2433 }
2434 };
2435 };
2436 /**
2437 * @param dataMsg - An arbitrary data message to be sent to the server
2438 */
2439 Connection.prototype.sendRequest = function (dataMsg) {
2440 // wrap in a data message envelope and send it on
2441 var msg = { t: 'd', d: dataMsg };
2442 this.sendData_(msg);
2443 };
2444 Connection.prototype.tryCleanupConnection = function () {
2445 if (this.tx_ === this.secondaryConn_ && this.rx_ === this.secondaryConn_) {
2446 this.log_('cleaning up and promoting a connection: ' + this.secondaryConn_.connId);
2447 this.conn_ = this.secondaryConn_;
2448 this.secondaryConn_ = null;
2449 // the server will shutdown the old connection
2450 }
2451 };
2452 Connection.prototype.onSecondaryControl_ = function (controlData) {
2453 if (MESSAGE_TYPE in controlData) {
2454 var cmd = controlData[MESSAGE_TYPE];
2455 if (cmd === SWITCH_ACK) {
2456 this.upgradeIfSecondaryHealthy_();
2457 }
2458 else if (cmd === CONTROL_RESET) {
2459 // Most likely the session wasn't valid. Abandon the switch attempt
2460 this.log_('Got a reset on secondary, closing it');
2461 this.secondaryConn_.close();
2462 // If we were already using this connection for something, than we need to fully close
2463 if (this.tx_ === this.secondaryConn_ ||
2464 this.rx_ === this.secondaryConn_) {
2465 this.close();
2466 }
2467 }
2468 else if (cmd === CONTROL_PONG) {
2469 this.log_('got pong on secondary.');
2470 this.secondaryResponsesRequired_--;
2471 this.upgradeIfSecondaryHealthy_();
2472 }
2473 }
2474 };
2475 Connection.prototype.onSecondaryMessageReceived_ = function (parsedData) {
2476 var layer = requireKey('t', parsedData);
2477 var data = requireKey('d', parsedData);
2478 if (layer === 'c') {
2479 this.onSecondaryControl_(data);
2480 }
2481 else if (layer === 'd') {
2482 // got a data message, but we're still second connection. Need to buffer it up
2483 this.pendingDataMessages.push(data);
2484 }
2485 else {
2486 throw new Error('Unknown protocol layer: ' + layer);
2487 }
2488 };
2489 Connection.prototype.upgradeIfSecondaryHealthy_ = function () {
2490 if (this.secondaryResponsesRequired_ <= 0) {
2491 this.log_('Secondary connection is healthy.');
2492 this.isHealthy_ = true;
2493 this.secondaryConn_.markConnectionHealthy();
2494 this.proceedWithUpgrade_();
2495 }
2496 else {
2497 // Send a ping to make sure the connection is healthy.
2498 this.log_('sending ping on secondary.');
2499 this.secondaryConn_.send({ t: 'c', d: { t: PING, d: {} } });
2500 }
2501 };
2502 Connection.prototype.proceedWithUpgrade_ = function () {
2503 // tell this connection to consider itself open
2504 this.secondaryConn_.start();
2505 // send ack
2506 this.log_('sending client ack on secondary');
2507 this.secondaryConn_.send({ t: 'c', d: { t: SWITCH_ACK, d: {} } });
2508 // send end packet on primary transport, switch to sending on this one
2509 // can receive on this one, buffer responses until end received on primary transport
2510 this.log_('Ending transmission on primary');
2511 this.conn_.send({ t: 'c', d: { t: END_TRANSMISSION, d: {} } });
2512 this.tx_ = this.secondaryConn_;
2513 this.tryCleanupConnection();
2514 };
2515 Connection.prototype.onPrimaryMessageReceived_ = function (parsedData) {
2516 // Must refer to parsedData properties in quotes, so closure doesn't touch them.
2517 var layer = requireKey('t', parsedData);
2518 var data = requireKey('d', parsedData);
2519 if (layer === 'c') {
2520 this.onControl_(data);
2521 }
2522 else if (layer === 'd') {
2523 this.onDataMessage_(data);
2524 }
2525 };
2526 Connection.prototype.onDataMessage_ = function (message) {
2527 this.onPrimaryResponse_();
2528 // We don't do anything with data messages, just kick them up a level
2529 this.onMessage_(message);
2530 };
2531 Connection.prototype.onPrimaryResponse_ = function () {
2532 if (!this.isHealthy_) {
2533 this.primaryResponsesRequired_--;
2534 if (this.primaryResponsesRequired_ <= 0) {
2535 this.log_('Primary connection is healthy.');
2536 this.isHealthy_ = true;
2537 this.conn_.markConnectionHealthy();
2538 }
2539 }
2540 };
2541 Connection.prototype.onControl_ = function (controlData) {
2542 var cmd = requireKey(MESSAGE_TYPE, controlData);
2543 if (MESSAGE_DATA in controlData) {
2544 var payload = controlData[MESSAGE_DATA];
2545 if (cmd === SERVER_HELLO) {
2546 this.onHandshake_(payload);
2547 }
2548 else if (cmd === END_TRANSMISSION) {
2549 this.log_('recvd end transmission on primary');
2550 this.rx_ = this.secondaryConn_;
2551 for (var i = 0; i < this.pendingDataMessages.length; ++i) {
2552 this.onDataMessage_(this.pendingDataMessages[i]);
2553 }
2554 this.pendingDataMessages = [];
2555 this.tryCleanupConnection();
2556 }
2557 else if (cmd === CONTROL_SHUTDOWN) {
2558 // This was previously the 'onKill' callback passed to the lower-level connection
2559 // payload in this case is the reason for the shutdown. Generally a human-readable error
2560 this.onConnectionShutdown_(payload);
2561 }
2562 else if (cmd === CONTROL_RESET) {
2563 // payload in this case is the host we should contact
2564 this.onReset_(payload);
2565 }
2566 else if (cmd === CONTROL_ERROR) {
2567 error('Server Error: ' + payload);
2568 }
2569 else if (cmd === CONTROL_PONG) {
2570 this.log_('got pong on primary.');
2571 this.onPrimaryResponse_();
2572 this.sendPingOnPrimaryIfNecessary_();
2573 }
2574 else {
2575 error('Unknown control packet command: ' + cmd);
2576 }
2577 }
2578 };
2579 /**
2580 * @param handshake - The handshake data returned from the server
2581 */
2582 Connection.prototype.onHandshake_ = function (handshake) {
2583 var timestamp = handshake.ts;
2584 var version = handshake.v;
2585 var host = handshake.h;
2586 this.sessionId = handshake.s;
2587 this.repoInfo_.host = host;
2588 // if we've already closed the connection, then don't bother trying to progress further
2589 if (this.state_ === 0 /* CONNECTING */) {
2590 this.conn_.start();
2591 this.onConnectionEstablished_(this.conn_, timestamp);
2592 if (PROTOCOL_VERSION !== version) {
2593 warn('Protocol version mismatch detected');
2594 }
2595 // TODO: do we want to upgrade? when? maybe a delay?
2596 this.tryStartUpgrade_();
2597 }
2598 };
2599 Connection.prototype.tryStartUpgrade_ = function () {
2600 var conn = this.transportManager_.upgradeTransport();
2601 if (conn) {
2602 this.startUpgrade_(conn);
2603 }
2604 };
2605 Connection.prototype.startUpgrade_ = function (conn) {
2606 var _this = this;
2607 this.secondaryConn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, this.sessionId);
2608 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2609 // can consider the transport healthy.
2610 this.secondaryResponsesRequired_ =
2611 conn['responsesRequiredToBeHealthy'] || 0;
2612 var onMessage = this.connReceiver_(this.secondaryConn_);
2613 var onDisconnect = this.disconnReceiver_(this.secondaryConn_);
2614 this.secondaryConn_.open(onMessage, onDisconnect);
2615 // If we haven't successfully upgraded after UPGRADE_TIMEOUT, give up and kill the secondary.
2616 setTimeoutNonBlocking(function () {
2617 if (_this.secondaryConn_) {
2618 _this.log_('Timed out trying to upgrade.');
2619 _this.secondaryConn_.close();
2620 }
2621 }, Math.floor(UPGRADE_TIMEOUT));
2622 };
2623 Connection.prototype.onReset_ = function (host) {
2624 this.log_('Reset packet received. New host: ' + host);
2625 this.repoInfo_.host = host;
2626 // TODO: if we're already "connected", we need to trigger a disconnect at the next layer up.
2627 // We don't currently support resets after the connection has already been established
2628 if (this.state_ === 1 /* CONNECTED */) {
2629 this.close();
2630 }
2631 else {
2632 // Close whatever connections we have open and start again.
2633 this.closeConnections_();
2634 this.start_();
2635 }
2636 };
2637 Connection.prototype.onConnectionEstablished_ = function (conn, timestamp) {
2638 var _this = this;
2639 this.log_('Realtime connection established.');
2640 this.conn_ = conn;
2641 this.state_ = 1 /* CONNECTED */;
2642 if (this.onReady_) {
2643 this.onReady_(timestamp, this.sessionId);
2644 this.onReady_ = null;
2645 }
2646 // If after 5 seconds we haven't sent enough requests to the server to get the connection healthy,
2647 // send some pings.
2648 if (this.primaryResponsesRequired_ === 0) {
2649 this.log_('Primary connection is healthy.');
2650 this.isHealthy_ = true;
2651 }
2652 else {
2653 setTimeoutNonBlocking(function () {
2654 _this.sendPingOnPrimaryIfNecessary_();
2655 }, Math.floor(DELAY_BEFORE_SENDING_EXTRA_REQUESTS));
2656 }
2657 };
2658 Connection.prototype.sendPingOnPrimaryIfNecessary_ = function () {
2659 // If the connection isn't considered healthy yet, we'll send a noop ping packet request.
2660 if (!this.isHealthy_ && this.state_ === 1 /* CONNECTED */) {
2661 this.log_('sending ping on primary.');
2662 this.sendData_({ t: 'c', d: { t: PING, d: {} } });
2663 }
2664 };
2665 Connection.prototype.onSecondaryConnectionLost_ = function () {
2666 var conn = this.secondaryConn_;
2667 this.secondaryConn_ = null;
2668 if (this.tx_ === conn || this.rx_ === conn) {
2669 // we are relying on this connection already in some capacity. Therefore, a failure is real
2670 this.close();
2671 }
2672 };
2673 /**
2674 * @param everConnected - Whether or not the connection ever reached a server. Used to determine if
2675 * we should flush the host cache
2676 */
2677 Connection.prototype.onConnectionLost_ = function (everConnected) {
2678 this.conn_ = null;
2679 // NOTE: IF you're seeing a Firefox error for this line, I think it might be because it's getting
2680 // called on window close and RealtimeState.CONNECTING is no longer defined. Just a guess.
2681 if (!everConnected && this.state_ === 0 /* CONNECTING */) {
2682 this.log_('Realtime connection failed.');
2683 // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
2684 if (this.repoInfo_.isCacheableHost()) {
2685 PersistentStorage.remove('host:' + this.repoInfo_.host);
2686 // reset the internal host to what we would show the user, i.e. <ns>.firebaseio.com
2687 this.repoInfo_.internalHost = this.repoInfo_.host;
2688 }
2689 }
2690 else if (this.state_ === 1 /* CONNECTED */) {
2691 this.log_('Realtime connection lost.');
2692 }
2693 this.close();
2694 };
2695 Connection.prototype.onConnectionShutdown_ = function (reason) {
2696 this.log_('Connection shutdown command received. Shutting down...');
2697 if (this.onKill_) {
2698 this.onKill_(reason);
2699 this.onKill_ = null;
2700 }
2701 // We intentionally don't want to fire onDisconnect (kill is a different case),
2702 // so clear the callback.
2703 this.onDisconnect_ = null;
2704 this.close();
2705 };
2706 Connection.prototype.sendData_ = function (data) {
2707 if (this.state_ !== 1 /* CONNECTED */) {
2708 throw 'Connection is not connected';
2709 }
2710 else {
2711 this.tx_.send(data);
2712 }
2713 };
2714 /**
2715 * Cleans up this connection, calling the appropriate callbacks
2716 */
2717 Connection.prototype.close = function () {
2718 if (this.state_ !== 2 /* DISCONNECTED */) {
2719 this.log_('Closing realtime connection.');
2720 this.state_ = 2 /* DISCONNECTED */;
2721 this.closeConnections_();
2722 if (this.onDisconnect_) {
2723 this.onDisconnect_();
2724 this.onDisconnect_ = null;
2725 }
2726 }
2727 };
2728 Connection.prototype.closeConnections_ = function () {
2729 this.log_('Shutting down all connections');
2730 if (this.conn_) {
2731 this.conn_.close();
2732 this.conn_ = null;
2733 }
2734 if (this.secondaryConn_) {
2735 this.secondaryConn_.close();
2736 this.secondaryConn_ = null;
2737 }
2738 if (this.healthyTimeout_) {
2739 clearTimeout(this.healthyTimeout_);
2740 this.healthyTimeout_ = null;
2741 }
2742 };
2743 return Connection;
2744}());
2745
2746/**
2747 * @license
2748 * Copyright 2017 Google LLC
2749 *
2750 * Licensed under the Apache License, Version 2.0 (the "License");
2751 * you may not use this file except in compliance with the License.
2752 * You may obtain a copy of the License at
2753 *
2754 * http://www.apache.org/licenses/LICENSE-2.0
2755 *
2756 * Unless required by applicable law or agreed to in writing, software
2757 * distributed under the License is distributed on an "AS IS" BASIS,
2758 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2759 * See the License for the specific language governing permissions and
2760 * limitations under the License.
2761 */
2762/**
2763 * Interface defining the set of actions that can be performed against the Firebase server
2764 * (basically corresponds to our wire protocol).
2765 *
2766 * @interface
2767 */
2768var ServerActions = /** @class */ (function () {
2769 function ServerActions() {
2770 }
2771 ServerActions.prototype.put = function (pathString, data, onComplete, hash) { };
2772 ServerActions.prototype.merge = function (pathString, data, onComplete, hash) { };
2773 /**
2774 * Refreshes the auth token for the current connection.
2775 * @param token - The authentication token
2776 */
2777 ServerActions.prototype.refreshAuthToken = function (token) { };
2778 /**
2779 * Refreshes the app check token for the current connection.
2780 * @param token The app check token
2781 */
2782 ServerActions.prototype.refreshAppCheckToken = function (token) { };
2783 ServerActions.prototype.onDisconnectPut = function (pathString, data, onComplete) { };
2784 ServerActions.prototype.onDisconnectMerge = function (pathString, data, onComplete) { };
2785 ServerActions.prototype.onDisconnectCancel = function (pathString, onComplete) { };
2786 ServerActions.prototype.reportStats = function (stats) { };
2787 return ServerActions;
2788}());
2789
2790/**
2791 * @license
2792 * Copyright 2017 Google LLC
2793 *
2794 * Licensed under the Apache License, Version 2.0 (the "License");
2795 * you may not use this file except in compliance with the License.
2796 * You may obtain a copy of the License at
2797 *
2798 * http://www.apache.org/licenses/LICENSE-2.0
2799 *
2800 * Unless required by applicable law or agreed to in writing, software
2801 * distributed under the License is distributed on an "AS IS" BASIS,
2802 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2803 * See the License for the specific language governing permissions and
2804 * limitations under the License.
2805 */
2806/**
2807 * Base class to be used if you want to emit events. Call the constructor with
2808 * the set of allowed event names.
2809 */
2810var EventEmitter = /** @class */ (function () {
2811 function EventEmitter(allowedEvents_) {
2812 this.allowedEvents_ = allowedEvents_;
2813 this.listeners_ = {};
2814 assert(Array.isArray(allowedEvents_) && allowedEvents_.length > 0, 'Requires a non-empty array');
2815 }
2816 /**
2817 * To be called by derived classes to trigger events.
2818 */
2819 EventEmitter.prototype.trigger = function (eventType) {
2820 var varArgs = [];
2821 for (var _i = 1; _i < arguments.length; _i++) {
2822 varArgs[_i - 1] = arguments[_i];
2823 }
2824 if (Array.isArray(this.listeners_[eventType])) {
2825 // Clone the list, since callbacks could add/remove listeners.
2826 var listeners = __spreadArray([], __read(this.listeners_[eventType]));
2827 for (var i = 0; i < listeners.length; i++) {
2828 listeners[i].callback.apply(listeners[i].context, varArgs);
2829 }
2830 }
2831 };
2832 EventEmitter.prototype.on = function (eventType, callback, context) {
2833 this.validateEventType_(eventType);
2834 this.listeners_[eventType] = this.listeners_[eventType] || [];
2835 this.listeners_[eventType].push({ callback: callback, context: context });
2836 var eventData = this.getInitialEvent(eventType);
2837 if (eventData) {
2838 callback.apply(context, eventData);
2839 }
2840 };
2841 EventEmitter.prototype.off = function (eventType, callback, context) {
2842 this.validateEventType_(eventType);
2843 var listeners = this.listeners_[eventType] || [];
2844 for (var i = 0; i < listeners.length; i++) {
2845 if (listeners[i].callback === callback &&
2846 (!context || context === listeners[i].context)) {
2847 listeners.splice(i, 1);
2848 return;
2849 }
2850 }
2851 };
2852 EventEmitter.prototype.validateEventType_ = function (eventType) {
2853 assert(this.allowedEvents_.find(function (et) {
2854 return et === eventType;
2855 }), 'Unknown event: ' + eventType);
2856 };
2857 return EventEmitter;
2858}());
2859
2860/**
2861 * @license
2862 * Copyright 2017 Google LLC
2863 *
2864 * Licensed under the Apache License, Version 2.0 (the "License");
2865 * you may not use this file except in compliance with the License.
2866 * You may obtain a copy of the License at
2867 *
2868 * http://www.apache.org/licenses/LICENSE-2.0
2869 *
2870 * Unless required by applicable law or agreed to in writing, software
2871 * distributed under the License is distributed on an "AS IS" BASIS,
2872 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2873 * See the License for the specific language governing permissions and
2874 * limitations under the License.
2875 */
2876/**
2877 * Monitors online state (as reported by window.online/offline events).
2878 *
2879 * The expectation is that this could have many false positives (thinks we are online
2880 * when we're not), but no false negatives. So we can safely use it to determine when
2881 * we definitely cannot reach the internet.
2882 */
2883var OnlineMonitor = /** @class */ (function (_super) {
2884 __extends(OnlineMonitor, _super);
2885 function OnlineMonitor() {
2886 var _this = _super.call(this, ['online']) || this;
2887 _this.online_ = true;
2888 // We've had repeated complaints that Cordova apps can get stuck "offline", e.g.
2889 // https://forum.ionicframework.com/t/firebase-connection-is-lost-and-never-come-back/43810
2890 // It would seem that the 'online' event does not always fire consistently. So we disable it
2891 // for Cordova.
2892 if (typeof window !== 'undefined' &&
2893 typeof window.addEventListener !== 'undefined' &&
2894 !isMobileCordova()) {
2895 window.addEventListener('online', function () {
2896 if (!_this.online_) {
2897 _this.online_ = true;
2898 _this.trigger('online', true);
2899 }
2900 }, false);
2901 window.addEventListener('offline', function () {
2902 if (_this.online_) {
2903 _this.online_ = false;
2904 _this.trigger('online', false);
2905 }
2906 }, false);
2907 }
2908 return _this;
2909 }
2910 OnlineMonitor.getInstance = function () {
2911 return new OnlineMonitor();
2912 };
2913 OnlineMonitor.prototype.getInitialEvent = function (eventType) {
2914 assert(eventType === 'online', 'Unknown event type: ' + eventType);
2915 return [this.online_];
2916 };
2917 OnlineMonitor.prototype.currentlyOnline = function () {
2918 return this.online_;
2919 };
2920 return OnlineMonitor;
2921}(EventEmitter));
2922
2923/**
2924 * @license
2925 * Copyright 2017 Google LLC
2926 *
2927 * Licensed under the Apache License, Version 2.0 (the "License");
2928 * you may not use this file except in compliance with the License.
2929 * You may obtain a copy of the License at
2930 *
2931 * http://www.apache.org/licenses/LICENSE-2.0
2932 *
2933 * Unless required by applicable law or agreed to in writing, software
2934 * distributed under the License is distributed on an "AS IS" BASIS,
2935 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2936 * See the License for the specific language governing permissions and
2937 * limitations under the License.
2938 */
2939/** Maximum key depth. */
2940var MAX_PATH_DEPTH = 32;
2941/** Maximum number of (UTF8) bytes in a Firebase path. */
2942var MAX_PATH_LENGTH_BYTES = 768;
2943/**
2944 * An immutable object representing a parsed path. It's immutable so that you
2945 * can pass them around to other functions without worrying about them changing
2946 * it.
2947 */
2948var Path = /** @class */ (function () {
2949 /**
2950 * @param pathOrString - Path string to parse, or another path, or the raw
2951 * tokens array
2952 */
2953 function Path(pathOrString, pieceNum) {
2954 if (pieceNum === void 0) {
2955 this.pieces_ = pathOrString.split('/');
2956 // Remove empty pieces.
2957 var copyTo = 0;
2958 for (var i = 0; i < this.pieces_.length; i++) {
2959 if (this.pieces_[i].length > 0) {
2960 this.pieces_[copyTo] = this.pieces_[i];
2961 copyTo++;
2962 }
2963 }
2964 this.pieces_.length = copyTo;
2965 this.pieceNum_ = 0;
2966 }
2967 else {
2968 this.pieces_ = pathOrString;
2969 this.pieceNum_ = pieceNum;
2970 }
2971 }
2972 Path.prototype.toString = function () {
2973 var pathString = '';
2974 for (var i = this.pieceNum_; i < this.pieces_.length; i++) {
2975 if (this.pieces_[i] !== '') {
2976 pathString += '/' + this.pieces_[i];
2977 }
2978 }
2979 return pathString || '/';
2980 };
2981 return Path;
2982}());
2983function newEmptyPath() {
2984 return new Path('');
2985}
2986function pathGetFront(path) {
2987 if (path.pieceNum_ >= path.pieces_.length) {
2988 return null;
2989 }
2990 return path.pieces_[path.pieceNum_];
2991}
2992/**
2993 * @returns The number of segments in this path
2994 */
2995function pathGetLength(path) {
2996 return path.pieces_.length - path.pieceNum_;
2997}
2998function pathPopFront(path) {
2999 var pieceNum = path.pieceNum_;
3000 if (pieceNum < path.pieces_.length) {
3001 pieceNum++;
3002 }
3003 return new Path(path.pieces_, pieceNum);
3004}
3005function pathGetBack(path) {
3006 if (path.pieceNum_ < path.pieces_.length) {
3007 return path.pieces_[path.pieces_.length - 1];
3008 }
3009 return null;
3010}
3011function pathToUrlEncodedString(path) {
3012 var pathString = '';
3013 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3014 if (path.pieces_[i] !== '') {
3015 pathString += '/' + encodeURIComponent(String(path.pieces_[i]));
3016 }
3017 }
3018 return pathString || '/';
3019}
3020/**
3021 * Shallow copy of the parts of the path.
3022 *
3023 */
3024function pathSlice(path, begin) {
3025 if (begin === void 0) { begin = 0; }
3026 return path.pieces_.slice(path.pieceNum_ + begin);
3027}
3028function pathParent(path) {
3029 if (path.pieceNum_ >= path.pieces_.length) {
3030 return null;
3031 }
3032 var pieces = [];
3033 for (var i = path.pieceNum_; i < path.pieces_.length - 1; i++) {
3034 pieces.push(path.pieces_[i]);
3035 }
3036 return new Path(pieces, 0);
3037}
3038function pathChild(path, childPathObj) {
3039 var pieces = [];
3040 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3041 pieces.push(path.pieces_[i]);
3042 }
3043 if (childPathObj instanceof Path) {
3044 for (var i = childPathObj.pieceNum_; i < childPathObj.pieces_.length; i++) {
3045 pieces.push(childPathObj.pieces_[i]);
3046 }
3047 }
3048 else {
3049 var childPieces = childPathObj.split('/');
3050 for (var i = 0; i < childPieces.length; i++) {
3051 if (childPieces[i].length > 0) {
3052 pieces.push(childPieces[i]);
3053 }
3054 }
3055 }
3056 return new Path(pieces, 0);
3057}
3058/**
3059 * @returns True if there are no segments in this path
3060 */
3061function pathIsEmpty(path) {
3062 return path.pieceNum_ >= path.pieces_.length;
3063}
3064/**
3065 * @returns The path from outerPath to innerPath
3066 */
3067function newRelativePath(outerPath, innerPath) {
3068 var outer = pathGetFront(outerPath), inner = pathGetFront(innerPath);
3069 if (outer === null) {
3070 return innerPath;
3071 }
3072 else if (outer === inner) {
3073 return newRelativePath(pathPopFront(outerPath), pathPopFront(innerPath));
3074 }
3075 else {
3076 throw new Error('INTERNAL ERROR: innerPath (' +
3077 innerPath +
3078 ') is not within ' +
3079 'outerPath (' +
3080 outerPath +
3081 ')');
3082 }
3083}
3084/**
3085 * @returns -1, 0, 1 if left is less, equal, or greater than the right.
3086 */
3087function pathCompare(left, right) {
3088 var leftKeys = pathSlice(left, 0);
3089 var rightKeys = pathSlice(right, 0);
3090 for (var i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
3091 var cmp = nameCompare(leftKeys[i], rightKeys[i]);
3092 if (cmp !== 0) {
3093 return cmp;
3094 }
3095 }
3096 if (leftKeys.length === rightKeys.length) {
3097 return 0;
3098 }
3099 return leftKeys.length < rightKeys.length ? -1 : 1;
3100}
3101/**
3102 * @returns true if paths are the same.
3103 */
3104function pathEquals(path, other) {
3105 if (pathGetLength(path) !== pathGetLength(other)) {
3106 return false;
3107 }
3108 for (var i = path.pieceNum_, j = other.pieceNum_; i <= path.pieces_.length; i++, j++) {
3109 if (path.pieces_[i] !== other.pieces_[j]) {
3110 return false;
3111 }
3112 }
3113 return true;
3114}
3115/**
3116 * @returns True if this path is a parent (or the same as) other
3117 */
3118function pathContains(path, other) {
3119 var i = path.pieceNum_;
3120 var j = other.pieceNum_;
3121 if (pathGetLength(path) > pathGetLength(other)) {
3122 return false;
3123 }
3124 while (i < path.pieces_.length) {
3125 if (path.pieces_[i] !== other.pieces_[j]) {
3126 return false;
3127 }
3128 ++i;
3129 ++j;
3130 }
3131 return true;
3132}
3133/**
3134 * Dynamic (mutable) path used to count path lengths.
3135 *
3136 * This class is used to efficiently check paths for valid
3137 * length (in UTF8 bytes) and depth (used in path validation).
3138 *
3139 * Throws Error exception if path is ever invalid.
3140 *
3141 * The definition of a path always begins with '/'.
3142 */
3143var ValidationPath = /** @class */ (function () {
3144 /**
3145 * @param path - Initial Path.
3146 * @param errorPrefix_ - Prefix for any error messages.
3147 */
3148 function ValidationPath(path, errorPrefix_) {
3149 this.errorPrefix_ = errorPrefix_;
3150 this.parts_ = pathSlice(path, 0);
3151 /** Initialize to number of '/' chars needed in path. */
3152 this.byteLength_ = Math.max(1, this.parts_.length);
3153 for (var i = 0; i < this.parts_.length; i++) {
3154 this.byteLength_ += stringLength(this.parts_[i]);
3155 }
3156 validationPathCheckValid(this);
3157 }
3158 return ValidationPath;
3159}());
3160function validationPathPush(validationPath, child) {
3161 // Count the needed '/'
3162 if (validationPath.parts_.length > 0) {
3163 validationPath.byteLength_ += 1;
3164 }
3165 validationPath.parts_.push(child);
3166 validationPath.byteLength_ += stringLength(child);
3167 validationPathCheckValid(validationPath);
3168}
3169function validationPathPop(validationPath) {
3170 var last = validationPath.parts_.pop();
3171 validationPath.byteLength_ -= stringLength(last);
3172 // Un-count the previous '/'
3173 if (validationPath.parts_.length > 0) {
3174 validationPath.byteLength_ -= 1;
3175 }
3176}
3177function validationPathCheckValid(validationPath) {
3178 if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
3179 throw new Error(validationPath.errorPrefix_ +
3180 'has a key path longer than ' +
3181 MAX_PATH_LENGTH_BYTES +
3182 ' bytes (' +
3183 validationPath.byteLength_ +
3184 ').');
3185 }
3186 if (validationPath.parts_.length > MAX_PATH_DEPTH) {
3187 throw new Error(validationPath.errorPrefix_ +
3188 'path specified exceeds the maximum depth that can be written (' +
3189 MAX_PATH_DEPTH +
3190 ') or object contains a cycle ' +
3191 validationPathToErrorString(validationPath));
3192 }
3193}
3194/**
3195 * String for use in error messages - uses '.' notation for path.
3196 */
3197function validationPathToErrorString(validationPath) {
3198 if (validationPath.parts_.length === 0) {
3199 return '';
3200 }
3201 return "in property '" + validationPath.parts_.join('.') + "'";
3202}
3203
3204/**
3205 * @license
3206 * Copyright 2017 Google LLC
3207 *
3208 * Licensed under the Apache License, Version 2.0 (the "License");
3209 * you may not use this file except in compliance with the License.
3210 * You may obtain a copy of the License at
3211 *
3212 * http://www.apache.org/licenses/LICENSE-2.0
3213 *
3214 * Unless required by applicable law or agreed to in writing, software
3215 * distributed under the License is distributed on an "AS IS" BASIS,
3216 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3217 * See the License for the specific language governing permissions and
3218 * limitations under the License.
3219 */
3220var VisibilityMonitor = /** @class */ (function (_super) {
3221 __extends(VisibilityMonitor, _super);
3222 function VisibilityMonitor() {
3223 var _this = _super.call(this, ['visible']) || this;
3224 var hidden;
3225 var visibilityChange;
3226 if (typeof document !== 'undefined' &&
3227 typeof document.addEventListener !== 'undefined') {
3228 if (typeof document['hidden'] !== 'undefined') {
3229 // Opera 12.10 and Firefox 18 and later support
3230 visibilityChange = 'visibilitychange';
3231 hidden = 'hidden';
3232 }
3233 else if (typeof document['mozHidden'] !== 'undefined') {
3234 visibilityChange = 'mozvisibilitychange';
3235 hidden = 'mozHidden';
3236 }
3237 else if (typeof document['msHidden'] !== 'undefined') {
3238 visibilityChange = 'msvisibilitychange';
3239 hidden = 'msHidden';
3240 }
3241 else if (typeof document['webkitHidden'] !== 'undefined') {
3242 visibilityChange = 'webkitvisibilitychange';
3243 hidden = 'webkitHidden';
3244 }
3245 }
3246 // Initially, we always assume we are visible. This ensures that in browsers
3247 // without page visibility support or in cases where we are never visible
3248 // (e.g. chrome extension), we act as if we are visible, i.e. don't delay
3249 // reconnects
3250 _this.visible_ = true;
3251 if (visibilityChange) {
3252 document.addEventListener(visibilityChange, function () {
3253 var visible = !document[hidden];
3254 if (visible !== _this.visible_) {
3255 _this.visible_ = visible;
3256 _this.trigger('visible', visible);
3257 }
3258 }, false);
3259 }
3260 return _this;
3261 }
3262 VisibilityMonitor.getInstance = function () {
3263 return new VisibilityMonitor();
3264 };
3265 VisibilityMonitor.prototype.getInitialEvent = function (eventType) {
3266 assert(eventType === 'visible', 'Unknown event type: ' + eventType);
3267 return [this.visible_];
3268 };
3269 return VisibilityMonitor;
3270}(EventEmitter));
3271
3272/**
3273 * @license
3274 * Copyright 2017 Google LLC
3275 *
3276 * Licensed under the Apache License, Version 2.0 (the "License");
3277 * you may not use this file except in compliance with the License.
3278 * You may obtain a copy of the License at
3279 *
3280 * http://www.apache.org/licenses/LICENSE-2.0
3281 *
3282 * Unless required by applicable law or agreed to in writing, software
3283 * distributed under the License is distributed on an "AS IS" BASIS,
3284 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3285 * See the License for the specific language governing permissions and
3286 * limitations under the License.
3287 */
3288var RECONNECT_MIN_DELAY = 1000;
3289var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
3290var GET_CONNECT_TIMEOUT = 3 * 1000;
3291var RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
3292var RECONNECT_DELAY_MULTIPLIER = 1.3;
3293var RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
3294var SERVER_KILL_INTERRUPT_REASON = 'server_kill';
3295// If auth fails repeatedly, we'll assume something is wrong and log a warning / back off.
3296var INVALID_TOKEN_THRESHOLD = 3;
3297/**
3298 * Firebase connection. Abstracts wire protocol and handles reconnecting.
3299 *
3300 * NOTE: All JSON objects sent to the realtime connection must have property names enclosed
3301 * in quotes to make sure the closure compiler does not minify them.
3302 */
3303var PersistentConnection = /** @class */ (function (_super) {
3304 __extends(PersistentConnection, _super);
3305 /**
3306 * @param repoInfo_ - Data about the namespace we are connecting to
3307 * @param applicationId_ - The Firebase App ID for this project
3308 * @param onDataUpdate_ - A callback for new data from the server
3309 */
3310 function PersistentConnection(repoInfo_, applicationId_, onDataUpdate_, onConnectStatus_, onServerInfoUpdate_, authTokenProvider_, appCheckTokenProvider_, authOverride_) {
3311 var _this = _super.call(this) || this;
3312 _this.repoInfo_ = repoInfo_;
3313 _this.applicationId_ = applicationId_;
3314 _this.onDataUpdate_ = onDataUpdate_;
3315 _this.onConnectStatus_ = onConnectStatus_;
3316 _this.onServerInfoUpdate_ = onServerInfoUpdate_;
3317 _this.authTokenProvider_ = authTokenProvider_;
3318 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
3319 _this.authOverride_ = authOverride_;
3320 // Used for diagnostic logging.
3321 _this.id = PersistentConnection.nextPersistentConnectionId_++;
3322 _this.log_ = logWrapper('p:' + _this.id + ':');
3323 _this.interruptReasons_ = {};
3324 _this.listens = new Map();
3325 _this.outstandingPuts_ = [];
3326 _this.outstandingGets_ = [];
3327 _this.outstandingPutCount_ = 0;
3328 _this.outstandingGetCount_ = 0;
3329 _this.onDisconnectRequestQueue_ = [];
3330 _this.connected_ = false;
3331 _this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3332 _this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_DEFAULT;
3333 _this.securityDebugCallback_ = null;
3334 _this.lastSessionId = null;
3335 _this.establishConnectionTimer_ = null;
3336 _this.visible_ = false;
3337 // Before we get connected, we keep a queue of pending messages to send.
3338 _this.requestCBHash_ = {};
3339 _this.requestNumber_ = 0;
3340 _this.realtime_ = null;
3341 _this.authToken_ = null;
3342 _this.appCheckToken_ = null;
3343 _this.forceTokenRefresh_ = false;
3344 _this.invalidAuthTokenCount_ = 0;
3345 _this.invalidAppCheckTokenCount_ = 0;
3346 _this.firstConnection_ = true;
3347 _this.lastConnectionAttemptTime_ = null;
3348 _this.lastConnectionEstablishedTime_ = null;
3349 if (authOverride_ && !isNodeSdk()) {
3350 throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
3351 }
3352 VisibilityMonitor.getInstance().on('visible', _this.onVisible_, _this);
3353 if (repoInfo_.host.indexOf('fblocal') === -1) {
3354 OnlineMonitor.getInstance().on('online', _this.onOnline_, _this);
3355 }
3356 return _this;
3357 }
3358 PersistentConnection.prototype.sendRequest = function (action, body, onResponse) {
3359 var curReqNum = ++this.requestNumber_;
3360 var msg = { r: curReqNum, a: action, b: body };
3361 this.log_(stringify(msg));
3362 assert(this.connected_, "sendRequest call when we're not connected not allowed.");
3363 this.realtime_.sendRequest(msg);
3364 if (onResponse) {
3365 this.requestCBHash_[curReqNum] = onResponse;
3366 }
3367 };
3368 PersistentConnection.prototype.get = function (query) {
3369 var _this = this;
3370 this.initConnection_();
3371 var deferred = new Deferred();
3372 var request = {
3373 p: query._path.toString(),
3374 q: query._queryObject
3375 };
3376 var outstandingGet = {
3377 action: 'g',
3378 request: request,
3379 onComplete: function (message) {
3380 var payload = message['d'];
3381 if (message['s'] === 'ok') {
3382 _this.onDataUpdate_(request['p'], payload,
3383 /*isMerge*/ false,
3384 /*tag*/ null);
3385 deferred.resolve(payload);
3386 }
3387 else {
3388 deferred.reject(payload);
3389 }
3390 }
3391 };
3392 this.outstandingGets_.push(outstandingGet);
3393 this.outstandingGetCount_++;
3394 var index = this.outstandingGets_.length - 1;
3395 if (!this.connected_) {
3396 setTimeout(function () {
3397 var get = _this.outstandingGets_[index];
3398 if (get === undefined || outstandingGet !== get) {
3399 return;
3400 }
3401 delete _this.outstandingGets_[index];
3402 _this.outstandingGetCount_--;
3403 if (_this.outstandingGetCount_ === 0) {
3404 _this.outstandingGets_ = [];
3405 }
3406 _this.log_('get ' + index + ' timed out on connection');
3407 deferred.reject(new Error('Client is offline.'));
3408 }, GET_CONNECT_TIMEOUT);
3409 }
3410 if (this.connected_) {
3411 this.sendGet_(index);
3412 }
3413 return deferred.promise;
3414 };
3415 PersistentConnection.prototype.listen = function (query, currentHashFn, tag, onComplete) {
3416 this.initConnection_();
3417 var queryId = query._queryIdentifier;
3418 var pathString = query._path.toString();
3419 this.log_('Listen called for ' + pathString + ' ' + queryId);
3420 if (!this.listens.has(pathString)) {
3421 this.listens.set(pathString, new Map());
3422 }
3423 assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'listen() called for non-default but complete query');
3424 assert(!this.listens.get(pathString).has(queryId), 'listen() called twice for same path/queryId.');
3425 var listenSpec = {
3426 onComplete: onComplete,
3427 hashFn: currentHashFn,
3428 query: query,
3429 tag: tag
3430 };
3431 this.listens.get(pathString).set(queryId, listenSpec);
3432 if (this.connected_) {
3433 this.sendListen_(listenSpec);
3434 }
3435 };
3436 PersistentConnection.prototype.sendGet_ = function (index) {
3437 var _this = this;
3438 var get = this.outstandingGets_[index];
3439 this.sendRequest('g', get.request, function (message) {
3440 delete _this.outstandingGets_[index];
3441 _this.outstandingGetCount_--;
3442 if (_this.outstandingGetCount_ === 0) {
3443 _this.outstandingGets_ = [];
3444 }
3445 if (get.onComplete) {
3446 get.onComplete(message);
3447 }
3448 });
3449 };
3450 PersistentConnection.prototype.sendListen_ = function (listenSpec) {
3451 var _this = this;
3452 var query = listenSpec.query;
3453 var pathString = query._path.toString();
3454 var queryId = query._queryIdentifier;
3455 this.log_('Listen on ' + pathString + ' for ' + queryId);
3456 var req = { /*path*/ p: pathString };
3457 var action = 'q';
3458 // Only bother to send query if it's non-default.
3459 if (listenSpec.tag) {
3460 req['q'] = query._queryObject;
3461 req['t'] = listenSpec.tag;
3462 }
3463 req[ /*hash*/'h'] = listenSpec.hashFn();
3464 this.sendRequest(action, req, function (message) {
3465 var payload = message[ /*data*/'d'];
3466 var status = message[ /*status*/'s'];
3467 // print warnings in any case...
3468 PersistentConnection.warnOnListenWarnings_(payload, query);
3469 var currentListenSpec = _this.listens.get(pathString) &&
3470 _this.listens.get(pathString).get(queryId);
3471 // only trigger actions if the listen hasn't been removed and readded
3472 if (currentListenSpec === listenSpec) {
3473 _this.log_('listen response', message);
3474 if (status !== 'ok') {
3475 _this.removeListen_(pathString, queryId);
3476 }
3477 if (listenSpec.onComplete) {
3478 listenSpec.onComplete(status, payload);
3479 }
3480 }
3481 });
3482 };
3483 PersistentConnection.warnOnListenWarnings_ = function (payload, query) {
3484 if (payload && typeof payload === 'object' && contains(payload, 'w')) {
3485 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3486 var warnings = safeGet(payload, 'w');
3487 if (Array.isArray(warnings) && ~warnings.indexOf('no_index')) {
3488 var indexSpec = '".indexOn": "' + query._queryParams.getIndex().toString() + '"';
3489 var indexPath = query._path.toString();
3490 warn("Using an unspecified index. Your data will be downloaded and " +
3491 ("filtered on the client. Consider adding " + indexSpec + " at ") +
3492 (indexPath + " to your security rules for better performance."));
3493 }
3494 }
3495 };
3496 PersistentConnection.prototype.refreshAuthToken = function (token) {
3497 this.authToken_ = token;
3498 this.log_('Auth token refreshed');
3499 if (this.authToken_) {
3500 this.tryAuth();
3501 }
3502 else {
3503 //If we're connected we want to let the server know to unauthenticate us. If we're not connected, simply delete
3504 //the credential so we dont become authenticated next time we connect.
3505 if (this.connected_) {
3506 this.sendRequest('unauth', {}, function () { });
3507 }
3508 }
3509 this.reduceReconnectDelayIfAdminCredential_(token);
3510 };
3511 PersistentConnection.prototype.reduceReconnectDelayIfAdminCredential_ = function (credential) {
3512 // NOTE: This isn't intended to be bulletproof (a malicious developer can always just modify the client).
3513 // Additionally, we don't bother resetting the max delay back to the default if auth fails / expires.
3514 var isFirebaseSecret = credential && credential.length === 40;
3515 if (isFirebaseSecret || isAdmin(credential)) {
3516 this.log_('Admin auth credential detected. Reducing max reconnect time.');
3517 this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
3518 }
3519 };
3520 PersistentConnection.prototype.refreshAppCheckToken = function (token) {
3521 this.appCheckToken_ = token;
3522 this.log_('App check token refreshed');
3523 if (this.appCheckToken_) {
3524 this.tryAppCheck();
3525 }
3526 else {
3527 //If we're connected we want to let the server know to unauthenticate us.
3528 //If we're not connected, simply delete the credential so we dont become
3529 // authenticated next time we connect.
3530 if (this.connected_) {
3531 this.sendRequest('unappeck', {}, function () { });
3532 }
3533 }
3534 };
3535 /**
3536 * Attempts to authenticate with the given credentials. If the authentication attempt fails, it's triggered like
3537 * a auth revoked (the connection is closed).
3538 */
3539 PersistentConnection.prototype.tryAuth = function () {
3540 var _this = this;
3541 if (this.connected_ && this.authToken_) {
3542 var token_1 = this.authToken_;
3543 var authMethod = isValidFormat(token_1) ? 'auth' : 'gauth';
3544 var requestData = { cred: token_1 };
3545 if (this.authOverride_ === null) {
3546 requestData['noauth'] = true;
3547 }
3548 else if (typeof this.authOverride_ === 'object') {
3549 requestData['authvar'] = this.authOverride_;
3550 }
3551 this.sendRequest(authMethod, requestData, function (res) {
3552 var status = res[ /*status*/'s'];
3553 var data = res[ /*data*/'d'] || 'error';
3554 if (_this.authToken_ === token_1) {
3555 if (status === 'ok') {
3556 _this.invalidAuthTokenCount_ = 0;
3557 }
3558 else {
3559 // Triggers reconnect and force refresh for auth token
3560 _this.onAuthRevoked_(status, data);
3561 }
3562 }
3563 });
3564 }
3565 };
3566 /**
3567 * Attempts to authenticate with the given token. If the authentication
3568 * attempt fails, it's triggered like the token was revoked (the connection is
3569 * closed).
3570 */
3571 PersistentConnection.prototype.tryAppCheck = function () {
3572 var _this = this;
3573 if (this.connected_ && this.appCheckToken_) {
3574 this.sendRequest('appcheck', { 'token': this.appCheckToken_ }, function (res) {
3575 var status = res[ /*status*/'s'];
3576 var data = res[ /*data*/'d'] || 'error';
3577 if (status === 'ok') {
3578 _this.invalidAppCheckTokenCount_ = 0;
3579 }
3580 else {
3581 _this.onAppCheckRevoked_(status, data);
3582 }
3583 });
3584 }
3585 };
3586 /**
3587 * @inheritDoc
3588 */
3589 PersistentConnection.prototype.unlisten = function (query, tag) {
3590 var pathString = query._path.toString();
3591 var queryId = query._queryIdentifier;
3592 this.log_('Unlisten called for ' + pathString + ' ' + queryId);
3593 assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'unlisten() called for non-default but complete query');
3594 var listen = this.removeListen_(pathString, queryId);
3595 if (listen && this.connected_) {
3596 this.sendUnlisten_(pathString, queryId, query._queryObject, tag);
3597 }
3598 };
3599 PersistentConnection.prototype.sendUnlisten_ = function (pathString, queryId, queryObj, tag) {
3600 this.log_('Unlisten on ' + pathString + ' for ' + queryId);
3601 var req = { /*path*/ p: pathString };
3602 var action = 'n';
3603 // Only bother sending queryId if it's non-default.
3604 if (tag) {
3605 req['q'] = queryObj;
3606 req['t'] = tag;
3607 }
3608 this.sendRequest(action, req);
3609 };
3610 PersistentConnection.prototype.onDisconnectPut = function (pathString, data, onComplete) {
3611 this.initConnection_();
3612 if (this.connected_) {
3613 this.sendOnDisconnect_('o', pathString, data, onComplete);
3614 }
3615 else {
3616 this.onDisconnectRequestQueue_.push({
3617 pathString: pathString,
3618 action: 'o',
3619 data: data,
3620 onComplete: onComplete
3621 });
3622 }
3623 };
3624 PersistentConnection.prototype.onDisconnectMerge = function (pathString, data, onComplete) {
3625 this.initConnection_();
3626 if (this.connected_) {
3627 this.sendOnDisconnect_('om', pathString, data, onComplete);
3628 }
3629 else {
3630 this.onDisconnectRequestQueue_.push({
3631 pathString: pathString,
3632 action: 'om',
3633 data: data,
3634 onComplete: onComplete
3635 });
3636 }
3637 };
3638 PersistentConnection.prototype.onDisconnectCancel = function (pathString, onComplete) {
3639 this.initConnection_();
3640 if (this.connected_) {
3641 this.sendOnDisconnect_('oc', pathString, null, onComplete);
3642 }
3643 else {
3644 this.onDisconnectRequestQueue_.push({
3645 pathString: pathString,
3646 action: 'oc',
3647 data: null,
3648 onComplete: onComplete
3649 });
3650 }
3651 };
3652 PersistentConnection.prototype.sendOnDisconnect_ = function (action, pathString, data, onComplete) {
3653 var request = { /*path*/ p: pathString, /*data*/ d: data };
3654 this.log_('onDisconnect ' + action, request);
3655 this.sendRequest(action, request, function (response) {
3656 if (onComplete) {
3657 setTimeout(function () {
3658 onComplete(response[ /*status*/'s'], response[ /* data */'d']);
3659 }, Math.floor(0));
3660 }
3661 });
3662 };
3663 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
3664 this.putInternal('p', pathString, data, onComplete, hash);
3665 };
3666 PersistentConnection.prototype.merge = function (pathString, data, onComplete, hash) {
3667 this.putInternal('m', pathString, data, onComplete, hash);
3668 };
3669 PersistentConnection.prototype.putInternal = function (action, pathString, data, onComplete, hash) {
3670 this.initConnection_();
3671 var request = {
3672 /*path*/ p: pathString,
3673 /*data*/ d: data
3674 };
3675 if (hash !== undefined) {
3676 request[ /*hash*/'h'] = hash;
3677 }
3678 // TODO: Only keep track of the most recent put for a given path?
3679 this.outstandingPuts_.push({
3680 action: action,
3681 request: request,
3682 onComplete: onComplete
3683 });
3684 this.outstandingPutCount_++;
3685 var index = this.outstandingPuts_.length - 1;
3686 if (this.connected_) {
3687 this.sendPut_(index);
3688 }
3689 else {
3690 this.log_('Buffering put: ' + pathString);
3691 }
3692 };
3693 PersistentConnection.prototype.sendPut_ = function (index) {
3694 var _this = this;
3695 var action = this.outstandingPuts_[index].action;
3696 var request = this.outstandingPuts_[index].request;
3697 var onComplete = this.outstandingPuts_[index].onComplete;
3698 this.outstandingPuts_[index].queued = this.connected_;
3699 this.sendRequest(action, request, function (message) {
3700 _this.log_(action + ' response', message);
3701 delete _this.outstandingPuts_[index];
3702 _this.outstandingPutCount_--;
3703 // Clean up array occasionally.
3704 if (_this.outstandingPutCount_ === 0) {
3705 _this.outstandingPuts_ = [];
3706 }
3707 if (onComplete) {
3708 onComplete(message[ /*status*/'s'], message[ /* data */'d']);
3709 }
3710 });
3711 };
3712 PersistentConnection.prototype.reportStats = function (stats) {
3713 var _this = this;
3714 // If we're not connected, we just drop the stats.
3715 if (this.connected_) {
3716 var request = { /*counters*/ c: stats };
3717 this.log_('reportStats', request);
3718 this.sendRequest(/*stats*/ 's', request, function (result) {
3719 var status = result[ /*status*/'s'];
3720 if (status !== 'ok') {
3721 var errorReason = result[ /* data */'d'];
3722 _this.log_('reportStats', 'Error sending stats: ' + errorReason);
3723 }
3724 });
3725 }
3726 };
3727 PersistentConnection.prototype.onDataMessage_ = function (message) {
3728 if ('r' in message) {
3729 // this is a response
3730 this.log_('from server: ' + stringify(message));
3731 var reqNum = message['r'];
3732 var onResponse = this.requestCBHash_[reqNum];
3733 if (onResponse) {
3734 delete this.requestCBHash_[reqNum];
3735 onResponse(message[ /*body*/'b']);
3736 }
3737 }
3738 else if ('error' in message) {
3739 throw 'A server-side error has occurred: ' + message['error'];
3740 }
3741 else if ('a' in message) {
3742 // a and b are action and body, respectively
3743 this.onDataPush_(message['a'], message['b']);
3744 }
3745 };
3746 PersistentConnection.prototype.onDataPush_ = function (action, body) {
3747 this.log_('handleServerMessage', action, body);
3748 if (action === 'd') {
3749 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3750 /*isMerge*/ false, body['t']);
3751 }
3752 else if (action === 'm') {
3753 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3754 /*isMerge=*/ true, body['t']);
3755 }
3756 else if (action === 'c') {
3757 this.onListenRevoked_(body[ /*path*/'p'], body[ /*query*/'q']);
3758 }
3759 else if (action === 'ac') {
3760 this.onAuthRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3761 }
3762 else if (action === 'apc') {
3763 this.onAppCheckRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3764 }
3765 else if (action === 'sd') {
3766 this.onSecurityDebugPacket_(body);
3767 }
3768 else {
3769 error('Unrecognized action received from server: ' +
3770 stringify(action) +
3771 '\nAre you using the latest client?');
3772 }
3773 };
3774 PersistentConnection.prototype.onReady_ = function (timestamp, sessionId) {
3775 this.log_('connection ready');
3776 this.connected_ = true;
3777 this.lastConnectionEstablishedTime_ = new Date().getTime();
3778 this.handleTimestamp_(timestamp);
3779 this.lastSessionId = sessionId;
3780 if (this.firstConnection_) {
3781 this.sendConnectStats_();
3782 }
3783 this.restoreState_();
3784 this.firstConnection_ = false;
3785 this.onConnectStatus_(true);
3786 };
3787 PersistentConnection.prototype.scheduleConnect_ = function (timeout) {
3788 var _this = this;
3789 assert(!this.realtime_, "Scheduling a connect when we're already connected/ing?");
3790 if (this.establishConnectionTimer_) {
3791 clearTimeout(this.establishConnectionTimer_);
3792 }
3793 // NOTE: Even when timeout is 0, it's important to do a setTimeout to work around an infuriating "Security Error" in
3794 // Firefox when trying to write to our long-polling iframe in some scenarios (e.g. Forge or our unit tests).
3795 this.establishConnectionTimer_ = setTimeout(function () {
3796 _this.establishConnectionTimer_ = null;
3797 _this.establishConnection_();
3798 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3799 }, Math.floor(timeout));
3800 };
3801 PersistentConnection.prototype.initConnection_ = function () {
3802 if (!this.realtime_ && this.firstConnection_) {
3803 this.scheduleConnect_(0);
3804 }
3805 };
3806 PersistentConnection.prototype.onVisible_ = function (visible) {
3807 // NOTE: Tabbing away and back to a window will defeat our reconnect backoff, but I think that's fine.
3808 if (visible &&
3809 !this.visible_ &&
3810 this.reconnectDelay_ === this.maxReconnectDelay_) {
3811 this.log_('Window became visible. Reducing delay.');
3812 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3813 if (!this.realtime_) {
3814 this.scheduleConnect_(0);
3815 }
3816 }
3817 this.visible_ = visible;
3818 };
3819 PersistentConnection.prototype.onOnline_ = function (online) {
3820 if (online) {
3821 this.log_('Browser went online.');
3822 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3823 if (!this.realtime_) {
3824 this.scheduleConnect_(0);
3825 }
3826 }
3827 else {
3828 this.log_('Browser went offline. Killing connection.');
3829 if (this.realtime_) {
3830 this.realtime_.close();
3831 }
3832 }
3833 };
3834 PersistentConnection.prototype.onRealtimeDisconnect_ = function () {
3835 this.log_('data client disconnected');
3836 this.connected_ = false;
3837 this.realtime_ = null;
3838 // Since we don't know if our sent transactions succeeded or not, we need to cancel them.
3839 this.cancelSentTransactions_();
3840 // Clear out the pending requests.
3841 this.requestCBHash_ = {};
3842 if (this.shouldReconnect_()) {
3843 if (!this.visible_) {
3844 this.log_("Window isn't visible. Delaying reconnect.");
3845 this.reconnectDelay_ = this.maxReconnectDelay_;
3846 this.lastConnectionAttemptTime_ = new Date().getTime();
3847 }
3848 else if (this.lastConnectionEstablishedTime_) {
3849 // If we've been connected long enough, reset reconnect delay to minimum.
3850 var timeSinceLastConnectSucceeded = new Date().getTime() - this.lastConnectionEstablishedTime_;
3851 if (timeSinceLastConnectSucceeded > RECONNECT_DELAY_RESET_TIMEOUT) {
3852 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3853 }
3854 this.lastConnectionEstablishedTime_ = null;
3855 }
3856 var timeSinceLastConnectAttempt = new Date().getTime() - this.lastConnectionAttemptTime_;
3857 var reconnectDelay = Math.max(0, this.reconnectDelay_ - timeSinceLastConnectAttempt);
3858 reconnectDelay = Math.random() * reconnectDelay;
3859 this.log_('Trying to reconnect in ' + reconnectDelay + 'ms');
3860 this.scheduleConnect_(reconnectDelay);
3861 // Adjust reconnect delay for next time.
3862 this.reconnectDelay_ = Math.min(this.maxReconnectDelay_, this.reconnectDelay_ * RECONNECT_DELAY_MULTIPLIER);
3863 }
3864 this.onConnectStatus_(false);
3865 };
3866 PersistentConnection.prototype.establishConnection_ = function () {
3867 return __awaiter(this, void 0, void 0, function () {
3868 var onDataMessage, onReady, onDisconnect_1, connId, lastSessionId, canceled_1, connection_1, closeFn, sendRequestFn, forceRefresh, _a, authToken, appCheckToken, error_1;
3869 var _this = this;
3870 return __generator(this, function (_b) {
3871 switch (_b.label) {
3872 case 0:
3873 if (!this.shouldReconnect_()) return [3 /*break*/, 4];
3874 this.log_('Making a connection attempt');
3875 this.lastConnectionAttemptTime_ = new Date().getTime();
3876 this.lastConnectionEstablishedTime_ = null;
3877 onDataMessage = this.onDataMessage_.bind(this);
3878 onReady = this.onReady_.bind(this);
3879 onDisconnect_1 = this.onRealtimeDisconnect_.bind(this);
3880 connId = this.id + ':' + PersistentConnection.nextConnectionId_++;
3881 lastSessionId = this.lastSessionId;
3882 canceled_1 = false;
3883 connection_1 = null;
3884 closeFn = function () {
3885 if (connection_1) {
3886 connection_1.close();
3887 }
3888 else {
3889 canceled_1 = true;
3890 onDisconnect_1();
3891 }
3892 };
3893 sendRequestFn = function (msg) {
3894 assert(connection_1, "sendRequest call when we're not connected not allowed.");
3895 connection_1.sendRequest(msg);
3896 };
3897 this.realtime_ = {
3898 close: closeFn,
3899 sendRequest: sendRequestFn
3900 };
3901 forceRefresh = this.forceTokenRefresh_;
3902 this.forceTokenRefresh_ = false;
3903 _b.label = 1;
3904 case 1:
3905 _b.trys.push([1, 3, , 4]);
3906 return [4 /*yield*/, Promise.all([
3907 this.authTokenProvider_.getToken(forceRefresh),
3908 this.appCheckTokenProvider_.getToken(forceRefresh)
3909 ])];
3910 case 2:
3911 _a = __read.apply(void 0, [_b.sent(), 2]), authToken = _a[0], appCheckToken = _a[1];
3912 if (!canceled_1) {
3913 log('getToken() completed. Creating connection.');
3914 this.authToken_ = authToken && authToken.accessToken;
3915 this.appCheckToken_ = appCheckToken && appCheckToken.token;
3916 connection_1 = new Connection(connId, this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, onDataMessage, onReady, onDisconnect_1,
3917 /* onKill= */ function (reason) {
3918 warn(reason + ' (' + _this.repoInfo_.toString() + ')');
3919 _this.interrupt(SERVER_KILL_INTERRUPT_REASON);
3920 }, lastSessionId);
3921 }
3922 else {
3923 log('getToken() completed but was canceled');
3924 }
3925 return [3 /*break*/, 4];
3926 case 3:
3927 error_1 = _b.sent();
3928 this.log_('Failed to get token: ' + error_1);
3929 if (!canceled_1) {
3930 if (this.repoInfo_.nodeAdmin) {
3931 // This may be a critical error for the Admin Node.js SDK, so log a warning.
3932 // But getToken() may also just have temporarily failed, so we still want to
3933 // continue retrying.
3934 warn(error_1);
3935 }
3936 closeFn();
3937 }
3938 return [3 /*break*/, 4];
3939 case 4: return [2 /*return*/];
3940 }
3941 });
3942 });
3943 };
3944 PersistentConnection.prototype.interrupt = function (reason) {
3945 log('Interrupting connection for reason: ' + reason);
3946 this.interruptReasons_[reason] = true;
3947 if (this.realtime_) {
3948 this.realtime_.close();
3949 }
3950 else {
3951 if (this.establishConnectionTimer_) {
3952 clearTimeout(this.establishConnectionTimer_);
3953 this.establishConnectionTimer_ = null;
3954 }
3955 if (this.connected_) {
3956 this.onRealtimeDisconnect_();
3957 }
3958 }
3959 };
3960 PersistentConnection.prototype.resume = function (reason) {
3961 log('Resuming connection for reason: ' + reason);
3962 delete this.interruptReasons_[reason];
3963 if (isEmpty(this.interruptReasons_)) {
3964 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3965 if (!this.realtime_) {
3966 this.scheduleConnect_(0);
3967 }
3968 }
3969 };
3970 PersistentConnection.prototype.handleTimestamp_ = function (timestamp) {
3971 var delta = timestamp - new Date().getTime();
3972 this.onServerInfoUpdate_({ serverTimeOffset: delta });
3973 };
3974 PersistentConnection.prototype.cancelSentTransactions_ = function () {
3975 for (var i = 0; i < this.outstandingPuts_.length; i++) {
3976 var put = this.outstandingPuts_[i];
3977 if (put && /*hash*/ 'h' in put.request && put.queued) {
3978 if (put.onComplete) {
3979 put.onComplete('disconnect');
3980 }
3981 delete this.outstandingPuts_[i];
3982 this.outstandingPutCount_--;
3983 }
3984 }
3985 // Clean up array occasionally.
3986 if (this.outstandingPutCount_ === 0) {
3987 this.outstandingPuts_ = [];
3988 }
3989 };
3990 PersistentConnection.prototype.onListenRevoked_ = function (pathString, query) {
3991 // Remove the listen and manufacture a "permission_denied" error for the failed listen.
3992 var queryId;
3993 if (!query) {
3994 queryId = 'default';
3995 }
3996 else {
3997 queryId = query.map(function (q) { return ObjectToUniqueKey(q); }).join('$');
3998 }
3999 var listen = this.removeListen_(pathString, queryId);
4000 if (listen && listen.onComplete) {
4001 listen.onComplete('permission_denied');
4002 }
4003 };
4004 PersistentConnection.prototype.removeListen_ = function (pathString, queryId) {
4005 var normalizedPathString = new Path(pathString).toString(); // normalize path.
4006 var listen;
4007 if (this.listens.has(normalizedPathString)) {
4008 var map = this.listens.get(normalizedPathString);
4009 listen = map.get(queryId);
4010 map.delete(queryId);
4011 if (map.size === 0) {
4012 this.listens.delete(normalizedPathString);
4013 }
4014 }
4015 else {
4016 // all listens for this path has already been removed
4017 listen = undefined;
4018 }
4019 return listen;
4020 };
4021 PersistentConnection.prototype.onAuthRevoked_ = function (statusCode, explanation) {
4022 log('Auth token revoked: ' + statusCode + '/' + explanation);
4023 this.authToken_ = null;
4024 this.forceTokenRefresh_ = true;
4025 this.realtime_.close();
4026 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4027 // We'll wait a couple times before logging the warning / increasing the
4028 // retry period since oauth tokens will report as "invalid" if they're
4029 // just expired. Plus there may be transient issues that resolve themselves.
4030 this.invalidAuthTokenCount_++;
4031 if (this.invalidAuthTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4032 // Set a long reconnect delay because recovery is unlikely
4033 this.reconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
4034 // Notify the auth token provider that the token is invalid, which will log
4035 // a warning
4036 this.authTokenProvider_.notifyForInvalidToken();
4037 }
4038 }
4039 };
4040 PersistentConnection.prototype.onAppCheckRevoked_ = function (statusCode, explanation) {
4041 log('App check token revoked: ' + statusCode + '/' + explanation);
4042 this.appCheckToken_ = null;
4043 this.forceTokenRefresh_ = true;
4044 // Note: We don't close the connection as the developer may not have
4045 // enforcement enabled. The backend closes connections with enforcements.
4046 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4047 // We'll wait a couple times before logging the warning / increasing the
4048 // retry period since oauth tokens will report as "invalid" if they're
4049 // just expired. Plus there may be transient issues that resolve themselves.
4050 this.invalidAppCheckTokenCount_++;
4051 if (this.invalidAppCheckTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4052 this.appCheckTokenProvider_.notifyForInvalidToken();
4053 }
4054 }
4055 };
4056 PersistentConnection.prototype.onSecurityDebugPacket_ = function (body) {
4057 if (this.securityDebugCallback_) {
4058 this.securityDebugCallback_(body);
4059 }
4060 else {
4061 if ('msg' in body) {
4062 console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
4063 }
4064 }
4065 };
4066 PersistentConnection.prototype.restoreState_ = function () {
4067 var e_1, _a, e_2, _b;
4068 //Re-authenticate ourselves if we have a credential stored.
4069 this.tryAuth();
4070 this.tryAppCheck();
4071 try {
4072 // Puts depend on having received the corresponding data update from the server before they complete, so we must
4073 // make sure to send listens before puts.
4074 for (var _c = __values(this.listens.values()), _d = _c.next(); !_d.done; _d = _c.next()) {
4075 var queries = _d.value;
4076 try {
4077 for (var _e = (e_2 = void 0, __values(queries.values())), _f = _e.next(); !_f.done; _f = _e.next()) {
4078 var listenSpec = _f.value;
4079 this.sendListen_(listenSpec);
4080 }
4081 }
4082 catch (e_2_1) { e_2 = { error: e_2_1 }; }
4083 finally {
4084 try {
4085 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
4086 }
4087 finally { if (e_2) throw e_2.error; }
4088 }
4089 }
4090 }
4091 catch (e_1_1) { e_1 = { error: e_1_1 }; }
4092 finally {
4093 try {
4094 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
4095 }
4096 finally { if (e_1) throw e_1.error; }
4097 }
4098 for (var i = 0; i < this.outstandingPuts_.length; i++) {
4099 if (this.outstandingPuts_[i]) {
4100 this.sendPut_(i);
4101 }
4102 }
4103 while (this.onDisconnectRequestQueue_.length) {
4104 var request = this.onDisconnectRequestQueue_.shift();
4105 this.sendOnDisconnect_(request.action, request.pathString, request.data, request.onComplete);
4106 }
4107 for (var i = 0; i < this.outstandingGets_.length; i++) {
4108 if (this.outstandingGets_[i]) {
4109 this.sendGet_(i);
4110 }
4111 }
4112 };
4113 /**
4114 * Sends client stats for first connection
4115 */
4116 PersistentConnection.prototype.sendConnectStats_ = function () {
4117 var stats = {};
4118 var clientName = 'js';
4119 if (isNodeSdk()) {
4120 if (this.repoInfo_.nodeAdmin) {
4121 clientName = 'admin_node';
4122 }
4123 else {
4124 clientName = 'node';
4125 }
4126 }
4127 stats['sdk.' + clientName + '.' + SDK_VERSION.replace(/\./g, '-')] = 1;
4128 if (isMobileCordova()) {
4129 stats['framework.cordova'] = 1;
4130 }
4131 else if (isReactNative()) {
4132 stats['framework.reactnative'] = 1;
4133 }
4134 this.reportStats(stats);
4135 };
4136 PersistentConnection.prototype.shouldReconnect_ = function () {
4137 var online = OnlineMonitor.getInstance().currentlyOnline();
4138 return isEmpty(this.interruptReasons_) && online;
4139 };
4140 PersistentConnection.nextPersistentConnectionId_ = 0;
4141 /**
4142 * Counter for number of connections created. Mainly used for tagging in the logs
4143 */
4144 PersistentConnection.nextConnectionId_ = 0;
4145 return PersistentConnection;
4146}(ServerActions));
4147
4148/**
4149 * @license
4150 * Copyright 2017 Google LLC
4151 *
4152 * Licensed under the Apache License, Version 2.0 (the "License");
4153 * you may not use this file except in compliance with the License.
4154 * You may obtain a copy of the License at
4155 *
4156 * http://www.apache.org/licenses/LICENSE-2.0
4157 *
4158 * Unless required by applicable law or agreed to in writing, software
4159 * distributed under the License is distributed on an "AS IS" BASIS,
4160 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4161 * See the License for the specific language governing permissions and
4162 * limitations under the License.
4163 */
4164var NamedNode = /** @class */ (function () {
4165 function NamedNode(name, node) {
4166 this.name = name;
4167 this.node = node;
4168 }
4169 NamedNode.Wrap = function (name, node) {
4170 return new NamedNode(name, node);
4171 };
4172 return NamedNode;
4173}());
4174
4175/**
4176 * @license
4177 * Copyright 2017 Google LLC
4178 *
4179 * Licensed under the Apache License, Version 2.0 (the "License");
4180 * you may not use this file except in compliance with the License.
4181 * You may obtain a copy of the License at
4182 *
4183 * http://www.apache.org/licenses/LICENSE-2.0
4184 *
4185 * Unless required by applicable law or agreed to in writing, software
4186 * distributed under the License is distributed on an "AS IS" BASIS,
4187 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4188 * See the License for the specific language governing permissions and
4189 * limitations under the License.
4190 */
4191var Index = /** @class */ (function () {
4192 function Index() {
4193 }
4194 /**
4195 * @returns A standalone comparison function for
4196 * this index
4197 */
4198 Index.prototype.getCompare = function () {
4199 return this.compare.bind(this);
4200 };
4201 /**
4202 * Given a before and after value for a node, determine if the indexed value has changed. Even if they are different,
4203 * it's possible that the changes are isolated to parts of the snapshot that are not indexed.
4204 *
4205 *
4206 * @returns True if the portion of the snapshot being indexed changed between oldNode and newNode
4207 */
4208 Index.prototype.indexedValueChanged = function (oldNode, newNode) {
4209 var oldWrapped = new NamedNode(MIN_NAME, oldNode);
4210 var newWrapped = new NamedNode(MIN_NAME, newNode);
4211 return this.compare(oldWrapped, newWrapped) !== 0;
4212 };
4213 /**
4214 * @returns a node wrapper that will sort equal to or less than
4215 * any other node wrapper, using this index
4216 */
4217 Index.prototype.minPost = function () {
4218 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4219 return NamedNode.MIN;
4220 };
4221 return Index;
4222}());
4223
4224/**
4225 * @license
4226 * Copyright 2017 Google LLC
4227 *
4228 * Licensed under the Apache License, Version 2.0 (the "License");
4229 * you may not use this file except in compliance with the License.
4230 * You may obtain a copy of the License at
4231 *
4232 * http://www.apache.org/licenses/LICENSE-2.0
4233 *
4234 * Unless required by applicable law or agreed to in writing, software
4235 * distributed under the License is distributed on an "AS IS" BASIS,
4236 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4237 * See the License for the specific language governing permissions and
4238 * limitations under the License.
4239 */
4240var __EMPTY_NODE;
4241var KeyIndex = /** @class */ (function (_super) {
4242 __extends(KeyIndex, _super);
4243 function KeyIndex() {
4244 return _super !== null && _super.apply(this, arguments) || this;
4245 }
4246 Object.defineProperty(KeyIndex, "__EMPTY_NODE", {
4247 get: function () {
4248 return __EMPTY_NODE;
4249 },
4250 set: function (val) {
4251 __EMPTY_NODE = val;
4252 },
4253 enumerable: false,
4254 configurable: true
4255 });
4256 KeyIndex.prototype.compare = function (a, b) {
4257 return nameCompare(a.name, b.name);
4258 };
4259 KeyIndex.prototype.isDefinedOn = function (node) {
4260 // We could probably return true here (since every node has a key), but it's never called
4261 // so just leaving unimplemented for now.
4262 throw assertionError('KeyIndex.isDefinedOn not expected to be called.');
4263 };
4264 KeyIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
4265 return false; // The key for a node never changes.
4266 };
4267 KeyIndex.prototype.minPost = function () {
4268 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4269 return NamedNode.MIN;
4270 };
4271 KeyIndex.prototype.maxPost = function () {
4272 // TODO: This should really be created once and cached in a static property, but
4273 // NamedNode isn't defined yet, so I can't use it in a static. Bleh.
4274 return new NamedNode(MAX_NAME, __EMPTY_NODE);
4275 };
4276 KeyIndex.prototype.makePost = function (indexValue, name) {
4277 assert(typeof indexValue === 'string', 'KeyIndex indexValue must always be a string.');
4278 // We just use empty node, but it'll never be compared, since our comparator only looks at name.
4279 return new NamedNode(indexValue, __EMPTY_NODE);
4280 };
4281 /**
4282 * @returns String representation for inclusion in a query spec
4283 */
4284 KeyIndex.prototype.toString = function () {
4285 return '.key';
4286 };
4287 return KeyIndex;
4288}(Index));
4289var KEY_INDEX = new KeyIndex();
4290
4291/**
4292 * @license
4293 * Copyright 2017 Google LLC
4294 *
4295 * Licensed under the Apache License, Version 2.0 (the "License");
4296 * you may not use this file except in compliance with the License.
4297 * You may obtain a copy of the License at
4298 *
4299 * http://www.apache.org/licenses/LICENSE-2.0
4300 *
4301 * Unless required by applicable law or agreed to in writing, software
4302 * distributed under the License is distributed on an "AS IS" BASIS,
4303 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4304 * See the License for the specific language governing permissions and
4305 * limitations under the License.
4306 */
4307/**
4308 * An iterator over an LLRBNode.
4309 */
4310var SortedMapIterator = /** @class */ (function () {
4311 /**
4312 * @param node - Node to iterate.
4313 * @param isReverse_ - Whether or not to iterate in reverse
4314 */
4315 function SortedMapIterator(node, startKey, comparator, isReverse_, resultGenerator_) {
4316 if (resultGenerator_ === void 0) { resultGenerator_ = null; }
4317 this.isReverse_ = isReverse_;
4318 this.resultGenerator_ = resultGenerator_;
4319 this.nodeStack_ = [];
4320 var cmp = 1;
4321 while (!node.isEmpty()) {
4322 node = node;
4323 cmp = startKey ? comparator(node.key, startKey) : 1;
4324 // flip the comparison if we're going in reverse
4325 if (isReverse_) {
4326 cmp *= -1;
4327 }
4328 if (cmp < 0) {
4329 // This node is less than our start key. ignore it
4330 if (this.isReverse_) {
4331 node = node.left;
4332 }
4333 else {
4334 node = node.right;
4335 }
4336 }
4337 else if (cmp === 0) {
4338 // This node is exactly equal to our start key. Push it on the stack, but stop iterating;
4339 this.nodeStack_.push(node);
4340 break;
4341 }
4342 else {
4343 // This node is greater than our start key, add it to the stack and move to the next one
4344 this.nodeStack_.push(node);
4345 if (this.isReverse_) {
4346 node = node.right;
4347 }
4348 else {
4349 node = node.left;
4350 }
4351 }
4352 }
4353 }
4354 SortedMapIterator.prototype.getNext = function () {
4355 if (this.nodeStack_.length === 0) {
4356 return null;
4357 }
4358 var node = this.nodeStack_.pop();
4359 var result;
4360 if (this.resultGenerator_) {
4361 result = this.resultGenerator_(node.key, node.value);
4362 }
4363 else {
4364 result = { key: node.key, value: node.value };
4365 }
4366 if (this.isReverse_) {
4367 node = node.left;
4368 while (!node.isEmpty()) {
4369 this.nodeStack_.push(node);
4370 node = node.right;
4371 }
4372 }
4373 else {
4374 node = node.right;
4375 while (!node.isEmpty()) {
4376 this.nodeStack_.push(node);
4377 node = node.left;
4378 }
4379 }
4380 return result;
4381 };
4382 SortedMapIterator.prototype.hasNext = function () {
4383 return this.nodeStack_.length > 0;
4384 };
4385 SortedMapIterator.prototype.peek = function () {
4386 if (this.nodeStack_.length === 0) {
4387 return null;
4388 }
4389 var node = this.nodeStack_[this.nodeStack_.length - 1];
4390 if (this.resultGenerator_) {
4391 return this.resultGenerator_(node.key, node.value);
4392 }
4393 else {
4394 return { key: node.key, value: node.value };
4395 }
4396 };
4397 return SortedMapIterator;
4398}());
4399/**
4400 * Represents a node in a Left-leaning Red-Black tree.
4401 */
4402var LLRBNode = /** @class */ (function () {
4403 /**
4404 * @param key - Key associated with this node.
4405 * @param value - Value associated with this node.
4406 * @param color - Whether this node is red.
4407 * @param left - Left child.
4408 * @param right - Right child.
4409 */
4410 function LLRBNode(key, value, color, left, right) {
4411 this.key = key;
4412 this.value = value;
4413 this.color = color != null ? color : LLRBNode.RED;
4414 this.left =
4415 left != null ? left : SortedMap.EMPTY_NODE;
4416 this.right =
4417 right != null ? right : SortedMap.EMPTY_NODE;
4418 }
4419 /**
4420 * Returns a copy of the current node, optionally replacing pieces of it.
4421 *
4422 * @param key - New key for the node, or null.
4423 * @param value - New value for the node, or null.
4424 * @param color - New color for the node, or null.
4425 * @param left - New left child for the node, or null.
4426 * @param right - New right child for the node, or null.
4427 * @returns The node copy.
4428 */
4429 LLRBNode.prototype.copy = function (key, value, color, left, right) {
4430 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);
4431 };
4432 /**
4433 * @returns The total number of nodes in the tree.
4434 */
4435 LLRBNode.prototype.count = function () {
4436 return this.left.count() + 1 + this.right.count();
4437 };
4438 /**
4439 * @returns True if the tree is empty.
4440 */
4441 LLRBNode.prototype.isEmpty = function () {
4442 return false;
4443 };
4444 /**
4445 * Traverses the tree in key order and calls the specified action function
4446 * for each node.
4447 *
4448 * @param action - Callback function to be called for each
4449 * node. If it returns true, traversal is aborted.
4450 * @returns The first truthy value returned by action, or the last falsey
4451 * value returned by action
4452 */
4453 LLRBNode.prototype.inorderTraversal = function (action) {
4454 return (this.left.inorderTraversal(action) ||
4455 !!action(this.key, this.value) ||
4456 this.right.inorderTraversal(action));
4457 };
4458 /**
4459 * Traverses the tree in reverse key order and calls the specified action function
4460 * for each node.
4461 *
4462 * @param action - Callback function to be called for each
4463 * node. If it returns true, traversal is aborted.
4464 * @returns True if traversal was aborted.
4465 */
4466 LLRBNode.prototype.reverseTraversal = function (action) {
4467 return (this.right.reverseTraversal(action) ||
4468 action(this.key, this.value) ||
4469 this.left.reverseTraversal(action));
4470 };
4471 /**
4472 * @returns The minimum node in the tree.
4473 */
4474 LLRBNode.prototype.min_ = function () {
4475 if (this.left.isEmpty()) {
4476 return this;
4477 }
4478 else {
4479 return this.left.min_();
4480 }
4481 };
4482 /**
4483 * @returns The maximum key in the tree.
4484 */
4485 LLRBNode.prototype.minKey = function () {
4486 return this.min_().key;
4487 };
4488 /**
4489 * @returns The maximum key in the tree.
4490 */
4491 LLRBNode.prototype.maxKey = function () {
4492 if (this.right.isEmpty()) {
4493 return this.key;
4494 }
4495 else {
4496 return this.right.maxKey();
4497 }
4498 };
4499 /**
4500 * @param key - Key to insert.
4501 * @param value - Value to insert.
4502 * @param comparator - Comparator.
4503 * @returns New tree, with the key/value added.
4504 */
4505 LLRBNode.prototype.insert = function (key, value, comparator) {
4506 var n = this;
4507 var cmp = comparator(key, n.key);
4508 if (cmp < 0) {
4509 n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
4510 }
4511 else if (cmp === 0) {
4512 n = n.copy(null, value, null, null, null);
4513 }
4514 else {
4515 n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
4516 }
4517 return n.fixUp_();
4518 };
4519 /**
4520 * @returns New tree, with the minimum key removed.
4521 */
4522 LLRBNode.prototype.removeMin_ = function () {
4523 if (this.left.isEmpty()) {
4524 return SortedMap.EMPTY_NODE;
4525 }
4526 var n = this;
4527 if (!n.left.isRed_() && !n.left.left.isRed_()) {
4528 n = n.moveRedLeft_();
4529 }
4530 n = n.copy(null, null, null, n.left.removeMin_(), null);
4531 return n.fixUp_();
4532 };
4533 /**
4534 * @param key - The key of the item to remove.
4535 * @param comparator - Comparator.
4536 * @returns New tree, with the specified item removed.
4537 */
4538 LLRBNode.prototype.remove = function (key, comparator) {
4539 var n, smallest;
4540 n = this;
4541 if (comparator(key, n.key) < 0) {
4542 if (!n.left.isEmpty() && !n.left.isRed_() && !n.left.left.isRed_()) {
4543 n = n.moveRedLeft_();
4544 }
4545 n = n.copy(null, null, null, n.left.remove(key, comparator), null);
4546 }
4547 else {
4548 if (n.left.isRed_()) {
4549 n = n.rotateRight_();
4550 }
4551 if (!n.right.isEmpty() && !n.right.isRed_() && !n.right.left.isRed_()) {
4552 n = n.moveRedRight_();
4553 }
4554 if (comparator(key, n.key) === 0) {
4555 if (n.right.isEmpty()) {
4556 return SortedMap.EMPTY_NODE;
4557 }
4558 else {
4559 smallest = n.right.min_();
4560 n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin_());
4561 }
4562 }
4563 n = n.copy(null, null, null, null, n.right.remove(key, comparator));
4564 }
4565 return n.fixUp_();
4566 };
4567 /**
4568 * @returns Whether this is a RED node.
4569 */
4570 LLRBNode.prototype.isRed_ = function () {
4571 return this.color;
4572 };
4573 /**
4574 * @returns New tree after performing any needed rotations.
4575 */
4576 LLRBNode.prototype.fixUp_ = function () {
4577 var n = this;
4578 if (n.right.isRed_() && !n.left.isRed_()) {
4579 n = n.rotateLeft_();
4580 }
4581 if (n.left.isRed_() && n.left.left.isRed_()) {
4582 n = n.rotateRight_();
4583 }
4584 if (n.left.isRed_() && n.right.isRed_()) {
4585 n = n.colorFlip_();
4586 }
4587 return n;
4588 };
4589 /**
4590 * @returns New tree, after moveRedLeft.
4591 */
4592 LLRBNode.prototype.moveRedLeft_ = function () {
4593 var n = this.colorFlip_();
4594 if (n.right.left.isRed_()) {
4595 n = n.copy(null, null, null, null, n.right.rotateRight_());
4596 n = n.rotateLeft_();
4597 n = n.colorFlip_();
4598 }
4599 return n;
4600 };
4601 /**
4602 * @returns New tree, after moveRedRight.
4603 */
4604 LLRBNode.prototype.moveRedRight_ = function () {
4605 var n = this.colorFlip_();
4606 if (n.left.left.isRed_()) {
4607 n = n.rotateRight_();
4608 n = n.colorFlip_();
4609 }
4610 return n;
4611 };
4612 /**
4613 * @returns New tree, after rotateLeft.
4614 */
4615 LLRBNode.prototype.rotateLeft_ = function () {
4616 var nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
4617 return this.right.copy(null, null, this.color, nl, null);
4618 };
4619 /**
4620 * @returns New tree, after rotateRight.
4621 */
4622 LLRBNode.prototype.rotateRight_ = function () {
4623 var nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
4624 return this.left.copy(null, null, this.color, null, nr);
4625 };
4626 /**
4627 * @returns Newt ree, after colorFlip.
4628 */
4629 LLRBNode.prototype.colorFlip_ = function () {
4630 var left = this.left.copy(null, null, !this.left.color, null, null);
4631 var right = this.right.copy(null, null, !this.right.color, null, null);
4632 return this.copy(null, null, !this.color, left, right);
4633 };
4634 /**
4635 * For testing.
4636 *
4637 * @returns True if all is well.
4638 */
4639 LLRBNode.prototype.checkMaxDepth_ = function () {
4640 var blackDepth = this.check_();
4641 return Math.pow(2.0, blackDepth) <= this.count() + 1;
4642 };
4643 LLRBNode.prototype.check_ = function () {
4644 if (this.isRed_() && this.left.isRed_()) {
4645 throw new Error('Red node has red child(' + this.key + ',' + this.value + ')');
4646 }
4647 if (this.right.isRed_()) {
4648 throw new Error('Right child of (' + this.key + ',' + this.value + ') is red');
4649 }
4650 var blackDepth = this.left.check_();
4651 if (blackDepth !== this.right.check_()) {
4652 throw new Error('Black depths differ');
4653 }
4654 else {
4655 return blackDepth + (this.isRed_() ? 0 : 1);
4656 }
4657 };
4658 LLRBNode.RED = true;
4659 LLRBNode.BLACK = false;
4660 return LLRBNode;
4661}());
4662/**
4663 * Represents an empty node (a leaf node in the Red-Black Tree).
4664 */
4665var LLRBEmptyNode = /** @class */ (function () {
4666 function LLRBEmptyNode() {
4667 }
4668 /**
4669 * Returns a copy of the current node.
4670 *
4671 * @returns The node copy.
4672 */
4673 LLRBEmptyNode.prototype.copy = function (key, value, color, left, right) {
4674 return this;
4675 };
4676 /**
4677 * Returns a copy of the tree, with the specified key/value added.
4678 *
4679 * @param key - Key to be added.
4680 * @param value - Value to be added.
4681 * @param comparator - Comparator.
4682 * @returns New tree, with item added.
4683 */
4684 LLRBEmptyNode.prototype.insert = function (key, value, comparator) {
4685 return new LLRBNode(key, value, null);
4686 };
4687 /**
4688 * Returns a copy of the tree, with the specified key removed.
4689 *
4690 * @param key - The key to remove.
4691 * @param comparator - Comparator.
4692 * @returns New tree, with item removed.
4693 */
4694 LLRBEmptyNode.prototype.remove = function (key, comparator) {
4695 return this;
4696 };
4697 /**
4698 * @returns The total number of nodes in the tree.
4699 */
4700 LLRBEmptyNode.prototype.count = function () {
4701 return 0;
4702 };
4703 /**
4704 * @returns True if the tree is empty.
4705 */
4706 LLRBEmptyNode.prototype.isEmpty = function () {
4707 return true;
4708 };
4709 /**
4710 * Traverses the tree in key order and calls the specified action function
4711 * for each node.
4712 *
4713 * @param action - Callback function to be called for each
4714 * node. If it returns true, traversal is aborted.
4715 * @returns True if traversal was aborted.
4716 */
4717 LLRBEmptyNode.prototype.inorderTraversal = function (action) {
4718 return false;
4719 };
4720 /**
4721 * Traverses the tree in reverse key order and calls the specified action function
4722 * for each node.
4723 *
4724 * @param action - Callback function to be called for each
4725 * node. If it returns true, traversal is aborted.
4726 * @returns True if traversal was aborted.
4727 */
4728 LLRBEmptyNode.prototype.reverseTraversal = function (action) {
4729 return false;
4730 };
4731 LLRBEmptyNode.prototype.minKey = function () {
4732 return null;
4733 };
4734 LLRBEmptyNode.prototype.maxKey = function () {
4735 return null;
4736 };
4737 LLRBEmptyNode.prototype.check_ = function () {
4738 return 0;
4739 };
4740 /**
4741 * @returns Whether this node is red.
4742 */
4743 LLRBEmptyNode.prototype.isRed_ = function () {
4744 return false;
4745 };
4746 return LLRBEmptyNode;
4747}());
4748/**
4749 * An immutable sorted map implementation, based on a Left-leaning Red-Black
4750 * tree.
4751 */
4752var SortedMap = /** @class */ (function () {
4753 /**
4754 * @param comparator_ - Key comparator.
4755 * @param root_ - Optional root node for the map.
4756 */
4757 function SortedMap(comparator_, root_) {
4758 if (root_ === void 0) { root_ = SortedMap.EMPTY_NODE; }
4759 this.comparator_ = comparator_;
4760 this.root_ = root_;
4761 }
4762 /**
4763 * Returns a copy of the map, with the specified key/value added or replaced.
4764 * (TODO: We should perhaps rename this method to 'put')
4765 *
4766 * @param key - Key to be added.
4767 * @param value - Value to be added.
4768 * @returns New map, with item added.
4769 */
4770 SortedMap.prototype.insert = function (key, value) {
4771 return new SortedMap(this.comparator_, this.root_
4772 .insert(key, value, this.comparator_)
4773 .copy(null, null, LLRBNode.BLACK, null, null));
4774 };
4775 /**
4776 * Returns a copy of the map, with the specified key removed.
4777 *
4778 * @param key - The key to remove.
4779 * @returns New map, with item removed.
4780 */
4781 SortedMap.prototype.remove = function (key) {
4782 return new SortedMap(this.comparator_, this.root_
4783 .remove(key, this.comparator_)
4784 .copy(null, null, LLRBNode.BLACK, null, null));
4785 };
4786 /**
4787 * Returns the value of the node with the given key, or null.
4788 *
4789 * @param key - The key to look up.
4790 * @returns The value of the node with the given key, or null if the
4791 * key doesn't exist.
4792 */
4793 SortedMap.prototype.get = function (key) {
4794 var cmp;
4795 var node = this.root_;
4796 while (!node.isEmpty()) {
4797 cmp = this.comparator_(key, node.key);
4798 if (cmp === 0) {
4799 return node.value;
4800 }
4801 else if (cmp < 0) {
4802 node = node.left;
4803 }
4804 else if (cmp > 0) {
4805 node = node.right;
4806 }
4807 }
4808 return null;
4809 };
4810 /**
4811 * Returns the key of the item *before* the specified key, or null if key is the first item.
4812 * @param key - The key to find the predecessor of
4813 * @returns The predecessor key.
4814 */
4815 SortedMap.prototype.getPredecessorKey = function (key) {
4816 var cmp, node = this.root_, rightParent = null;
4817 while (!node.isEmpty()) {
4818 cmp = this.comparator_(key, node.key);
4819 if (cmp === 0) {
4820 if (!node.left.isEmpty()) {
4821 node = node.left;
4822 while (!node.right.isEmpty()) {
4823 node = node.right;
4824 }
4825 return node.key;
4826 }
4827 else if (rightParent) {
4828 return rightParent.key;
4829 }
4830 else {
4831 return null; // first item.
4832 }
4833 }
4834 else if (cmp < 0) {
4835 node = node.left;
4836 }
4837 else if (cmp > 0) {
4838 rightParent = node;
4839 node = node.right;
4840 }
4841 }
4842 throw new Error('Attempted to find predecessor key for a nonexistent key. What gives?');
4843 };
4844 /**
4845 * @returns True if the map is empty.
4846 */
4847 SortedMap.prototype.isEmpty = function () {
4848 return this.root_.isEmpty();
4849 };
4850 /**
4851 * @returns The total number of nodes in the map.
4852 */
4853 SortedMap.prototype.count = function () {
4854 return this.root_.count();
4855 };
4856 /**
4857 * @returns The minimum key in the map.
4858 */
4859 SortedMap.prototype.minKey = function () {
4860 return this.root_.minKey();
4861 };
4862 /**
4863 * @returns The maximum key in the map.
4864 */
4865 SortedMap.prototype.maxKey = function () {
4866 return this.root_.maxKey();
4867 };
4868 /**
4869 * Traverses the map in key order and calls the specified action function
4870 * for each key/value pair.
4871 *
4872 * @param action - Callback function to be called
4873 * for each key/value pair. If action returns true, traversal is aborted.
4874 * @returns The first truthy value returned by action, or the last falsey
4875 * value returned by action
4876 */
4877 SortedMap.prototype.inorderTraversal = function (action) {
4878 return this.root_.inorderTraversal(action);
4879 };
4880 /**
4881 * Traverses the map in reverse key order and calls the specified action function
4882 * for each key/value pair.
4883 *
4884 * @param action - Callback function to be called
4885 * for each key/value pair. If action returns true, traversal is aborted.
4886 * @returns True if the traversal was aborted.
4887 */
4888 SortedMap.prototype.reverseTraversal = function (action) {
4889 return this.root_.reverseTraversal(action);
4890 };
4891 /**
4892 * Returns an iterator over the SortedMap.
4893 * @returns The iterator.
4894 */
4895 SortedMap.prototype.getIterator = function (resultGenerator) {
4896 return new SortedMapIterator(this.root_, null, this.comparator_, false, resultGenerator);
4897 };
4898 SortedMap.prototype.getIteratorFrom = function (key, resultGenerator) {
4899 return new SortedMapIterator(this.root_, key, this.comparator_, false, resultGenerator);
4900 };
4901 SortedMap.prototype.getReverseIteratorFrom = function (key, resultGenerator) {
4902 return new SortedMapIterator(this.root_, key, this.comparator_, true, resultGenerator);
4903 };
4904 SortedMap.prototype.getReverseIterator = function (resultGenerator) {
4905 return new SortedMapIterator(this.root_, null, this.comparator_, true, resultGenerator);
4906 };
4907 /**
4908 * Always use the same empty node, to reduce memory.
4909 */
4910 SortedMap.EMPTY_NODE = new LLRBEmptyNode();
4911 return SortedMap;
4912}());
4913
4914/**
4915 * @license
4916 * Copyright 2017 Google LLC
4917 *
4918 * Licensed under the Apache License, Version 2.0 (the "License");
4919 * you may not use this file except in compliance with the License.
4920 * You may obtain a copy of the License at
4921 *
4922 * http://www.apache.org/licenses/LICENSE-2.0
4923 *
4924 * Unless required by applicable law or agreed to in writing, software
4925 * distributed under the License is distributed on an "AS IS" BASIS,
4926 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4927 * See the License for the specific language governing permissions and
4928 * limitations under the License.
4929 */
4930function NAME_ONLY_COMPARATOR(left, right) {
4931 return nameCompare(left.name, right.name);
4932}
4933function NAME_COMPARATOR(left, right) {
4934 return nameCompare(left, right);
4935}
4936
4937/**
4938 * @license
4939 * Copyright 2017 Google LLC
4940 *
4941 * Licensed under the Apache License, Version 2.0 (the "License");
4942 * you may not use this file except in compliance with the License.
4943 * You may obtain a copy of the License at
4944 *
4945 * http://www.apache.org/licenses/LICENSE-2.0
4946 *
4947 * Unless required by applicable law or agreed to in writing, software
4948 * distributed under the License is distributed on an "AS IS" BASIS,
4949 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4950 * See the License for the specific language governing permissions and
4951 * limitations under the License.
4952 */
4953var MAX_NODE$2;
4954function setMaxNode$1(val) {
4955 MAX_NODE$2 = val;
4956}
4957var priorityHashText = function (priority) {
4958 if (typeof priority === 'number') {
4959 return 'number:' + doubleToIEEE754String(priority);
4960 }
4961 else {
4962 return 'string:' + priority;
4963 }
4964};
4965/**
4966 * Validates that a priority snapshot Node is valid.
4967 */
4968var validatePriorityNode = function (priorityNode) {
4969 if (priorityNode.isLeafNode()) {
4970 var val = priorityNode.val();
4971 assert(typeof val === 'string' ||
4972 typeof val === 'number' ||
4973 (typeof val === 'object' && contains(val, '.sv')), 'Priority must be a string or number.');
4974 }
4975 else {
4976 assert(priorityNode === MAX_NODE$2 || priorityNode.isEmpty(), 'priority of unexpected type.');
4977 }
4978 // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
4979 assert(priorityNode === MAX_NODE$2 || priorityNode.getPriority().isEmpty(), "Priority nodes can't have a priority of their own.");
4980};
4981
4982/**
4983 * @license
4984 * Copyright 2017 Google LLC
4985 *
4986 * Licensed under the Apache License, Version 2.0 (the "License");
4987 * you may not use this file except in compliance with the License.
4988 * You may obtain a copy of the License at
4989 *
4990 * http://www.apache.org/licenses/LICENSE-2.0
4991 *
4992 * Unless required by applicable law or agreed to in writing, software
4993 * distributed under the License is distributed on an "AS IS" BASIS,
4994 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4995 * See the License for the specific language governing permissions and
4996 * limitations under the License.
4997 */
4998var __childrenNodeConstructor;
4999/**
5000 * LeafNode is a class for storing leaf nodes in a DataSnapshot. It
5001 * implements Node and stores the value of the node (a string,
5002 * number, or boolean) accessible via getValue().
5003 */
5004var LeafNode = /** @class */ (function () {
5005 /**
5006 * @param value_ - The value to store in this leaf node. The object type is
5007 * possible in the event of a deferred value
5008 * @param priorityNode_ - The priority of this node.
5009 */
5010 function LeafNode(value_, priorityNode_) {
5011 if (priorityNode_ === void 0) { priorityNode_ = LeafNode.__childrenNodeConstructor.EMPTY_NODE; }
5012 this.value_ = value_;
5013 this.priorityNode_ = priorityNode_;
5014 this.lazyHash_ = null;
5015 assert(this.value_ !== undefined && this.value_ !== null, "LeafNode shouldn't be created with null/undefined value.");
5016 validatePriorityNode(this.priorityNode_);
5017 }
5018 Object.defineProperty(LeafNode, "__childrenNodeConstructor", {
5019 get: function () {
5020 return __childrenNodeConstructor;
5021 },
5022 set: function (val) {
5023 __childrenNodeConstructor = val;
5024 },
5025 enumerable: false,
5026 configurable: true
5027 });
5028 /** @inheritDoc */
5029 LeafNode.prototype.isLeafNode = function () {
5030 return true;
5031 };
5032 /** @inheritDoc */
5033 LeafNode.prototype.getPriority = function () {
5034 return this.priorityNode_;
5035 };
5036 /** @inheritDoc */
5037 LeafNode.prototype.updatePriority = function (newPriorityNode) {
5038 return new LeafNode(this.value_, newPriorityNode);
5039 };
5040 /** @inheritDoc */
5041 LeafNode.prototype.getImmediateChild = function (childName) {
5042 // Hack to treat priority as a regular child
5043 if (childName === '.priority') {
5044 return this.priorityNode_;
5045 }
5046 else {
5047 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5048 }
5049 };
5050 /** @inheritDoc */
5051 LeafNode.prototype.getChild = function (path) {
5052 if (pathIsEmpty(path)) {
5053 return this;
5054 }
5055 else if (pathGetFront(path) === '.priority') {
5056 return this.priorityNode_;
5057 }
5058 else {
5059 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5060 }
5061 };
5062 LeafNode.prototype.hasChild = function () {
5063 return false;
5064 };
5065 /** @inheritDoc */
5066 LeafNode.prototype.getPredecessorChildName = function (childName, childNode) {
5067 return null;
5068 };
5069 /** @inheritDoc */
5070 LeafNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5071 if (childName === '.priority') {
5072 return this.updatePriority(newChildNode);
5073 }
5074 else if (newChildNode.isEmpty() && childName !== '.priority') {
5075 return this;
5076 }
5077 else {
5078 return LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateImmediateChild(childName, newChildNode).updatePriority(this.priorityNode_);
5079 }
5080 };
5081 /** @inheritDoc */
5082 LeafNode.prototype.updateChild = function (path, newChildNode) {
5083 var front = pathGetFront(path);
5084 if (front === null) {
5085 return newChildNode;
5086 }
5087 else if (newChildNode.isEmpty() && front !== '.priority') {
5088 return this;
5089 }
5090 else {
5091 assert(front !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5092 return this.updateImmediateChild(front, LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateChild(pathPopFront(path), newChildNode));
5093 }
5094 };
5095 /** @inheritDoc */
5096 LeafNode.prototype.isEmpty = function () {
5097 return false;
5098 };
5099 /** @inheritDoc */
5100 LeafNode.prototype.numChildren = function () {
5101 return 0;
5102 };
5103 /** @inheritDoc */
5104 LeafNode.prototype.forEachChild = function (index, action) {
5105 return false;
5106 };
5107 LeafNode.prototype.val = function (exportFormat) {
5108 if (exportFormat && !this.getPriority().isEmpty()) {
5109 return {
5110 '.value': this.getValue(),
5111 '.priority': this.getPriority().val()
5112 };
5113 }
5114 else {
5115 return this.getValue();
5116 }
5117 };
5118 /** @inheritDoc */
5119 LeafNode.prototype.hash = function () {
5120 if (this.lazyHash_ === null) {
5121 var toHash = '';
5122 if (!this.priorityNode_.isEmpty()) {
5123 toHash +=
5124 'priority:' +
5125 priorityHashText(this.priorityNode_.val()) +
5126 ':';
5127 }
5128 var type = typeof this.value_;
5129 toHash += type + ':';
5130 if (type === 'number') {
5131 toHash += doubleToIEEE754String(this.value_);
5132 }
5133 else {
5134 toHash += this.value_;
5135 }
5136 this.lazyHash_ = sha1(toHash);
5137 }
5138 return this.lazyHash_;
5139 };
5140 /**
5141 * Returns the value of the leaf node.
5142 * @returns The value of the node.
5143 */
5144 LeafNode.prototype.getValue = function () {
5145 return this.value_;
5146 };
5147 LeafNode.prototype.compareTo = function (other) {
5148 if (other === LeafNode.__childrenNodeConstructor.EMPTY_NODE) {
5149 return 1;
5150 }
5151 else if (other instanceof LeafNode.__childrenNodeConstructor) {
5152 return -1;
5153 }
5154 else {
5155 assert(other.isLeafNode(), 'Unknown node type');
5156 return this.compareToLeafNode_(other);
5157 }
5158 };
5159 /**
5160 * Comparison specifically for two leaf nodes
5161 */
5162 LeafNode.prototype.compareToLeafNode_ = function (otherLeaf) {
5163 var otherLeafType = typeof otherLeaf.value_;
5164 var thisLeafType = typeof this.value_;
5165 var otherIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(otherLeafType);
5166 var thisIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(thisLeafType);
5167 assert(otherIndex >= 0, 'Unknown leaf type: ' + otherLeafType);
5168 assert(thisIndex >= 0, 'Unknown leaf type: ' + thisLeafType);
5169 if (otherIndex === thisIndex) {
5170 // Same type, compare values
5171 if (thisLeafType === 'object') {
5172 // Deferred value nodes are all equal, but we should also never get to this point...
5173 return 0;
5174 }
5175 else {
5176 // Note that this works because true > false, all others are number or string comparisons
5177 if (this.value_ < otherLeaf.value_) {
5178 return -1;
5179 }
5180 else if (this.value_ === otherLeaf.value_) {
5181 return 0;
5182 }
5183 else {
5184 return 1;
5185 }
5186 }
5187 }
5188 else {
5189 return thisIndex - otherIndex;
5190 }
5191 };
5192 LeafNode.prototype.withIndex = function () {
5193 return this;
5194 };
5195 LeafNode.prototype.isIndexed = function () {
5196 return true;
5197 };
5198 LeafNode.prototype.equals = function (other) {
5199 if (other === this) {
5200 return true;
5201 }
5202 else if (other.isLeafNode()) {
5203 var otherLeaf = other;
5204 return (this.value_ === otherLeaf.value_ &&
5205 this.priorityNode_.equals(otherLeaf.priorityNode_));
5206 }
5207 else {
5208 return false;
5209 }
5210 };
5211 /**
5212 * The sort order for comparing leaf nodes of different types. If two leaf nodes have
5213 * the same type, the comparison falls back to their value
5214 */
5215 LeafNode.VALUE_TYPE_ORDER = ['object', 'boolean', 'number', 'string'];
5216 return LeafNode;
5217}());
5218
5219/**
5220 * @license
5221 * Copyright 2017 Google LLC
5222 *
5223 * Licensed under the Apache License, Version 2.0 (the "License");
5224 * you may not use this file except in compliance with the License.
5225 * You may obtain a copy of the License at
5226 *
5227 * http://www.apache.org/licenses/LICENSE-2.0
5228 *
5229 * Unless required by applicable law or agreed to in writing, software
5230 * distributed under the License is distributed on an "AS IS" BASIS,
5231 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5232 * See the License for the specific language governing permissions and
5233 * limitations under the License.
5234 */
5235var nodeFromJSON$1;
5236var MAX_NODE$1;
5237function setNodeFromJSON(val) {
5238 nodeFromJSON$1 = val;
5239}
5240function setMaxNode(val) {
5241 MAX_NODE$1 = val;
5242}
5243var PriorityIndex = /** @class */ (function (_super) {
5244 __extends(PriorityIndex, _super);
5245 function PriorityIndex() {
5246 return _super !== null && _super.apply(this, arguments) || this;
5247 }
5248 PriorityIndex.prototype.compare = function (a, b) {
5249 var aPriority = a.node.getPriority();
5250 var bPriority = b.node.getPriority();
5251 var indexCmp = aPriority.compareTo(bPriority);
5252 if (indexCmp === 0) {
5253 return nameCompare(a.name, b.name);
5254 }
5255 else {
5256 return indexCmp;
5257 }
5258 };
5259 PriorityIndex.prototype.isDefinedOn = function (node) {
5260 return !node.getPriority().isEmpty();
5261 };
5262 PriorityIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
5263 return !oldNode.getPriority().equals(newNode.getPriority());
5264 };
5265 PriorityIndex.prototype.minPost = function () {
5266 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5267 return NamedNode.MIN;
5268 };
5269 PriorityIndex.prototype.maxPost = function () {
5270 return new NamedNode(MAX_NAME, new LeafNode('[PRIORITY-POST]', MAX_NODE$1));
5271 };
5272 PriorityIndex.prototype.makePost = function (indexValue, name) {
5273 var priorityNode = nodeFromJSON$1(indexValue);
5274 return new NamedNode(name, new LeafNode('[PRIORITY-POST]', priorityNode));
5275 };
5276 /**
5277 * @returns String representation for inclusion in a query spec
5278 */
5279 PriorityIndex.prototype.toString = function () {
5280 return '.priority';
5281 };
5282 return PriorityIndex;
5283}(Index));
5284var PRIORITY_INDEX = new PriorityIndex();
5285
5286/**
5287 * @license
5288 * Copyright 2017 Google LLC
5289 *
5290 * Licensed under the Apache License, Version 2.0 (the "License");
5291 * you may not use this file except in compliance with the License.
5292 * You may obtain a copy of the License at
5293 *
5294 * http://www.apache.org/licenses/LICENSE-2.0
5295 *
5296 * Unless required by applicable law or agreed to in writing, software
5297 * distributed under the License is distributed on an "AS IS" BASIS,
5298 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5299 * See the License for the specific language governing permissions and
5300 * limitations under the License.
5301 */
5302var LOG_2 = Math.log(2);
5303var Base12Num = /** @class */ (function () {
5304 function Base12Num(length) {
5305 var logBase2 = function (num) {
5306 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5307 return parseInt((Math.log(num) / LOG_2), 10);
5308 };
5309 var bitMask = function (bits) { return parseInt(Array(bits + 1).join('1'), 2); };
5310 this.count = logBase2(length + 1);
5311 this.current_ = this.count - 1;
5312 var mask = bitMask(this.count);
5313 this.bits_ = (length + 1) & mask;
5314 }
5315 Base12Num.prototype.nextBitIsOne = function () {
5316 //noinspection JSBitwiseOperatorUsage
5317 var result = !(this.bits_ & (0x1 << this.current_));
5318 this.current_--;
5319 return result;
5320 };
5321 return Base12Num;
5322}());
5323/**
5324 * Takes a list of child nodes and constructs a SortedSet using the given comparison
5325 * function
5326 *
5327 * Uses the algorithm described in the paper linked here:
5328 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458
5329 *
5330 * @param childList - Unsorted list of children
5331 * @param cmp - The comparison method to be used
5332 * @param keyFn - An optional function to extract K from a node wrapper, if K's
5333 * type is not NamedNode
5334 * @param mapSortFn - An optional override for comparator used by the generated sorted map
5335 */
5336var buildChildSet = function (childList, cmp, keyFn, mapSortFn) {
5337 childList.sort(cmp);
5338 var buildBalancedTree = function (low, high) {
5339 var length = high - low;
5340 var namedNode;
5341 var key;
5342 if (length === 0) {
5343 return null;
5344 }
5345 else if (length === 1) {
5346 namedNode = childList[low];
5347 key = keyFn ? keyFn(namedNode) : namedNode;
5348 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, null, null);
5349 }
5350 else {
5351 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5352 var middle = parseInt((length / 2), 10) + low;
5353 var left = buildBalancedTree(low, middle);
5354 var right = buildBalancedTree(middle + 1, high);
5355 namedNode = childList[middle];
5356 key = keyFn ? keyFn(namedNode) : namedNode;
5357 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, left, right);
5358 }
5359 };
5360 var buildFrom12Array = function (base12) {
5361 var node = null;
5362 var root = null;
5363 var index = childList.length;
5364 var buildPennant = function (chunkSize, color) {
5365 var low = index - chunkSize;
5366 var high = index;
5367 index -= chunkSize;
5368 var childTree = buildBalancedTree(low + 1, high);
5369 var namedNode = childList[low];
5370 var key = keyFn ? keyFn(namedNode) : namedNode;
5371 attachPennant(new LLRBNode(key, namedNode.node, color, null, childTree));
5372 };
5373 var attachPennant = function (pennant) {
5374 if (node) {
5375 node.left = pennant;
5376 node = pennant;
5377 }
5378 else {
5379 root = pennant;
5380 node = pennant;
5381 }
5382 };
5383 for (var i = 0; i < base12.count; ++i) {
5384 var isOne = base12.nextBitIsOne();
5385 // The number of nodes taken in each slice is 2^(arr.length - (i + 1))
5386 var chunkSize = Math.pow(2, base12.count - (i + 1));
5387 if (isOne) {
5388 buildPennant(chunkSize, LLRBNode.BLACK);
5389 }
5390 else {
5391 // current == 2
5392 buildPennant(chunkSize, LLRBNode.BLACK);
5393 buildPennant(chunkSize, LLRBNode.RED);
5394 }
5395 }
5396 return root;
5397 };
5398 var base12 = new Base12Num(childList.length);
5399 var root = buildFrom12Array(base12);
5400 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5401 return new SortedMap(mapSortFn || cmp, root);
5402};
5403
5404/**
5405 * @license
5406 * Copyright 2017 Google LLC
5407 *
5408 * Licensed under the Apache License, Version 2.0 (the "License");
5409 * you may not use this file except in compliance with the License.
5410 * You may obtain a copy of the License at
5411 *
5412 * http://www.apache.org/licenses/LICENSE-2.0
5413 *
5414 * Unless required by applicable law or agreed to in writing, software
5415 * distributed under the License is distributed on an "AS IS" BASIS,
5416 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5417 * See the License for the specific language governing permissions and
5418 * limitations under the License.
5419 */
5420var _defaultIndexMap;
5421var fallbackObject = {};
5422var IndexMap = /** @class */ (function () {
5423 function IndexMap(indexes_, indexSet_) {
5424 this.indexes_ = indexes_;
5425 this.indexSet_ = indexSet_;
5426 }
5427 Object.defineProperty(IndexMap, "Default", {
5428 /**
5429 * The default IndexMap for nodes without a priority
5430 */
5431 get: function () {
5432 assert(fallbackObject && PRIORITY_INDEX, 'ChildrenNode.ts has not been loaded');
5433 _defaultIndexMap =
5434 _defaultIndexMap ||
5435 new IndexMap({ '.priority': fallbackObject }, { '.priority': PRIORITY_INDEX });
5436 return _defaultIndexMap;
5437 },
5438 enumerable: false,
5439 configurable: true
5440 });
5441 IndexMap.prototype.get = function (indexKey) {
5442 var sortedMap = safeGet(this.indexes_, indexKey);
5443 if (!sortedMap) {
5444 throw new Error('No index defined for ' + indexKey);
5445 }
5446 if (sortedMap instanceof SortedMap) {
5447 return sortedMap;
5448 }
5449 else {
5450 // The index exists, but it falls back to just name comparison. Return null so that the calling code uses the
5451 // regular child map
5452 return null;
5453 }
5454 };
5455 IndexMap.prototype.hasIndex = function (indexDefinition) {
5456 return contains(this.indexSet_, indexDefinition.toString());
5457 };
5458 IndexMap.prototype.addIndex = function (indexDefinition, existingChildren) {
5459 assert(indexDefinition !== KEY_INDEX, "KeyIndex always exists and isn't meant to be added to the IndexMap.");
5460 var childList = [];
5461 var sawIndexedValue = false;
5462 var iter = existingChildren.getIterator(NamedNode.Wrap);
5463 var next = iter.getNext();
5464 while (next) {
5465 sawIndexedValue =
5466 sawIndexedValue || indexDefinition.isDefinedOn(next.node);
5467 childList.push(next);
5468 next = iter.getNext();
5469 }
5470 var newIndex;
5471 if (sawIndexedValue) {
5472 newIndex = buildChildSet(childList, indexDefinition.getCompare());
5473 }
5474 else {
5475 newIndex = fallbackObject;
5476 }
5477 var indexName = indexDefinition.toString();
5478 var newIndexSet = __assign({}, this.indexSet_);
5479 newIndexSet[indexName] = indexDefinition;
5480 var newIndexes = __assign({}, this.indexes_);
5481 newIndexes[indexName] = newIndex;
5482 return new IndexMap(newIndexes, newIndexSet);
5483 };
5484 /**
5485 * Ensure that this node is properly tracked in any indexes that we're maintaining
5486 */
5487 IndexMap.prototype.addToIndexes = function (namedNode, existingChildren) {
5488 var _this = this;
5489 var newIndexes = map(this.indexes_, function (indexedChildren, indexName) {
5490 var index = safeGet(_this.indexSet_, indexName);
5491 assert(index, 'Missing index implementation for ' + indexName);
5492 if (indexedChildren === fallbackObject) {
5493 // Check to see if we need to index everything
5494 if (index.isDefinedOn(namedNode.node)) {
5495 // We need to build this index
5496 var childList = [];
5497 var iter = existingChildren.getIterator(NamedNode.Wrap);
5498 var next = iter.getNext();
5499 while (next) {
5500 if (next.name !== namedNode.name) {
5501 childList.push(next);
5502 }
5503 next = iter.getNext();
5504 }
5505 childList.push(namedNode);
5506 return buildChildSet(childList, index.getCompare());
5507 }
5508 else {
5509 // No change, this remains a fallback
5510 return fallbackObject;
5511 }
5512 }
5513 else {
5514 var existingSnap = existingChildren.get(namedNode.name);
5515 var newChildren = indexedChildren;
5516 if (existingSnap) {
5517 newChildren = newChildren.remove(new NamedNode(namedNode.name, existingSnap));
5518 }
5519 return newChildren.insert(namedNode, namedNode.node);
5520 }
5521 });
5522 return new IndexMap(newIndexes, this.indexSet_);
5523 };
5524 /**
5525 * Create a new IndexMap instance with the given value removed
5526 */
5527 IndexMap.prototype.removeFromIndexes = function (namedNode, existingChildren) {
5528 var newIndexes = map(this.indexes_, function (indexedChildren) {
5529 if (indexedChildren === fallbackObject) {
5530 // This is the fallback. Just return it, nothing to do in this case
5531 return indexedChildren;
5532 }
5533 else {
5534 var existingSnap = existingChildren.get(namedNode.name);
5535 if (existingSnap) {
5536 return indexedChildren.remove(new NamedNode(namedNode.name, existingSnap));
5537 }
5538 else {
5539 // No record of this child
5540 return indexedChildren;
5541 }
5542 }
5543 });
5544 return new IndexMap(newIndexes, this.indexSet_);
5545 };
5546 return IndexMap;
5547}());
5548
5549/**
5550 * @license
5551 * Copyright 2017 Google LLC
5552 *
5553 * Licensed under the Apache License, Version 2.0 (the "License");
5554 * you may not use this file except in compliance with the License.
5555 * You may obtain a copy of the License at
5556 *
5557 * http://www.apache.org/licenses/LICENSE-2.0
5558 *
5559 * Unless required by applicable law or agreed to in writing, software
5560 * distributed under the License is distributed on an "AS IS" BASIS,
5561 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5562 * See the License for the specific language governing permissions and
5563 * limitations under the License.
5564 */
5565// TODO: For memory savings, don't store priorityNode_ if it's empty.
5566var EMPTY_NODE;
5567/**
5568 * ChildrenNode is a class for storing internal nodes in a DataSnapshot
5569 * (i.e. nodes with children). It implements Node and stores the
5570 * list of children in the children property, sorted by child name.
5571 */
5572var ChildrenNode = /** @class */ (function () {
5573 /**
5574 * @param children_ - List of children of this node..
5575 * @param priorityNode_ - The priority of this node (as a snapshot node).
5576 */
5577 function ChildrenNode(children_, priorityNode_, indexMap_) {
5578 this.children_ = children_;
5579 this.priorityNode_ = priorityNode_;
5580 this.indexMap_ = indexMap_;
5581 this.lazyHash_ = null;
5582 /**
5583 * Note: The only reason we allow null priority is for EMPTY_NODE, since we can't use
5584 * EMPTY_NODE as the priority of EMPTY_NODE. We might want to consider making EMPTY_NODE its own
5585 * class instead of an empty ChildrenNode.
5586 */
5587 if (this.priorityNode_) {
5588 validatePriorityNode(this.priorityNode_);
5589 }
5590 if (this.children_.isEmpty()) {
5591 assert(!this.priorityNode_ || this.priorityNode_.isEmpty(), 'An empty node cannot have a priority');
5592 }
5593 }
5594 Object.defineProperty(ChildrenNode, "EMPTY_NODE", {
5595 get: function () {
5596 return (EMPTY_NODE ||
5597 (EMPTY_NODE = new ChildrenNode(new SortedMap(NAME_COMPARATOR), null, IndexMap.Default)));
5598 },
5599 enumerable: false,
5600 configurable: true
5601 });
5602 /** @inheritDoc */
5603 ChildrenNode.prototype.isLeafNode = function () {
5604 return false;
5605 };
5606 /** @inheritDoc */
5607 ChildrenNode.prototype.getPriority = function () {
5608 return this.priorityNode_ || EMPTY_NODE;
5609 };
5610 /** @inheritDoc */
5611 ChildrenNode.prototype.updatePriority = function (newPriorityNode) {
5612 if (this.children_.isEmpty()) {
5613 // Don't allow priorities on empty nodes
5614 return this;
5615 }
5616 else {
5617 return new ChildrenNode(this.children_, newPriorityNode, this.indexMap_);
5618 }
5619 };
5620 /** @inheritDoc */
5621 ChildrenNode.prototype.getImmediateChild = function (childName) {
5622 // Hack to treat priority as a regular child
5623 if (childName === '.priority') {
5624 return this.getPriority();
5625 }
5626 else {
5627 var child = this.children_.get(childName);
5628 return child === null ? EMPTY_NODE : child;
5629 }
5630 };
5631 /** @inheritDoc */
5632 ChildrenNode.prototype.getChild = function (path) {
5633 var front = pathGetFront(path);
5634 if (front === null) {
5635 return this;
5636 }
5637 return this.getImmediateChild(front).getChild(pathPopFront(path));
5638 };
5639 /** @inheritDoc */
5640 ChildrenNode.prototype.hasChild = function (childName) {
5641 return this.children_.get(childName) !== null;
5642 };
5643 /** @inheritDoc */
5644 ChildrenNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5645 assert(newChildNode, 'We should always be passing snapshot nodes');
5646 if (childName === '.priority') {
5647 return this.updatePriority(newChildNode);
5648 }
5649 else {
5650 var namedNode = new NamedNode(childName, newChildNode);
5651 var newChildren = void 0, newIndexMap = void 0;
5652 if (newChildNode.isEmpty()) {
5653 newChildren = this.children_.remove(childName);
5654 newIndexMap = this.indexMap_.removeFromIndexes(namedNode, this.children_);
5655 }
5656 else {
5657 newChildren = this.children_.insert(childName, newChildNode);
5658 newIndexMap = this.indexMap_.addToIndexes(namedNode, this.children_);
5659 }
5660 var newPriority = newChildren.isEmpty()
5661 ? EMPTY_NODE
5662 : this.priorityNode_;
5663 return new ChildrenNode(newChildren, newPriority, newIndexMap);
5664 }
5665 };
5666 /** @inheritDoc */
5667 ChildrenNode.prototype.updateChild = function (path, newChildNode) {
5668 var front = pathGetFront(path);
5669 if (front === null) {
5670 return newChildNode;
5671 }
5672 else {
5673 assert(pathGetFront(path) !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5674 var newImmediateChild = this.getImmediateChild(front).updateChild(pathPopFront(path), newChildNode);
5675 return this.updateImmediateChild(front, newImmediateChild);
5676 }
5677 };
5678 /** @inheritDoc */
5679 ChildrenNode.prototype.isEmpty = function () {
5680 return this.children_.isEmpty();
5681 };
5682 /** @inheritDoc */
5683 ChildrenNode.prototype.numChildren = function () {
5684 return this.children_.count();
5685 };
5686 /** @inheritDoc */
5687 ChildrenNode.prototype.val = function (exportFormat) {
5688 if (this.isEmpty()) {
5689 return null;
5690 }
5691 var obj = {};
5692 var numKeys = 0, maxKey = 0, allIntegerKeys = true;
5693 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5694 obj[key] = childNode.val(exportFormat);
5695 numKeys++;
5696 if (allIntegerKeys && ChildrenNode.INTEGER_REGEXP_.test(key)) {
5697 maxKey = Math.max(maxKey, Number(key));
5698 }
5699 else {
5700 allIntegerKeys = false;
5701 }
5702 });
5703 if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
5704 // convert to array.
5705 var array = [];
5706 // eslint-disable-next-line guard-for-in
5707 for (var key in obj) {
5708 array[key] = obj[key];
5709 }
5710 return array;
5711 }
5712 else {
5713 if (exportFormat && !this.getPriority().isEmpty()) {
5714 obj['.priority'] = this.getPriority().val();
5715 }
5716 return obj;
5717 }
5718 };
5719 /** @inheritDoc */
5720 ChildrenNode.prototype.hash = function () {
5721 if (this.lazyHash_ === null) {
5722 var toHash_1 = '';
5723 if (!this.getPriority().isEmpty()) {
5724 toHash_1 +=
5725 'priority:' +
5726 priorityHashText(this.getPriority().val()) +
5727 ':';
5728 }
5729 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5730 var childHash = childNode.hash();
5731 if (childHash !== '') {
5732 toHash_1 += ':' + key + ':' + childHash;
5733 }
5734 });
5735 this.lazyHash_ = toHash_1 === '' ? '' : sha1(toHash_1);
5736 }
5737 return this.lazyHash_;
5738 };
5739 /** @inheritDoc */
5740 ChildrenNode.prototype.getPredecessorChildName = function (childName, childNode, index) {
5741 var idx = this.resolveIndex_(index);
5742 if (idx) {
5743 var predecessor = idx.getPredecessorKey(new NamedNode(childName, childNode));
5744 return predecessor ? predecessor.name : null;
5745 }
5746 else {
5747 return this.children_.getPredecessorKey(childName);
5748 }
5749 };
5750 ChildrenNode.prototype.getFirstChildName = function (indexDefinition) {
5751 var idx = this.resolveIndex_(indexDefinition);
5752 if (idx) {
5753 var minKey = idx.minKey();
5754 return minKey && minKey.name;
5755 }
5756 else {
5757 return this.children_.minKey();
5758 }
5759 };
5760 ChildrenNode.prototype.getFirstChild = function (indexDefinition) {
5761 var minKey = this.getFirstChildName(indexDefinition);
5762 if (minKey) {
5763 return new NamedNode(minKey, this.children_.get(minKey));
5764 }
5765 else {
5766 return null;
5767 }
5768 };
5769 /**
5770 * Given an index, return the key name of the largest value we have, according to that index
5771 */
5772 ChildrenNode.prototype.getLastChildName = function (indexDefinition) {
5773 var idx = this.resolveIndex_(indexDefinition);
5774 if (idx) {
5775 var maxKey = idx.maxKey();
5776 return maxKey && maxKey.name;
5777 }
5778 else {
5779 return this.children_.maxKey();
5780 }
5781 };
5782 ChildrenNode.prototype.getLastChild = function (indexDefinition) {
5783 var maxKey = this.getLastChildName(indexDefinition);
5784 if (maxKey) {
5785 return new NamedNode(maxKey, this.children_.get(maxKey));
5786 }
5787 else {
5788 return null;
5789 }
5790 };
5791 ChildrenNode.prototype.forEachChild = function (index, action) {
5792 var idx = this.resolveIndex_(index);
5793 if (idx) {
5794 return idx.inorderTraversal(function (wrappedNode) {
5795 return action(wrappedNode.name, wrappedNode.node);
5796 });
5797 }
5798 else {
5799 return this.children_.inorderTraversal(action);
5800 }
5801 };
5802 ChildrenNode.prototype.getIterator = function (indexDefinition) {
5803 return this.getIteratorFrom(indexDefinition.minPost(), indexDefinition);
5804 };
5805 ChildrenNode.prototype.getIteratorFrom = function (startPost, indexDefinition) {
5806 var idx = this.resolveIndex_(indexDefinition);
5807 if (idx) {
5808 return idx.getIteratorFrom(startPost, function (key) { return key; });
5809 }
5810 else {
5811 var iterator = this.children_.getIteratorFrom(startPost.name, NamedNode.Wrap);
5812 var next = iterator.peek();
5813 while (next != null && indexDefinition.compare(next, startPost) < 0) {
5814 iterator.getNext();
5815 next = iterator.peek();
5816 }
5817 return iterator;
5818 }
5819 };
5820 ChildrenNode.prototype.getReverseIterator = function (indexDefinition) {
5821 return this.getReverseIteratorFrom(indexDefinition.maxPost(), indexDefinition);
5822 };
5823 ChildrenNode.prototype.getReverseIteratorFrom = function (endPost, indexDefinition) {
5824 var idx = this.resolveIndex_(indexDefinition);
5825 if (idx) {
5826 return idx.getReverseIteratorFrom(endPost, function (key) {
5827 return key;
5828 });
5829 }
5830 else {
5831 var iterator = this.children_.getReverseIteratorFrom(endPost.name, NamedNode.Wrap);
5832 var next = iterator.peek();
5833 while (next != null && indexDefinition.compare(next, endPost) > 0) {
5834 iterator.getNext();
5835 next = iterator.peek();
5836 }
5837 return iterator;
5838 }
5839 };
5840 ChildrenNode.prototype.compareTo = function (other) {
5841 if (this.isEmpty()) {
5842 if (other.isEmpty()) {
5843 return 0;
5844 }
5845 else {
5846 return -1;
5847 }
5848 }
5849 else if (other.isLeafNode() || other.isEmpty()) {
5850 return 1;
5851 }
5852 else if (other === MAX_NODE) {
5853 return -1;
5854 }
5855 else {
5856 // Must be another node with children.
5857 return 0;
5858 }
5859 };
5860 ChildrenNode.prototype.withIndex = function (indexDefinition) {
5861 if (indexDefinition === KEY_INDEX ||
5862 this.indexMap_.hasIndex(indexDefinition)) {
5863 return this;
5864 }
5865 else {
5866 var newIndexMap = this.indexMap_.addIndex(indexDefinition, this.children_);
5867 return new ChildrenNode(this.children_, this.priorityNode_, newIndexMap);
5868 }
5869 };
5870 ChildrenNode.prototype.isIndexed = function (index) {
5871 return index === KEY_INDEX || this.indexMap_.hasIndex(index);
5872 };
5873 ChildrenNode.prototype.equals = function (other) {
5874 if (other === this) {
5875 return true;
5876 }
5877 else if (other.isLeafNode()) {
5878 return false;
5879 }
5880 else {
5881 var otherChildrenNode = other;
5882 if (!this.getPriority().equals(otherChildrenNode.getPriority())) {
5883 return false;
5884 }
5885 else if (this.children_.count() === otherChildrenNode.children_.count()) {
5886 var thisIter = this.getIterator(PRIORITY_INDEX);
5887 var otherIter = otherChildrenNode.getIterator(PRIORITY_INDEX);
5888 var thisCurrent = thisIter.getNext();
5889 var otherCurrent = otherIter.getNext();
5890 while (thisCurrent && otherCurrent) {
5891 if (thisCurrent.name !== otherCurrent.name ||
5892 !thisCurrent.node.equals(otherCurrent.node)) {
5893 return false;
5894 }
5895 thisCurrent = thisIter.getNext();
5896 otherCurrent = otherIter.getNext();
5897 }
5898 return thisCurrent === null && otherCurrent === null;
5899 }
5900 else {
5901 return false;
5902 }
5903 }
5904 };
5905 /**
5906 * Returns a SortedMap ordered by index, or null if the default (by-key) ordering can be used
5907 * instead.
5908 *
5909 */
5910 ChildrenNode.prototype.resolveIndex_ = function (indexDefinition) {
5911 if (indexDefinition === KEY_INDEX) {
5912 return null;
5913 }
5914 else {
5915 return this.indexMap_.get(indexDefinition.toString());
5916 }
5917 };
5918 ChildrenNode.INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;
5919 return ChildrenNode;
5920}());
5921var MaxNode = /** @class */ (function (_super) {
5922 __extends(MaxNode, _super);
5923 function MaxNode() {
5924 return _super.call(this, new SortedMap(NAME_COMPARATOR), ChildrenNode.EMPTY_NODE, IndexMap.Default) || this;
5925 }
5926 MaxNode.prototype.compareTo = function (other) {
5927 if (other === this) {
5928 return 0;
5929 }
5930 else {
5931 return 1;
5932 }
5933 };
5934 MaxNode.prototype.equals = function (other) {
5935 // Not that we every compare it, but MAX_NODE is only ever equal to itself
5936 return other === this;
5937 };
5938 MaxNode.prototype.getPriority = function () {
5939 return this;
5940 };
5941 MaxNode.prototype.getImmediateChild = function (childName) {
5942 return ChildrenNode.EMPTY_NODE;
5943 };
5944 MaxNode.prototype.isEmpty = function () {
5945 return false;
5946 };
5947 return MaxNode;
5948}(ChildrenNode));
5949/**
5950 * Marker that will sort higher than any other snapshot.
5951 */
5952var MAX_NODE = new MaxNode();
5953Object.defineProperties(NamedNode, {
5954 MIN: {
5955 value: new NamedNode(MIN_NAME, ChildrenNode.EMPTY_NODE)
5956 },
5957 MAX: {
5958 value: new NamedNode(MAX_NAME, MAX_NODE)
5959 }
5960});
5961/**
5962 * Reference Extensions
5963 */
5964KeyIndex.__EMPTY_NODE = ChildrenNode.EMPTY_NODE;
5965LeafNode.__childrenNodeConstructor = ChildrenNode;
5966setMaxNode$1(MAX_NODE);
5967setMaxNode(MAX_NODE);
5968
5969/**
5970 * @license
5971 * Copyright 2017 Google LLC
5972 *
5973 * Licensed under the Apache License, Version 2.0 (the "License");
5974 * you may not use this file except in compliance with the License.
5975 * You may obtain a copy of the License at
5976 *
5977 * http://www.apache.org/licenses/LICENSE-2.0
5978 *
5979 * Unless required by applicable law or agreed to in writing, software
5980 * distributed under the License is distributed on an "AS IS" BASIS,
5981 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5982 * See the License for the specific language governing permissions and
5983 * limitations under the License.
5984 */
5985var USE_HINZE = true;
5986/**
5987 * Constructs a snapshot node representing the passed JSON and returns it.
5988 * @param json - JSON to create a node for.
5989 * @param priority - Optional priority to use. This will be ignored if the
5990 * passed JSON contains a .priority property.
5991 */
5992function nodeFromJSON(json, priority) {
5993 if (priority === void 0) { priority = null; }
5994 if (json === null) {
5995 return ChildrenNode.EMPTY_NODE;
5996 }
5997 if (typeof json === 'object' && '.priority' in json) {
5998 priority = json['.priority'];
5999 }
6000 assert(priority === null ||
6001 typeof priority === 'string' ||
6002 typeof priority === 'number' ||
6003 (typeof priority === 'object' && '.sv' in priority), 'Invalid priority type found: ' + typeof priority);
6004 if (typeof json === 'object' && '.value' in json && json['.value'] !== null) {
6005 json = json['.value'];
6006 }
6007 // Valid leaf nodes include non-objects or server-value wrapper objects
6008 if (typeof json !== 'object' || '.sv' in json) {
6009 var jsonLeaf = json;
6010 return new LeafNode(jsonLeaf, nodeFromJSON(priority));
6011 }
6012 if (!(json instanceof Array) && USE_HINZE) {
6013 var children_1 = [];
6014 var childrenHavePriority_1 = false;
6015 var hinzeJsonObj = json;
6016 each(hinzeJsonObj, function (key, child) {
6017 if (key.substring(0, 1) !== '.') {
6018 // Ignore metadata nodes
6019 var childNode = nodeFromJSON(child);
6020 if (!childNode.isEmpty()) {
6021 childrenHavePriority_1 =
6022 childrenHavePriority_1 || !childNode.getPriority().isEmpty();
6023 children_1.push(new NamedNode(key, childNode));
6024 }
6025 }
6026 });
6027 if (children_1.length === 0) {
6028 return ChildrenNode.EMPTY_NODE;
6029 }
6030 var childSet = buildChildSet(children_1, NAME_ONLY_COMPARATOR, function (namedNode) { return namedNode.name; }, NAME_COMPARATOR);
6031 if (childrenHavePriority_1) {
6032 var sortedChildSet = buildChildSet(children_1, PRIORITY_INDEX.getCompare());
6033 return new ChildrenNode(childSet, nodeFromJSON(priority), new IndexMap({ '.priority': sortedChildSet }, { '.priority': PRIORITY_INDEX }));
6034 }
6035 else {
6036 return new ChildrenNode(childSet, nodeFromJSON(priority), IndexMap.Default);
6037 }
6038 }
6039 else {
6040 var node_1 = ChildrenNode.EMPTY_NODE;
6041 each(json, function (key, childData) {
6042 if (contains(json, key)) {
6043 if (key.substring(0, 1) !== '.') {
6044 // ignore metadata nodes.
6045 var childNode = nodeFromJSON(childData);
6046 if (childNode.isLeafNode() || !childNode.isEmpty()) {
6047 node_1 = node_1.updateImmediateChild(key, childNode);
6048 }
6049 }
6050 }
6051 });
6052 return node_1.updatePriority(nodeFromJSON(priority));
6053 }
6054}
6055setNodeFromJSON(nodeFromJSON);
6056
6057/**
6058 * @license
6059 * Copyright 2017 Google LLC
6060 *
6061 * Licensed under the Apache License, Version 2.0 (the "License");
6062 * you may not use this file except in compliance with the License.
6063 * You may obtain a copy of the License at
6064 *
6065 * http://www.apache.org/licenses/LICENSE-2.0
6066 *
6067 * Unless required by applicable law or agreed to in writing, software
6068 * distributed under the License is distributed on an "AS IS" BASIS,
6069 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6070 * See the License for the specific language governing permissions and
6071 * limitations under the License.
6072 */
6073var PathIndex = /** @class */ (function (_super) {
6074 __extends(PathIndex, _super);
6075 function PathIndex(indexPath_) {
6076 var _this = _super.call(this) || this;
6077 _this.indexPath_ = indexPath_;
6078 assert(!pathIsEmpty(indexPath_) && pathGetFront(indexPath_) !== '.priority', "Can't create PathIndex with empty path or .priority key");
6079 return _this;
6080 }
6081 PathIndex.prototype.extractChild = function (snap) {
6082 return snap.getChild(this.indexPath_);
6083 };
6084 PathIndex.prototype.isDefinedOn = function (node) {
6085 return !node.getChild(this.indexPath_).isEmpty();
6086 };
6087 PathIndex.prototype.compare = function (a, b) {
6088 var aChild = this.extractChild(a.node);
6089 var bChild = this.extractChild(b.node);
6090 var indexCmp = aChild.compareTo(bChild);
6091 if (indexCmp === 0) {
6092 return nameCompare(a.name, b.name);
6093 }
6094 else {
6095 return indexCmp;
6096 }
6097 };
6098 PathIndex.prototype.makePost = function (indexValue, name) {
6099 var valueNode = nodeFromJSON(indexValue);
6100 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, valueNode);
6101 return new NamedNode(name, node);
6102 };
6103 PathIndex.prototype.maxPost = function () {
6104 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, MAX_NODE);
6105 return new NamedNode(MAX_NAME, node);
6106 };
6107 PathIndex.prototype.toString = function () {
6108 return pathSlice(this.indexPath_, 0).join('/');
6109 };
6110 return PathIndex;
6111}(Index));
6112
6113/**
6114 * @license
6115 * Copyright 2017 Google LLC
6116 *
6117 * Licensed under the Apache License, Version 2.0 (the "License");
6118 * you may not use this file except in compliance with the License.
6119 * You may obtain a copy of the License at
6120 *
6121 * http://www.apache.org/licenses/LICENSE-2.0
6122 *
6123 * Unless required by applicable law or agreed to in writing, software
6124 * distributed under the License is distributed on an "AS IS" BASIS,
6125 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6126 * See the License for the specific language governing permissions and
6127 * limitations under the License.
6128 */
6129var ValueIndex = /** @class */ (function (_super) {
6130 __extends(ValueIndex, _super);
6131 function ValueIndex() {
6132 return _super !== null && _super.apply(this, arguments) || this;
6133 }
6134 ValueIndex.prototype.compare = function (a, b) {
6135 var indexCmp = a.node.compareTo(b.node);
6136 if (indexCmp === 0) {
6137 return nameCompare(a.name, b.name);
6138 }
6139 else {
6140 return indexCmp;
6141 }
6142 };
6143 ValueIndex.prototype.isDefinedOn = function (node) {
6144 return true;
6145 };
6146 ValueIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
6147 return !oldNode.equals(newNode);
6148 };
6149 ValueIndex.prototype.minPost = function () {
6150 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6151 return NamedNode.MIN;
6152 };
6153 ValueIndex.prototype.maxPost = function () {
6154 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6155 return NamedNode.MAX;
6156 };
6157 ValueIndex.prototype.makePost = function (indexValue, name) {
6158 var valueNode = nodeFromJSON(indexValue);
6159 return new NamedNode(name, valueNode);
6160 };
6161 /**
6162 * @returns String representation for inclusion in a query spec
6163 */
6164 ValueIndex.prototype.toString = function () {
6165 return '.value';
6166 };
6167 return ValueIndex;
6168}(Index));
6169var VALUE_INDEX = new ValueIndex();
6170
6171/**
6172 * @license
6173 * Copyright 2017 Google LLC
6174 *
6175 * Licensed under the Apache License, Version 2.0 (the "License");
6176 * you may not use this file except in compliance with the License.
6177 * You may obtain a copy of the License at
6178 *
6179 * http://www.apache.org/licenses/LICENSE-2.0
6180 *
6181 * Unless required by applicable law or agreed to in writing, software
6182 * distributed under the License is distributed on an "AS IS" BASIS,
6183 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6184 * See the License for the specific language governing permissions and
6185 * limitations under the License.
6186 */
6187// Modeled after base64 web-safe chars, but ordered by ASCII.
6188var PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
6189var MIN_PUSH_CHAR = '-';
6190var MAX_PUSH_CHAR = 'z';
6191var MAX_KEY_LEN = 786;
6192/**
6193 * Fancy ID generator that creates 20-character string identifiers with the
6194 * following properties:
6195 *
6196 * 1. They're based on timestamp so that they sort *after* any existing ids.
6197 * 2. They contain 72-bits of random data after the timestamp so that IDs won't
6198 * collide with other clients' IDs.
6199 * 3. They sort *lexicographically* (so the timestamp is converted to characters
6200 * that will sort properly).
6201 * 4. They're monotonically increasing. Even if you generate more than one in
6202 * the same timestamp, the latter ones will sort after the former ones. We do
6203 * this by using the previous random bits but "incrementing" them by 1 (only
6204 * in the case of a timestamp collision).
6205 */
6206var nextPushId = (function () {
6207 // Timestamp of last push, used to prevent local collisions if you push twice
6208 // in one ms.
6209 var lastPushTime = 0;
6210 // We generate 72-bits of randomness which get turned into 12 characters and
6211 // appended to the timestamp to prevent collisions with other clients. We
6212 // store the last characters we generated because in the event of a collision,
6213 // we'll use those same characters except "incremented" by one.
6214 var lastRandChars = [];
6215 return function (now) {
6216 var duplicateTime = now === lastPushTime;
6217 lastPushTime = now;
6218 var i;
6219 var timeStampChars = new Array(8);
6220 for (i = 7; i >= 0; i--) {
6221 timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
6222 // NOTE: Can't use << here because javascript will convert to int and lose
6223 // the upper bits.
6224 now = Math.floor(now / 64);
6225 }
6226 assert(now === 0, 'Cannot push at time == 0');
6227 var id = timeStampChars.join('');
6228 if (!duplicateTime) {
6229 for (i = 0; i < 12; i++) {
6230 lastRandChars[i] = Math.floor(Math.random() * 64);
6231 }
6232 }
6233 else {
6234 // If the timestamp hasn't changed since last push, use the same random
6235 // number, except incremented by 1.
6236 for (i = 11; i >= 0 && lastRandChars[i] === 63; i--) {
6237 lastRandChars[i] = 0;
6238 }
6239 lastRandChars[i]++;
6240 }
6241 for (i = 0; i < 12; i++) {
6242 id += PUSH_CHARS.charAt(lastRandChars[i]);
6243 }
6244 assert(id.length === 20, 'nextPushId: Length should be 20.');
6245 return id;
6246 };
6247})();
6248var successor = function (key) {
6249 if (key === '' + INTEGER_32_MAX) {
6250 // See https://firebase.google.com/docs/database/web/lists-of-data#data-order
6251 return MIN_PUSH_CHAR;
6252 }
6253 var keyAsInt = tryParseInt(key);
6254 if (keyAsInt != null) {
6255 return '' + (keyAsInt + 1);
6256 }
6257 var next = new Array(key.length);
6258 for (var i_1 = 0; i_1 < next.length; i_1++) {
6259 next[i_1] = key.charAt(i_1);
6260 }
6261 if (next.length < MAX_KEY_LEN) {
6262 next.push(MIN_PUSH_CHAR);
6263 return next.join('');
6264 }
6265 var i = next.length - 1;
6266 while (i >= 0 && next[i] === MAX_PUSH_CHAR) {
6267 i--;
6268 }
6269 // `successor` was called on the largest possible key, so return the
6270 // MAX_NAME, which sorts larger than all keys.
6271 if (i === -1) {
6272 return MAX_NAME;
6273 }
6274 var source = next[i];
6275 var sourcePlusOne = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(source) + 1);
6276 next[i] = sourcePlusOne;
6277 return next.slice(0, i + 1).join('');
6278};
6279// `key` is assumed to be non-empty.
6280var predecessor = function (key) {
6281 if (key === '' + INTEGER_32_MIN) {
6282 return MIN_NAME;
6283 }
6284 var keyAsInt = tryParseInt(key);
6285 if (keyAsInt != null) {
6286 return '' + (keyAsInt - 1);
6287 }
6288 var next = new Array(key.length);
6289 for (var i = 0; i < next.length; i++) {
6290 next[i] = key.charAt(i);
6291 }
6292 // If `key` ends in `MIN_PUSH_CHAR`, the largest key lexicographically
6293 // smaller than `key`, is `key[0:key.length - 1]`. The next key smaller
6294 // than that, `predecessor(predecessor(key))`, is
6295 //
6296 // `key[0:key.length - 2] + (key[key.length - 1] - 1) + \
6297 // { MAX_PUSH_CHAR repeated MAX_KEY_LEN - (key.length - 1) times }
6298 //
6299 // analogous to increment/decrement for base-10 integers.
6300 //
6301 // This works because lexigographic comparison works character-by-character,
6302 // using length as a tie-breaker if one key is a prefix of the other.
6303 if (next[next.length - 1] === MIN_PUSH_CHAR) {
6304 if (next.length === 1) {
6305 // See https://firebase.google.com/docs/database/web/lists-of-data#orderbykey
6306 return '' + INTEGER_32_MAX;
6307 }
6308 delete next[next.length - 1];
6309 return next.join('');
6310 }
6311 // Replace the last character with it's immediate predecessor, and
6312 // fill the suffix of the key with MAX_PUSH_CHAR. This is the
6313 // lexicographically largest possible key smaller than `key`.
6314 next[next.length - 1] = PUSH_CHARS.charAt(PUSH_CHARS.indexOf(next[next.length - 1]) - 1);
6315 return next.join('') + MAX_PUSH_CHAR.repeat(MAX_KEY_LEN - next.length);
6316};
6317
6318/**
6319 * @license
6320 * Copyright 2017 Google LLC
6321 *
6322 * Licensed under the Apache License, Version 2.0 (the "License");
6323 * you may not use this file except in compliance with the License.
6324 * You may obtain a copy of the License at
6325 *
6326 * http://www.apache.org/licenses/LICENSE-2.0
6327 *
6328 * Unless required by applicable law or agreed to in writing, software
6329 * distributed under the License is distributed on an "AS IS" BASIS,
6330 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6331 * See the License for the specific language governing permissions and
6332 * limitations under the License.
6333 */
6334function changeValue(snapshotNode) {
6335 return { type: "value" /* VALUE */, snapshotNode: snapshotNode };
6336}
6337function changeChildAdded(childName, snapshotNode) {
6338 return { type: "child_added" /* CHILD_ADDED */, snapshotNode: snapshotNode, childName: childName };
6339}
6340function changeChildRemoved(childName, snapshotNode) {
6341 return { type: "child_removed" /* CHILD_REMOVED */, snapshotNode: snapshotNode, childName: childName };
6342}
6343function changeChildChanged(childName, snapshotNode, oldSnap) {
6344 return {
6345 type: "child_changed" /* CHILD_CHANGED */,
6346 snapshotNode: snapshotNode,
6347 childName: childName,
6348 oldSnap: oldSnap
6349 };
6350}
6351function changeChildMoved(childName, snapshotNode) {
6352 return { type: "child_moved" /* CHILD_MOVED */, snapshotNode: snapshotNode, childName: childName };
6353}
6354
6355/**
6356 * @license
6357 * Copyright 2017 Google LLC
6358 *
6359 * Licensed under the Apache License, Version 2.0 (the "License");
6360 * you may not use this file except in compliance with the License.
6361 * You may obtain a copy of the License at
6362 *
6363 * http://www.apache.org/licenses/LICENSE-2.0
6364 *
6365 * Unless required by applicable law or agreed to in writing, software
6366 * distributed under the License is distributed on an "AS IS" BASIS,
6367 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6368 * See the License for the specific language governing permissions and
6369 * limitations under the License.
6370 */
6371/**
6372 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
6373 */
6374var IndexedFilter = /** @class */ (function () {
6375 function IndexedFilter(index_) {
6376 this.index_ = index_;
6377 }
6378 IndexedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6379 assert(snap.isIndexed(this.index_), 'A node must be indexed if only a child is updated');
6380 var oldChild = snap.getImmediateChild(key);
6381 // Check if anything actually changed.
6382 if (oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))) {
6383 // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
6384 // In this case, affectedPath will appear null in both the old and new snapshots. So we need
6385 // to avoid treating these cases as "nothing changed."
6386 if (oldChild.isEmpty() === newChild.isEmpty()) {
6387 // Nothing changed.
6388 // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
6389 //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
6390 return snap;
6391 }
6392 }
6393 if (optChangeAccumulator != null) {
6394 if (newChild.isEmpty()) {
6395 if (snap.hasChild(key)) {
6396 optChangeAccumulator.trackChildChange(changeChildRemoved(key, oldChild));
6397 }
6398 else {
6399 assert(snap.isLeafNode(), 'A child remove without an old child only makes sense on a leaf node');
6400 }
6401 }
6402 else if (oldChild.isEmpty()) {
6403 optChangeAccumulator.trackChildChange(changeChildAdded(key, newChild));
6404 }
6405 else {
6406 optChangeAccumulator.trackChildChange(changeChildChanged(key, newChild, oldChild));
6407 }
6408 }
6409 if (snap.isLeafNode() && newChild.isEmpty()) {
6410 return snap;
6411 }
6412 else {
6413 // Make sure the node is indexed
6414 return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
6415 }
6416 };
6417 IndexedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6418 if (optChangeAccumulator != null) {
6419 if (!oldSnap.isLeafNode()) {
6420 oldSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6421 if (!newSnap.hasChild(key)) {
6422 optChangeAccumulator.trackChildChange(changeChildRemoved(key, childNode));
6423 }
6424 });
6425 }
6426 if (!newSnap.isLeafNode()) {
6427 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6428 if (oldSnap.hasChild(key)) {
6429 var oldChild = oldSnap.getImmediateChild(key);
6430 if (!oldChild.equals(childNode)) {
6431 optChangeAccumulator.trackChildChange(changeChildChanged(key, childNode, oldChild));
6432 }
6433 }
6434 else {
6435 optChangeAccumulator.trackChildChange(changeChildAdded(key, childNode));
6436 }
6437 });
6438 }
6439 }
6440 return newSnap.withIndex(this.index_);
6441 };
6442 IndexedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6443 if (oldSnap.isEmpty()) {
6444 return ChildrenNode.EMPTY_NODE;
6445 }
6446 else {
6447 return oldSnap.updatePriority(newPriority);
6448 }
6449 };
6450 IndexedFilter.prototype.filtersNodes = function () {
6451 return false;
6452 };
6453 IndexedFilter.prototype.getIndexedFilter = function () {
6454 return this;
6455 };
6456 IndexedFilter.prototype.getIndex = function () {
6457 return this.index_;
6458 };
6459 return IndexedFilter;
6460}());
6461
6462/**
6463 * @license
6464 * Copyright 2017 Google LLC
6465 *
6466 * Licensed under the Apache License, Version 2.0 (the "License");
6467 * you may not use this file except in compliance with the License.
6468 * You may obtain a copy of the License at
6469 *
6470 * http://www.apache.org/licenses/LICENSE-2.0
6471 *
6472 * Unless required by applicable law or agreed to in writing, software
6473 * distributed under the License is distributed on an "AS IS" BASIS,
6474 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6475 * See the License for the specific language governing permissions and
6476 * limitations under the License.
6477 */
6478/**
6479 * Filters nodes by range and uses an IndexFilter to track any changes after filtering the node
6480 */
6481var RangedFilter = /** @class */ (function () {
6482 function RangedFilter(params) {
6483 this.indexedFilter_ = new IndexedFilter(params.getIndex());
6484 this.index_ = params.getIndex();
6485 this.startPost_ = RangedFilter.getStartPost_(params);
6486 this.endPost_ = RangedFilter.getEndPost_(params);
6487 }
6488 RangedFilter.prototype.getStartPost = function () {
6489 return this.startPost_;
6490 };
6491 RangedFilter.prototype.getEndPost = function () {
6492 return this.endPost_;
6493 };
6494 RangedFilter.prototype.matches = function (node) {
6495 return (this.index_.compare(this.getStartPost(), node) <= 0 &&
6496 this.index_.compare(node, this.getEndPost()) <= 0);
6497 };
6498 RangedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6499 if (!this.matches(new NamedNode(key, newChild))) {
6500 newChild = ChildrenNode.EMPTY_NODE;
6501 }
6502 return this.indexedFilter_.updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6503 };
6504 RangedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6505 if (newSnap.isLeafNode()) {
6506 // Make sure we have a children node with the correct index, not a leaf node;
6507 newSnap = ChildrenNode.EMPTY_NODE;
6508 }
6509 var filtered = newSnap.withIndex(this.index_);
6510 // Don't support priorities on queries
6511 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6512 var self = this;
6513 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6514 if (!self.matches(new NamedNode(key, childNode))) {
6515 filtered = filtered.updateImmediateChild(key, ChildrenNode.EMPTY_NODE);
6516 }
6517 });
6518 return this.indexedFilter_.updateFullNode(oldSnap, filtered, optChangeAccumulator);
6519 };
6520 RangedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6521 // Don't support priorities on queries
6522 return oldSnap;
6523 };
6524 RangedFilter.prototype.filtersNodes = function () {
6525 return true;
6526 };
6527 RangedFilter.prototype.getIndexedFilter = function () {
6528 return this.indexedFilter_;
6529 };
6530 RangedFilter.prototype.getIndex = function () {
6531 return this.index_;
6532 };
6533 RangedFilter.getStartPost_ = function (params) {
6534 if (params.hasStart()) {
6535 var startName = params.getIndexStartName();
6536 return params.getIndex().makePost(params.getIndexStartValue(), startName);
6537 }
6538 else {
6539 return params.getIndex().minPost();
6540 }
6541 };
6542 RangedFilter.getEndPost_ = function (params) {
6543 if (params.hasEnd()) {
6544 var endName = params.getIndexEndName();
6545 return params.getIndex().makePost(params.getIndexEndValue(), endName);
6546 }
6547 else {
6548 return params.getIndex().maxPost();
6549 }
6550 };
6551 return RangedFilter;
6552}());
6553
6554/**
6555 * @license
6556 * Copyright 2017 Google LLC
6557 *
6558 * Licensed under the Apache License, Version 2.0 (the "License");
6559 * you may not use this file except in compliance with the License.
6560 * You may obtain a copy of the License at
6561 *
6562 * http://www.apache.org/licenses/LICENSE-2.0
6563 *
6564 * Unless required by applicable law or agreed to in writing, software
6565 * distributed under the License is distributed on an "AS IS" BASIS,
6566 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6567 * See the License for the specific language governing permissions and
6568 * limitations under the License.
6569 */
6570/**
6571 * Applies a limit and a range to a node and uses RangedFilter to do the heavy lifting where possible
6572 */
6573var LimitedFilter = /** @class */ (function () {
6574 function LimitedFilter(params) {
6575 this.rangedFilter_ = new RangedFilter(params);
6576 this.index_ = params.getIndex();
6577 this.limit_ = params.getLimit();
6578 this.reverse_ = !params.isViewFromLeft();
6579 }
6580 LimitedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6581 if (!this.rangedFilter_.matches(new NamedNode(key, newChild))) {
6582 newChild = ChildrenNode.EMPTY_NODE;
6583 }
6584 if (snap.getImmediateChild(key).equals(newChild)) {
6585 // No change
6586 return snap;
6587 }
6588 else if (snap.numChildren() < this.limit_) {
6589 return this.rangedFilter_
6590 .getIndexedFilter()
6591 .updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6592 }
6593 else {
6594 return this.fullLimitUpdateChild_(snap, key, newChild, source, optChangeAccumulator);
6595 }
6596 };
6597 LimitedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6598 var filtered;
6599 if (newSnap.isLeafNode() || newSnap.isEmpty()) {
6600 // Make sure we have a children node with the correct index, not a leaf node;
6601 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6602 }
6603 else {
6604 if (this.limit_ * 2 < newSnap.numChildren() &&
6605 newSnap.isIndexed(this.index_)) {
6606 // Easier to build up a snapshot, since what we're given has more than twice the elements we want
6607 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6608 // anchor to the startPost, endPost, or last element as appropriate
6609 var iterator = void 0;
6610 if (this.reverse_) {
6611 iterator = newSnap.getReverseIteratorFrom(this.rangedFilter_.getEndPost(), this.index_);
6612 }
6613 else {
6614 iterator = newSnap.getIteratorFrom(this.rangedFilter_.getStartPost(), this.index_);
6615 }
6616 var count = 0;
6617 while (iterator.hasNext() && count < this.limit_) {
6618 var next = iterator.getNext();
6619 var inRange = void 0;
6620 if (this.reverse_) {
6621 inRange =
6622 this.index_.compare(this.rangedFilter_.getStartPost(), next) <= 0;
6623 }
6624 else {
6625 inRange =
6626 this.index_.compare(next, this.rangedFilter_.getEndPost()) <= 0;
6627 }
6628 if (inRange) {
6629 filtered = filtered.updateImmediateChild(next.name, next.node);
6630 count++;
6631 }
6632 else {
6633 // if we have reached the end post, we cannot keep adding elemments
6634 break;
6635 }
6636 }
6637 }
6638 else {
6639 // The snap contains less than twice the limit. Faster to delete from the snap than build up a new one
6640 filtered = newSnap.withIndex(this.index_);
6641 // Don't support priorities on queries
6642 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6643 var startPost = void 0;
6644 var endPost = void 0;
6645 var cmp = void 0;
6646 var iterator = void 0;
6647 if (this.reverse_) {
6648 iterator = filtered.getReverseIterator(this.index_);
6649 startPost = this.rangedFilter_.getEndPost();
6650 endPost = this.rangedFilter_.getStartPost();
6651 var indexCompare_1 = this.index_.getCompare();
6652 cmp = function (a, b) { return indexCompare_1(b, a); };
6653 }
6654 else {
6655 iterator = filtered.getIterator(this.index_);
6656 startPost = this.rangedFilter_.getStartPost();
6657 endPost = this.rangedFilter_.getEndPost();
6658 cmp = this.index_.getCompare();
6659 }
6660 var count = 0;
6661 var foundStartPost = false;
6662 while (iterator.hasNext()) {
6663 var next = iterator.getNext();
6664 if (!foundStartPost && cmp(startPost, next) <= 0) {
6665 // start adding
6666 foundStartPost = true;
6667 }
6668 var inRange = foundStartPost && count < this.limit_ && cmp(next, endPost) <= 0;
6669 if (inRange) {
6670 count++;
6671 }
6672 else {
6673 filtered = filtered.updateImmediateChild(next.name, ChildrenNode.EMPTY_NODE);
6674 }
6675 }
6676 }
6677 }
6678 return this.rangedFilter_
6679 .getIndexedFilter()
6680 .updateFullNode(oldSnap, filtered, optChangeAccumulator);
6681 };
6682 LimitedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6683 // Don't support priorities on queries
6684 return oldSnap;
6685 };
6686 LimitedFilter.prototype.filtersNodes = function () {
6687 return true;
6688 };
6689 LimitedFilter.prototype.getIndexedFilter = function () {
6690 return this.rangedFilter_.getIndexedFilter();
6691 };
6692 LimitedFilter.prototype.getIndex = function () {
6693 return this.index_;
6694 };
6695 LimitedFilter.prototype.fullLimitUpdateChild_ = function (snap, childKey, childSnap, source, changeAccumulator) {
6696 // TODO: rename all cache stuff etc to general snap terminology
6697 var cmp;
6698 if (this.reverse_) {
6699 var indexCmp_1 = this.index_.getCompare();
6700 cmp = function (a, b) { return indexCmp_1(b, a); };
6701 }
6702 else {
6703 cmp = this.index_.getCompare();
6704 }
6705 var oldEventCache = snap;
6706 assert(oldEventCache.numChildren() === this.limit_, '');
6707 var newChildNamedNode = new NamedNode(childKey, childSnap);
6708 var windowBoundary = this.reverse_
6709 ? oldEventCache.getFirstChild(this.index_)
6710 : oldEventCache.getLastChild(this.index_);
6711 var inRange = this.rangedFilter_.matches(newChildNamedNode);
6712 if (oldEventCache.hasChild(childKey)) {
6713 var oldChildSnap = oldEventCache.getImmediateChild(childKey);
6714 var nextChild = source.getChildAfterChild(this.index_, windowBoundary, this.reverse_);
6715 while (nextChild != null &&
6716 (nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))) {
6717 // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
6718 // been applied to the limited filter yet. Ignore this next child which will be updated later in
6719 // the limited filter...
6720 nextChild = source.getChildAfterChild(this.index_, nextChild, this.reverse_);
6721 }
6722 var compareNext = nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
6723 var remainsInWindow = inRange && !childSnap.isEmpty() && compareNext >= 0;
6724 if (remainsInWindow) {
6725 if (changeAccumulator != null) {
6726 changeAccumulator.trackChildChange(changeChildChanged(childKey, childSnap, oldChildSnap));
6727 }
6728 return oldEventCache.updateImmediateChild(childKey, childSnap);
6729 }
6730 else {
6731 if (changeAccumulator != null) {
6732 changeAccumulator.trackChildChange(changeChildRemoved(childKey, oldChildSnap));
6733 }
6734 var newEventCache = oldEventCache.updateImmediateChild(childKey, ChildrenNode.EMPTY_NODE);
6735 var nextChildInRange = nextChild != null && this.rangedFilter_.matches(nextChild);
6736 if (nextChildInRange) {
6737 if (changeAccumulator != null) {
6738 changeAccumulator.trackChildChange(changeChildAdded(nextChild.name, nextChild.node));
6739 }
6740 return newEventCache.updateImmediateChild(nextChild.name, nextChild.node);
6741 }
6742 else {
6743 return newEventCache;
6744 }
6745 }
6746 }
6747 else if (childSnap.isEmpty()) {
6748 // we're deleting a node, but it was not in the window, so ignore it
6749 return snap;
6750 }
6751 else if (inRange) {
6752 if (cmp(windowBoundary, newChildNamedNode) >= 0) {
6753 if (changeAccumulator != null) {
6754 changeAccumulator.trackChildChange(changeChildRemoved(windowBoundary.name, windowBoundary.node));
6755 changeAccumulator.trackChildChange(changeChildAdded(childKey, childSnap));
6756 }
6757 return oldEventCache
6758 .updateImmediateChild(childKey, childSnap)
6759 .updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
6760 }
6761 else {
6762 return snap;
6763 }
6764 }
6765 else {
6766 return snap;
6767 }
6768 };
6769 return LimitedFilter;
6770}());
6771
6772/**
6773 * @license
6774 * Copyright 2017 Google LLC
6775 *
6776 * Licensed under the Apache License, Version 2.0 (the "License");
6777 * you may not use this file except in compliance with the License.
6778 * You may obtain a copy of the License at
6779 *
6780 * http://www.apache.org/licenses/LICENSE-2.0
6781 *
6782 * Unless required by applicable law or agreed to in writing, software
6783 * distributed under the License is distributed on an "AS IS" BASIS,
6784 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6785 * See the License for the specific language governing permissions and
6786 * limitations under the License.
6787 */
6788/**
6789 * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a
6790 * range to be returned for a particular location. It is assumed that validation of parameters is done at the
6791 * user-facing API level, so it is not done here.
6792 *
6793 * @internal
6794 */
6795var QueryParams = /** @class */ (function () {
6796 function QueryParams() {
6797 this.limitSet_ = false;
6798 this.startSet_ = false;
6799 this.startNameSet_ = false;
6800 this.startAfterSet_ = false;
6801 this.endSet_ = false;
6802 this.endNameSet_ = false;
6803 this.endBeforeSet_ = false;
6804 this.limit_ = 0;
6805 this.viewFrom_ = '';
6806 this.indexStartValue_ = null;
6807 this.indexStartName_ = '';
6808 this.indexEndValue_ = null;
6809 this.indexEndName_ = '';
6810 this.index_ = PRIORITY_INDEX;
6811 }
6812 QueryParams.prototype.hasStart = function () {
6813 return this.startSet_;
6814 };
6815 QueryParams.prototype.hasStartAfter = function () {
6816 return this.startAfterSet_;
6817 };
6818 QueryParams.prototype.hasEndBefore = function () {
6819 return this.endBeforeSet_;
6820 };
6821 /**
6822 * @returns True if it would return from left.
6823 */
6824 QueryParams.prototype.isViewFromLeft = function () {
6825 if (this.viewFrom_ === '') {
6826 // limit(), rather than limitToFirst or limitToLast was called.
6827 // This means that only one of startSet_ and endSet_ is true. Use them
6828 // to calculate which side of the view to anchor to. If neither is set,
6829 // anchor to the end.
6830 return this.startSet_;
6831 }
6832 else {
6833 return this.viewFrom_ === "l" /* VIEW_FROM_LEFT */;
6834 }
6835 };
6836 /**
6837 * Only valid to call if hasStart() returns true
6838 */
6839 QueryParams.prototype.getIndexStartValue = function () {
6840 assert(this.startSet_, 'Only valid if start has been set');
6841 return this.indexStartValue_;
6842 };
6843 /**
6844 * Only valid to call if hasStart() returns true.
6845 * Returns the starting key name for the range defined by these query parameters
6846 */
6847 QueryParams.prototype.getIndexStartName = function () {
6848 assert(this.startSet_, 'Only valid if start has been set');
6849 if (this.startNameSet_) {
6850 return this.indexStartName_;
6851 }
6852 else {
6853 return MIN_NAME;
6854 }
6855 };
6856 QueryParams.prototype.hasEnd = function () {
6857 return this.endSet_;
6858 };
6859 /**
6860 * Only valid to call if hasEnd() returns true.
6861 */
6862 QueryParams.prototype.getIndexEndValue = function () {
6863 assert(this.endSet_, 'Only valid if end has been set');
6864 return this.indexEndValue_;
6865 };
6866 /**
6867 * Only valid to call if hasEnd() returns true.
6868 * Returns the end key name for the range defined by these query parameters
6869 */
6870 QueryParams.prototype.getIndexEndName = function () {
6871 assert(this.endSet_, 'Only valid if end has been set');
6872 if (this.endNameSet_) {
6873 return this.indexEndName_;
6874 }
6875 else {
6876 return MAX_NAME;
6877 }
6878 };
6879 QueryParams.prototype.hasLimit = function () {
6880 return this.limitSet_;
6881 };
6882 /**
6883 * @returns True if a limit has been set and it has been explicitly anchored
6884 */
6885 QueryParams.prototype.hasAnchoredLimit = function () {
6886 return this.limitSet_ && this.viewFrom_ !== '';
6887 };
6888 /**
6889 * Only valid to call if hasLimit() returns true
6890 */
6891 QueryParams.prototype.getLimit = function () {
6892 assert(this.limitSet_, 'Only valid if limit has been set');
6893 return this.limit_;
6894 };
6895 QueryParams.prototype.getIndex = function () {
6896 return this.index_;
6897 };
6898 QueryParams.prototype.loadsAllData = function () {
6899 return !(this.startSet_ || this.endSet_ || this.limitSet_);
6900 };
6901 QueryParams.prototype.isDefault = function () {
6902 return this.loadsAllData() && this.index_ === PRIORITY_INDEX;
6903 };
6904 QueryParams.prototype.copy = function () {
6905 var copy = new QueryParams();
6906 copy.limitSet_ = this.limitSet_;
6907 copy.limit_ = this.limit_;
6908 copy.startSet_ = this.startSet_;
6909 copy.indexStartValue_ = this.indexStartValue_;
6910 copy.startNameSet_ = this.startNameSet_;
6911 copy.indexStartName_ = this.indexStartName_;
6912 copy.endSet_ = this.endSet_;
6913 copy.indexEndValue_ = this.indexEndValue_;
6914 copy.endNameSet_ = this.endNameSet_;
6915 copy.indexEndName_ = this.indexEndName_;
6916 copy.index_ = this.index_;
6917 copy.viewFrom_ = this.viewFrom_;
6918 return copy;
6919 };
6920 return QueryParams;
6921}());
6922function queryParamsGetNodeFilter(queryParams) {
6923 if (queryParams.loadsAllData()) {
6924 return new IndexedFilter(queryParams.getIndex());
6925 }
6926 else if (queryParams.hasLimit()) {
6927 return new LimitedFilter(queryParams);
6928 }
6929 else {
6930 return new RangedFilter(queryParams);
6931 }
6932}
6933function queryParamsLimitToFirst(queryParams, newLimit) {
6934 var newParams = queryParams.copy();
6935 newParams.limitSet_ = true;
6936 newParams.limit_ = newLimit;
6937 newParams.viewFrom_ = "l" /* VIEW_FROM_LEFT */;
6938 return newParams;
6939}
6940function queryParamsLimitToLast(queryParams, newLimit) {
6941 var newParams = queryParams.copy();
6942 newParams.limitSet_ = true;
6943 newParams.limit_ = newLimit;
6944 newParams.viewFrom_ = "r" /* VIEW_FROM_RIGHT */;
6945 return newParams;
6946}
6947function queryParamsStartAt(queryParams, indexValue, key) {
6948 var newParams = queryParams.copy();
6949 newParams.startSet_ = true;
6950 if (indexValue === undefined) {
6951 indexValue = null;
6952 }
6953 newParams.indexStartValue_ = indexValue;
6954 if (key != null) {
6955 newParams.startNameSet_ = true;
6956 newParams.indexStartName_ = key;
6957 }
6958 else {
6959 newParams.startNameSet_ = false;
6960 newParams.indexStartName_ = '';
6961 }
6962 return newParams;
6963}
6964function queryParamsStartAfter(queryParams, indexValue, key) {
6965 var params;
6966 if (queryParams.index_ === KEY_INDEX) {
6967 if (typeof indexValue === 'string') {
6968 indexValue = successor(indexValue);
6969 }
6970 params = queryParamsStartAt(queryParams, indexValue, key);
6971 }
6972 else {
6973 var childKey = void 0;
6974 if (key == null) {
6975 childKey = MAX_NAME;
6976 }
6977 else {
6978 childKey = successor(key);
6979 }
6980 params = queryParamsStartAt(queryParams, indexValue, childKey);
6981 }
6982 params.startAfterSet_ = true;
6983 return params;
6984}
6985function queryParamsEndAt(queryParams, indexValue, key) {
6986 var newParams = queryParams.copy();
6987 newParams.endSet_ = true;
6988 if (indexValue === undefined) {
6989 indexValue = null;
6990 }
6991 newParams.indexEndValue_ = indexValue;
6992 if (key !== undefined) {
6993 newParams.endNameSet_ = true;
6994 newParams.indexEndName_ = key;
6995 }
6996 else {
6997 newParams.endNameSet_ = false;
6998 newParams.indexEndName_ = '';
6999 }
7000 return newParams;
7001}
7002function queryParamsEndBefore(queryParams, indexValue, key) {
7003 var childKey;
7004 var params;
7005 if (queryParams.index_ === KEY_INDEX) {
7006 if (typeof indexValue === 'string') {
7007 indexValue = predecessor(indexValue);
7008 }
7009 params = queryParamsEndAt(queryParams, indexValue, key);
7010 }
7011 else {
7012 if (key == null) {
7013 childKey = MIN_NAME;
7014 }
7015 else {
7016 childKey = predecessor(key);
7017 }
7018 params = queryParamsEndAt(queryParams, indexValue, childKey);
7019 }
7020 params.endBeforeSet_ = true;
7021 return params;
7022}
7023function queryParamsOrderBy(queryParams, index) {
7024 var newParams = queryParams.copy();
7025 newParams.index_ = index;
7026 return newParams;
7027}
7028/**
7029 * Returns a set of REST query string parameters representing this query.
7030 *
7031 * @returns query string parameters
7032 */
7033function queryParamsToRestQueryStringParameters(queryParams) {
7034 var qs = {};
7035 if (queryParams.isDefault()) {
7036 return qs;
7037 }
7038 var orderBy;
7039 if (queryParams.index_ === PRIORITY_INDEX) {
7040 orderBy = "$priority" /* PRIORITY_INDEX */;
7041 }
7042 else if (queryParams.index_ === VALUE_INDEX) {
7043 orderBy = "$value" /* VALUE_INDEX */;
7044 }
7045 else if (queryParams.index_ === KEY_INDEX) {
7046 orderBy = "$key" /* KEY_INDEX */;
7047 }
7048 else {
7049 assert(queryParams.index_ instanceof PathIndex, 'Unrecognized index type!');
7050 orderBy = queryParams.index_.toString();
7051 }
7052 qs["orderBy" /* ORDER_BY */] = stringify(orderBy);
7053 if (queryParams.startSet_) {
7054 qs["startAt" /* START_AT */] = stringify(queryParams.indexStartValue_);
7055 if (queryParams.startNameSet_) {
7056 qs["startAt" /* START_AT */] +=
7057 ',' + stringify(queryParams.indexStartName_);
7058 }
7059 }
7060 if (queryParams.endSet_) {
7061 qs["endAt" /* END_AT */] = stringify(queryParams.indexEndValue_);
7062 if (queryParams.endNameSet_) {
7063 qs["endAt" /* END_AT */] +=
7064 ',' + stringify(queryParams.indexEndName_);
7065 }
7066 }
7067 if (queryParams.limitSet_) {
7068 if (queryParams.isViewFromLeft()) {
7069 qs["limitToFirst" /* LIMIT_TO_FIRST */] = queryParams.limit_;
7070 }
7071 else {
7072 qs["limitToLast" /* LIMIT_TO_LAST */] = queryParams.limit_;
7073 }
7074 }
7075 return qs;
7076}
7077function queryParamsGetQueryObject(queryParams) {
7078 var obj = {};
7079 if (queryParams.startSet_) {
7080 obj["sp" /* INDEX_START_VALUE */] =
7081 queryParams.indexStartValue_;
7082 if (queryParams.startNameSet_) {
7083 obj["sn" /* INDEX_START_NAME */] =
7084 queryParams.indexStartName_;
7085 }
7086 }
7087 if (queryParams.endSet_) {
7088 obj["ep" /* INDEX_END_VALUE */] = queryParams.indexEndValue_;
7089 if (queryParams.endNameSet_) {
7090 obj["en" /* INDEX_END_NAME */] = queryParams.indexEndName_;
7091 }
7092 }
7093 if (queryParams.limitSet_) {
7094 obj["l" /* LIMIT */] = queryParams.limit_;
7095 var viewFrom = queryParams.viewFrom_;
7096 if (viewFrom === '') {
7097 if (queryParams.isViewFromLeft()) {
7098 viewFrom = "l" /* VIEW_FROM_LEFT */;
7099 }
7100 else {
7101 viewFrom = "r" /* VIEW_FROM_RIGHT */;
7102 }
7103 }
7104 obj["vf" /* VIEW_FROM */] = viewFrom;
7105 }
7106 // For now, priority index is the default, so we only specify if it's some other index
7107 if (queryParams.index_ !== PRIORITY_INDEX) {
7108 obj["i" /* INDEX */] = queryParams.index_.toString();
7109 }
7110 return obj;
7111}
7112
7113/**
7114 * @license
7115 * Copyright 2017 Google LLC
7116 *
7117 * Licensed under the Apache License, Version 2.0 (the "License");
7118 * you may not use this file except in compliance with the License.
7119 * You may obtain a copy of the License at
7120 *
7121 * http://www.apache.org/licenses/LICENSE-2.0
7122 *
7123 * Unless required by applicable law or agreed to in writing, software
7124 * distributed under the License is distributed on an "AS IS" BASIS,
7125 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7126 * See the License for the specific language governing permissions and
7127 * limitations under the License.
7128 */
7129/**
7130 * An implementation of ServerActions that communicates with the server via REST requests.
7131 * This is mostly useful for compatibility with crawlers, where we don't want to spin up a full
7132 * persistent connection (using WebSockets or long-polling)
7133 */
7134var ReadonlyRestClient = /** @class */ (function (_super) {
7135 __extends(ReadonlyRestClient, _super);
7136 /**
7137 * @param repoInfo_ - Data about the namespace we are connecting to
7138 * @param onDataUpdate_ - A callback for new data from the server
7139 */
7140 function ReadonlyRestClient(repoInfo_, onDataUpdate_, authTokenProvider_, appCheckTokenProvider_) {
7141 var _this = _super.call(this) || this;
7142 _this.repoInfo_ = repoInfo_;
7143 _this.onDataUpdate_ = onDataUpdate_;
7144 _this.authTokenProvider_ = authTokenProvider_;
7145 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
7146 /** @private {function(...[*])} */
7147 _this.log_ = logWrapper('p:rest:');
7148 /**
7149 * We don't actually need to track listens, except to prevent us calling an onComplete for a listen
7150 * that's been removed. :-/
7151 */
7152 _this.listens_ = {};
7153 return _this;
7154 }
7155 ReadonlyRestClient.prototype.reportStats = function (stats) {
7156 throw new Error('Method not implemented.');
7157 };
7158 ReadonlyRestClient.getListenId_ = function (query, tag) {
7159 if (tag !== undefined) {
7160 return 'tag$' + tag;
7161 }
7162 else {
7163 assert(query._queryParams.isDefault(), "should have a tag if it's not a default query.");
7164 return query._path.toString();
7165 }
7166 };
7167 /** @inheritDoc */
7168 ReadonlyRestClient.prototype.listen = function (query, currentHashFn, tag, onComplete) {
7169 var _this = this;
7170 var pathString = query._path.toString();
7171 this.log_('Listen called for ' + pathString + ' ' + query._queryIdentifier);
7172 // Mark this listener so we can tell if it's removed.
7173 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7174 var thisListen = {};
7175 this.listens_[listenId] = thisListen;
7176 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7177 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7178 var data = result;
7179 if (error === 404) {
7180 data = null;
7181 error = null;
7182 }
7183 if (error === null) {
7184 _this.onDataUpdate_(pathString, data, /*isMerge=*/ false, tag);
7185 }
7186 if (safeGet(_this.listens_, listenId) === thisListen) {
7187 var status_1;
7188 if (!error) {
7189 status_1 = 'ok';
7190 }
7191 else if (error === 401) {
7192 status_1 = 'permission_denied';
7193 }
7194 else {
7195 status_1 = 'rest_error:' + error;
7196 }
7197 onComplete(status_1, null);
7198 }
7199 });
7200 };
7201 /** @inheritDoc */
7202 ReadonlyRestClient.prototype.unlisten = function (query, tag) {
7203 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7204 delete this.listens_[listenId];
7205 };
7206 ReadonlyRestClient.prototype.get = function (query) {
7207 var _this = this;
7208 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7209 var pathString = query._path.toString();
7210 var deferred = new Deferred();
7211 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7212 var data = result;
7213 if (error === 404) {
7214 data = null;
7215 error = null;
7216 }
7217 if (error === null) {
7218 _this.onDataUpdate_(pathString, data,
7219 /*isMerge=*/ false,
7220 /*tag=*/ null);
7221 deferred.resolve(data);
7222 }
7223 else {
7224 deferred.reject(new Error(data));
7225 }
7226 });
7227 return deferred.promise;
7228 };
7229 /** @inheritDoc */
7230 ReadonlyRestClient.prototype.refreshAuthToken = function (token) {
7231 // no-op since we just always call getToken.
7232 };
7233 /**
7234 * Performs a REST request to the given path, with the provided query string parameters,
7235 * and any auth credentials we have.
7236 */
7237 ReadonlyRestClient.prototype.restRequest_ = function (pathString, queryStringParameters, callback) {
7238 var _this = this;
7239 if (queryStringParameters === void 0) { queryStringParameters = {}; }
7240 queryStringParameters['format'] = 'export';
7241 return Promise.all([
7242 this.authTokenProvider_.getToken(/*forceRefresh=*/ false),
7243 this.appCheckTokenProvider_.getToken(/*forceRefresh=*/ false)
7244 ]).then(function (_a) {
7245 var _b = __read(_a, 2), authToken = _b[0], appCheckToken = _b[1];
7246 if (authToken && authToken.accessToken) {
7247 queryStringParameters['auth'] = authToken.accessToken;
7248 }
7249 if (appCheckToken && appCheckToken.token) {
7250 queryStringParameters['ac'] = appCheckToken.token;
7251 }
7252 var url = (_this.repoInfo_.secure ? 'https://' : 'http://') +
7253 _this.repoInfo_.host +
7254 pathString +
7255 '?' +
7256 'ns=' +
7257 _this.repoInfo_.namespace +
7258 querystring(queryStringParameters);
7259 _this.log_('Sending REST request for ' + url);
7260 var xhr = new XMLHttpRequest();
7261 xhr.onreadystatechange = function () {
7262 if (callback && xhr.readyState === 4) {
7263 _this.log_('REST Response for ' + url + ' received. status:', xhr.status, 'response:', xhr.responseText);
7264 var res = null;
7265 if (xhr.status >= 200 && xhr.status < 300) {
7266 try {
7267 res = jsonEval(xhr.responseText);
7268 }
7269 catch (e) {
7270 warn('Failed to parse JSON response for ' +
7271 url +
7272 ': ' +
7273 xhr.responseText);
7274 }
7275 callback(null, res);
7276 }
7277 else {
7278 // 401 and 404 are expected.
7279 if (xhr.status !== 401 && xhr.status !== 404) {
7280 warn('Got unsuccessful REST response for ' +
7281 url +
7282 ' Status: ' +
7283 xhr.status);
7284 }
7285 callback(xhr.status);
7286 }
7287 callback = null;
7288 }
7289 };
7290 xhr.open('GET', url, /*asynchronous=*/ true);
7291 xhr.send();
7292 });
7293 };
7294 return ReadonlyRestClient;
7295}(ServerActions));
7296
7297/**
7298 * @license
7299 * Copyright 2017 Google LLC
7300 *
7301 * Licensed under the Apache License, Version 2.0 (the "License");
7302 * you may not use this file except in compliance with the License.
7303 * You may obtain a copy of the License at
7304 *
7305 * http://www.apache.org/licenses/LICENSE-2.0
7306 *
7307 * Unless required by applicable law or agreed to in writing, software
7308 * distributed under the License is distributed on an "AS IS" BASIS,
7309 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7310 * See the License for the specific language governing permissions and
7311 * limitations under the License.
7312 */
7313/**
7314 * Mutable object which basically just stores a reference to the "latest" immutable snapshot.
7315 */
7316var SnapshotHolder = /** @class */ (function () {
7317 function SnapshotHolder() {
7318 this.rootNode_ = ChildrenNode.EMPTY_NODE;
7319 }
7320 SnapshotHolder.prototype.getNode = function (path) {
7321 return this.rootNode_.getChild(path);
7322 };
7323 SnapshotHolder.prototype.updateSnapshot = function (path, newSnapshotNode) {
7324 this.rootNode_ = this.rootNode_.updateChild(path, newSnapshotNode);
7325 };
7326 return SnapshotHolder;
7327}());
7328
7329/**
7330 * @license
7331 * Copyright 2017 Google LLC
7332 *
7333 * Licensed under the Apache License, Version 2.0 (the "License");
7334 * you may not use this file except in compliance with the License.
7335 * You may obtain a copy of the License at
7336 *
7337 * http://www.apache.org/licenses/LICENSE-2.0
7338 *
7339 * Unless required by applicable law or agreed to in writing, software
7340 * distributed under the License is distributed on an "AS IS" BASIS,
7341 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7342 * See the License for the specific language governing permissions and
7343 * limitations under the License.
7344 */
7345function newSparseSnapshotTree() {
7346 return {
7347 value: null,
7348 children: new Map()
7349 };
7350}
7351/**
7352 * Stores the given node at the specified path. If there is already a node
7353 * at a shallower path, it merges the new data into that snapshot node.
7354 *
7355 * @param path - Path to look up snapshot for.
7356 * @param data - The new data, or null.
7357 */
7358function sparseSnapshotTreeRemember(sparseSnapshotTree, path, data) {
7359 if (pathIsEmpty(path)) {
7360 sparseSnapshotTree.value = data;
7361 sparseSnapshotTree.children.clear();
7362 }
7363 else if (sparseSnapshotTree.value !== null) {
7364 sparseSnapshotTree.value = sparseSnapshotTree.value.updateChild(path, data);
7365 }
7366 else {
7367 var childKey = pathGetFront(path);
7368 if (!sparseSnapshotTree.children.has(childKey)) {
7369 sparseSnapshotTree.children.set(childKey, newSparseSnapshotTree());
7370 }
7371 var child = sparseSnapshotTree.children.get(childKey);
7372 path = pathPopFront(path);
7373 sparseSnapshotTreeRemember(child, path, data);
7374 }
7375}
7376/**
7377 * Purge the data at path from the cache.
7378 *
7379 * @param path - Path to look up snapshot for.
7380 * @returns True if this node should now be removed.
7381 */
7382function sparseSnapshotTreeForget(sparseSnapshotTree, path) {
7383 if (pathIsEmpty(path)) {
7384 sparseSnapshotTree.value = null;
7385 sparseSnapshotTree.children.clear();
7386 return true;
7387 }
7388 else {
7389 if (sparseSnapshotTree.value !== null) {
7390 if (sparseSnapshotTree.value.isLeafNode()) {
7391 // We're trying to forget a node that doesn't exist
7392 return false;
7393 }
7394 else {
7395 var value = sparseSnapshotTree.value;
7396 sparseSnapshotTree.value = null;
7397 value.forEachChild(PRIORITY_INDEX, function (key, tree) {
7398 sparseSnapshotTreeRemember(sparseSnapshotTree, new Path(key), tree);
7399 });
7400 return sparseSnapshotTreeForget(sparseSnapshotTree, path);
7401 }
7402 }
7403 else if (sparseSnapshotTree.children.size > 0) {
7404 var childKey = pathGetFront(path);
7405 path = pathPopFront(path);
7406 if (sparseSnapshotTree.children.has(childKey)) {
7407 var safeToRemove = sparseSnapshotTreeForget(sparseSnapshotTree.children.get(childKey), path);
7408 if (safeToRemove) {
7409 sparseSnapshotTree.children.delete(childKey);
7410 }
7411 }
7412 return sparseSnapshotTree.children.size === 0;
7413 }
7414 else {
7415 return true;
7416 }
7417 }
7418}
7419/**
7420 * Recursively iterates through all of the stored tree and calls the
7421 * callback on each one.
7422 *
7423 * @param prefixPath - Path to look up node for.
7424 * @param func - The function to invoke for each tree.
7425 */
7426function sparseSnapshotTreeForEachTree(sparseSnapshotTree, prefixPath, func) {
7427 if (sparseSnapshotTree.value !== null) {
7428 func(prefixPath, sparseSnapshotTree.value);
7429 }
7430 else {
7431 sparseSnapshotTreeForEachChild(sparseSnapshotTree, function (key, tree) {
7432 var path = new Path(prefixPath.toString() + '/' + key);
7433 sparseSnapshotTreeForEachTree(tree, path, func);
7434 });
7435 }
7436}
7437/**
7438 * Iterates through each immediate child and triggers the callback.
7439 * Only seems to be used in tests.
7440 *
7441 * @param func - The function to invoke for each child.
7442 */
7443function sparseSnapshotTreeForEachChild(sparseSnapshotTree, func) {
7444 sparseSnapshotTree.children.forEach(function (tree, key) {
7445 func(key, tree);
7446 });
7447}
7448
7449/**
7450 * @license
7451 * Copyright 2017 Google LLC
7452 *
7453 * Licensed under the Apache License, Version 2.0 (the "License");
7454 * you may not use this file except in compliance with the License.
7455 * You may obtain a copy of the License at
7456 *
7457 * http://www.apache.org/licenses/LICENSE-2.0
7458 *
7459 * Unless required by applicable law or agreed to in writing, software
7460 * distributed under the License is distributed on an "AS IS" BASIS,
7461 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7462 * See the License for the specific language governing permissions and
7463 * limitations under the License.
7464 */
7465/**
7466 * Returns the delta from the previous call to get stats.
7467 *
7468 * @param collection_ - The collection to "listen" to.
7469 */
7470var StatsListener = /** @class */ (function () {
7471 function StatsListener(collection_) {
7472 this.collection_ = collection_;
7473 this.last_ = null;
7474 }
7475 StatsListener.prototype.get = function () {
7476 var newStats = this.collection_.get();
7477 var delta = __assign({}, newStats);
7478 if (this.last_) {
7479 each(this.last_, function (stat, value) {
7480 delta[stat] = delta[stat] - value;
7481 });
7482 }
7483 this.last_ = newStats;
7484 return delta;
7485 };
7486 return StatsListener;
7487}());
7488
7489/**
7490 * @license
7491 * Copyright 2017 Google LLC
7492 *
7493 * Licensed under the Apache License, Version 2.0 (the "License");
7494 * you may not use this file except in compliance with the License.
7495 * You may obtain a copy of the License at
7496 *
7497 * http://www.apache.org/licenses/LICENSE-2.0
7498 *
7499 * Unless required by applicable law or agreed to in writing, software
7500 * distributed under the License is distributed on an "AS IS" BASIS,
7501 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7502 * See the License for the specific language governing permissions and
7503 * limitations under the License.
7504 */
7505// Assuming some apps may have a short amount of time on page, and a bulk of firebase operations probably
7506// happen on page load, we try to report our first set of stats pretty quickly, but we wait at least 10
7507// seconds to try to ensure the Firebase connection is established / settled.
7508var FIRST_STATS_MIN_TIME = 10 * 1000;
7509var FIRST_STATS_MAX_TIME = 30 * 1000;
7510// We'll continue to report stats on average every 5 minutes.
7511var REPORT_STATS_INTERVAL = 5 * 60 * 1000;
7512var StatsReporter = /** @class */ (function () {
7513 function StatsReporter(collection, server_) {
7514 this.server_ = server_;
7515 this.statsToReport_ = {};
7516 this.statsListener_ = new StatsListener(collection);
7517 var timeout = FIRST_STATS_MIN_TIME +
7518 (FIRST_STATS_MAX_TIME - FIRST_STATS_MIN_TIME) * Math.random();
7519 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(timeout));
7520 }
7521 StatsReporter.prototype.reportStats_ = function () {
7522 var _this = this;
7523 var stats = this.statsListener_.get();
7524 var reportedStats = {};
7525 var haveStatsToReport = false;
7526 each(stats, function (stat, value) {
7527 if (value > 0 && contains(_this.statsToReport_, stat)) {
7528 reportedStats[stat] = value;
7529 haveStatsToReport = true;
7530 }
7531 });
7532 if (haveStatsToReport) {
7533 this.server_.reportStats(reportedStats);
7534 }
7535 // queue our next run.
7536 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(Math.random() * 2 * REPORT_STATS_INTERVAL));
7537 };
7538 return StatsReporter;
7539}());
7540
7541/**
7542 * @license
7543 * Copyright 2017 Google LLC
7544 *
7545 * Licensed under the Apache License, Version 2.0 (the "License");
7546 * you may not use this file except in compliance with the License.
7547 * You may obtain a copy of the License at
7548 *
7549 * http://www.apache.org/licenses/LICENSE-2.0
7550 *
7551 * Unless required by applicable law or agreed to in writing, software
7552 * distributed under the License is distributed on an "AS IS" BASIS,
7553 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7554 * See the License for the specific language governing permissions and
7555 * limitations under the License.
7556 */
7557/**
7558 *
7559 * @enum
7560 */
7561var OperationType;
7562(function (OperationType) {
7563 OperationType[OperationType["OVERWRITE"] = 0] = "OVERWRITE";
7564 OperationType[OperationType["MERGE"] = 1] = "MERGE";
7565 OperationType[OperationType["ACK_USER_WRITE"] = 2] = "ACK_USER_WRITE";
7566 OperationType[OperationType["LISTEN_COMPLETE"] = 3] = "LISTEN_COMPLETE";
7567})(OperationType || (OperationType = {}));
7568function newOperationSourceUser() {
7569 return {
7570 fromUser: true,
7571 fromServer: false,
7572 queryId: null,
7573 tagged: false
7574 };
7575}
7576function newOperationSourceServer() {
7577 return {
7578 fromUser: false,
7579 fromServer: true,
7580 queryId: null,
7581 tagged: false
7582 };
7583}
7584function newOperationSourceServerTaggedQuery(queryId) {
7585 return {
7586 fromUser: false,
7587 fromServer: true,
7588 queryId: queryId,
7589 tagged: true
7590 };
7591}
7592
7593/**
7594 * @license
7595 * Copyright 2017 Google LLC
7596 *
7597 * Licensed under the Apache License, Version 2.0 (the "License");
7598 * you may not use this file except in compliance with the License.
7599 * You may obtain a copy of the License at
7600 *
7601 * http://www.apache.org/licenses/LICENSE-2.0
7602 *
7603 * Unless required by applicable law or agreed to in writing, software
7604 * distributed under the License is distributed on an "AS IS" BASIS,
7605 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7606 * See the License for the specific language governing permissions and
7607 * limitations under the License.
7608 */
7609var AckUserWrite = /** @class */ (function () {
7610 /**
7611 * @param affectedTree - A tree containing true for each affected path. Affected paths can't overlap.
7612 */
7613 function AckUserWrite(
7614 /** @inheritDoc */ path,
7615 /** @inheritDoc */ affectedTree,
7616 /** @inheritDoc */ revert) {
7617 this.path = path;
7618 this.affectedTree = affectedTree;
7619 this.revert = revert;
7620 /** @inheritDoc */
7621 this.type = OperationType.ACK_USER_WRITE;
7622 /** @inheritDoc */
7623 this.source = newOperationSourceUser();
7624 }
7625 AckUserWrite.prototype.operationForChild = function (childName) {
7626 if (!pathIsEmpty(this.path)) {
7627 assert(pathGetFront(this.path) === childName, 'operationForChild called for unrelated child.');
7628 return new AckUserWrite(pathPopFront(this.path), this.affectedTree, this.revert);
7629 }
7630 else if (this.affectedTree.value != null) {
7631 assert(this.affectedTree.children.isEmpty(), 'affectedTree should not have overlapping affected paths.');
7632 // All child locations are affected as well; just return same operation.
7633 return this;
7634 }
7635 else {
7636 var childTree = this.affectedTree.subtree(new Path(childName));
7637 return new AckUserWrite(newEmptyPath(), childTree, this.revert);
7638 }
7639 };
7640 return AckUserWrite;
7641}());
7642
7643/**
7644 * @license
7645 * Copyright 2017 Google LLC
7646 *
7647 * Licensed under the Apache License, Version 2.0 (the "License");
7648 * you may not use this file except in compliance with the License.
7649 * You may obtain a copy of the License at
7650 *
7651 * http://www.apache.org/licenses/LICENSE-2.0
7652 *
7653 * Unless required by applicable law or agreed to in writing, software
7654 * distributed under the License is distributed on an "AS IS" BASIS,
7655 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7656 * See the License for the specific language governing permissions and
7657 * limitations under the License.
7658 */
7659var ListenComplete = /** @class */ (function () {
7660 function ListenComplete(source, path) {
7661 this.source = source;
7662 this.path = path;
7663 /** @inheritDoc */
7664 this.type = OperationType.LISTEN_COMPLETE;
7665 }
7666 ListenComplete.prototype.operationForChild = function (childName) {
7667 if (pathIsEmpty(this.path)) {
7668 return new ListenComplete(this.source, newEmptyPath());
7669 }
7670 else {
7671 return new ListenComplete(this.source, pathPopFront(this.path));
7672 }
7673 };
7674 return ListenComplete;
7675}());
7676
7677/**
7678 * @license
7679 * Copyright 2017 Google LLC
7680 *
7681 * Licensed under the Apache License, Version 2.0 (the "License");
7682 * you may not use this file except in compliance with the License.
7683 * You may obtain a copy of the License at
7684 *
7685 * http://www.apache.org/licenses/LICENSE-2.0
7686 *
7687 * Unless required by applicable law or agreed to in writing, software
7688 * distributed under the License is distributed on an "AS IS" BASIS,
7689 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7690 * See the License for the specific language governing permissions and
7691 * limitations under the License.
7692 */
7693var Overwrite = /** @class */ (function () {
7694 function Overwrite(source, path, snap) {
7695 this.source = source;
7696 this.path = path;
7697 this.snap = snap;
7698 /** @inheritDoc */
7699 this.type = OperationType.OVERWRITE;
7700 }
7701 Overwrite.prototype.operationForChild = function (childName) {
7702 if (pathIsEmpty(this.path)) {
7703 return new Overwrite(this.source, newEmptyPath(), this.snap.getImmediateChild(childName));
7704 }
7705 else {
7706 return new Overwrite(this.source, pathPopFront(this.path), this.snap);
7707 }
7708 };
7709 return Overwrite;
7710}());
7711
7712/**
7713 * @license
7714 * Copyright 2017 Google LLC
7715 *
7716 * Licensed under the Apache License, Version 2.0 (the "License");
7717 * you may not use this file except in compliance with the License.
7718 * You may obtain a copy of the License at
7719 *
7720 * http://www.apache.org/licenses/LICENSE-2.0
7721 *
7722 * Unless required by applicable law or agreed to in writing, software
7723 * distributed under the License is distributed on an "AS IS" BASIS,
7724 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7725 * See the License for the specific language governing permissions and
7726 * limitations under the License.
7727 */
7728var Merge = /** @class */ (function () {
7729 function Merge(
7730 /** @inheritDoc */ source,
7731 /** @inheritDoc */ path,
7732 /** @inheritDoc */ children) {
7733 this.source = source;
7734 this.path = path;
7735 this.children = children;
7736 /** @inheritDoc */
7737 this.type = OperationType.MERGE;
7738 }
7739 Merge.prototype.operationForChild = function (childName) {
7740 if (pathIsEmpty(this.path)) {
7741 var childTree = this.children.subtree(new Path(childName));
7742 if (childTree.isEmpty()) {
7743 // This child is unaffected
7744 return null;
7745 }
7746 else if (childTree.value) {
7747 // We have a snapshot for the child in question. This becomes an overwrite of the child.
7748 return new Overwrite(this.source, newEmptyPath(), childTree.value);
7749 }
7750 else {
7751 // This is a merge at a deeper level
7752 return new Merge(this.source, newEmptyPath(), childTree);
7753 }
7754 }
7755 else {
7756 assert(pathGetFront(this.path) === childName, "Can't get a merge for a child not on the path of the operation");
7757 return new Merge(this.source, pathPopFront(this.path), this.children);
7758 }
7759 };
7760 Merge.prototype.toString = function () {
7761 return ('Operation(' +
7762 this.path +
7763 ': ' +
7764 this.source.toString() +
7765 ' merge: ' +
7766 this.children.toString() +
7767 ')');
7768 };
7769 return Merge;
7770}());
7771
7772/**
7773 * @license
7774 * Copyright 2017 Google LLC
7775 *
7776 * Licensed under the Apache License, Version 2.0 (the "License");
7777 * you may not use this file except in compliance with the License.
7778 * You may obtain a copy of the License at
7779 *
7780 * http://www.apache.org/licenses/LICENSE-2.0
7781 *
7782 * Unless required by applicable law or agreed to in writing, software
7783 * distributed under the License is distributed on an "AS IS" BASIS,
7784 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7785 * See the License for the specific language governing permissions and
7786 * limitations under the License.
7787 */
7788/**
7789 * A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
7790 * initialized in the sense that we know at one point in time this represented a valid state of the world, e.g.
7791 * initialized with data from the server, or a complete overwrite by the client. The filtered flag also tracks
7792 * whether a node potentially had children removed due to a filter.
7793 */
7794var CacheNode = /** @class */ (function () {
7795 function CacheNode(node_, fullyInitialized_, filtered_) {
7796 this.node_ = node_;
7797 this.fullyInitialized_ = fullyInitialized_;
7798 this.filtered_ = filtered_;
7799 }
7800 /**
7801 * Returns whether this node was fully initialized with either server data or a complete overwrite by the client
7802 */
7803 CacheNode.prototype.isFullyInitialized = function () {
7804 return this.fullyInitialized_;
7805 };
7806 /**
7807 * Returns whether this node is potentially missing children due to a filter applied to the node
7808 */
7809 CacheNode.prototype.isFiltered = function () {
7810 return this.filtered_;
7811 };
7812 CacheNode.prototype.isCompleteForPath = function (path) {
7813 if (pathIsEmpty(path)) {
7814 return this.isFullyInitialized() && !this.filtered_;
7815 }
7816 var childKey = pathGetFront(path);
7817 return this.isCompleteForChild(childKey);
7818 };
7819 CacheNode.prototype.isCompleteForChild = function (key) {
7820 return ((this.isFullyInitialized() && !this.filtered_) || this.node_.hasChild(key));
7821 };
7822 CacheNode.prototype.getNode = function () {
7823 return this.node_;
7824 };
7825 return CacheNode;
7826}());
7827
7828/**
7829 * @license
7830 * Copyright 2017 Google LLC
7831 *
7832 * Licensed under the Apache License, Version 2.0 (the "License");
7833 * you may not use this file except in compliance with the License.
7834 * You may obtain a copy of the License at
7835 *
7836 * http://www.apache.org/licenses/LICENSE-2.0
7837 *
7838 * Unless required by applicable law or agreed to in writing, software
7839 * distributed under the License is distributed on an "AS IS" BASIS,
7840 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7841 * See the License for the specific language governing permissions and
7842 * limitations under the License.
7843 */
7844/**
7845 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
7846 * CacheDiffer into actual events (Event) that can be raised. See generateEventsForChanges()
7847 * for details.
7848 *
7849 */
7850var EventGenerator = /** @class */ (function () {
7851 function EventGenerator(query_) {
7852 this.query_ = query_;
7853 this.index_ = this.query_._queryParams.getIndex();
7854 }
7855 return EventGenerator;
7856}());
7857/**
7858 * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
7859 * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
7860 *
7861 * Notes:
7862 * - child_moved events will be synthesized at this time for any child_changed events that affect
7863 * our index.
7864 * - prevName will be calculated based on the index ordering.
7865 */
7866function eventGeneratorGenerateEventsForChanges(eventGenerator, changes, eventCache, eventRegistrations) {
7867 var events = [];
7868 var moves = [];
7869 changes.forEach(function (change) {
7870 if (change.type === "child_changed" /* CHILD_CHANGED */ &&
7871 eventGenerator.index_.indexedValueChanged(change.oldSnap, change.snapshotNode)) {
7872 moves.push(changeChildMoved(change.childName, change.snapshotNode));
7873 }
7874 });
7875 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_removed" /* CHILD_REMOVED */, changes, eventRegistrations, eventCache);
7876 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_added" /* CHILD_ADDED */, changes, eventRegistrations, eventCache);
7877 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_moved" /* CHILD_MOVED */, moves, eventRegistrations, eventCache);
7878 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_changed" /* CHILD_CHANGED */, changes, eventRegistrations, eventCache);
7879 eventGeneratorGenerateEventsForType(eventGenerator, events, "value" /* VALUE */, changes, eventRegistrations, eventCache);
7880 return events;
7881}
7882/**
7883 * Given changes of a single change type, generate the corresponding events.
7884 */
7885function eventGeneratorGenerateEventsForType(eventGenerator, events, eventType, changes, registrations, eventCache) {
7886 var filteredChanges = changes.filter(function (change) { return change.type === eventType; });
7887 filteredChanges.sort(function (a, b) {
7888 return eventGeneratorCompareChanges(eventGenerator, a, b);
7889 });
7890 filteredChanges.forEach(function (change) {
7891 var materializedChange = eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache);
7892 registrations.forEach(function (registration) {
7893 if (registration.respondsTo(change.type)) {
7894 events.push(registration.createEvent(materializedChange, eventGenerator.query_));
7895 }
7896 });
7897 });
7898}
7899function eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache) {
7900 if (change.type === 'value' || change.type === 'child_removed') {
7901 return change;
7902 }
7903 else {
7904 change.prevName = eventCache.getPredecessorChildName(change.childName, change.snapshotNode, eventGenerator.index_);
7905 return change;
7906 }
7907}
7908function eventGeneratorCompareChanges(eventGenerator, a, b) {
7909 if (a.childName == null || b.childName == null) {
7910 throw assertionError('Should only compare child_ events.');
7911 }
7912 var aWrapped = new NamedNode(a.childName, a.snapshotNode);
7913 var bWrapped = new NamedNode(b.childName, b.snapshotNode);
7914 return eventGenerator.index_.compare(aWrapped, bWrapped);
7915}
7916
7917/**
7918 * @license
7919 * Copyright 2017 Google LLC
7920 *
7921 * Licensed under the Apache License, Version 2.0 (the "License");
7922 * you may not use this file except in compliance with the License.
7923 * You may obtain a copy of the License at
7924 *
7925 * http://www.apache.org/licenses/LICENSE-2.0
7926 *
7927 * Unless required by applicable law or agreed to in writing, software
7928 * distributed under the License is distributed on an "AS IS" BASIS,
7929 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7930 * See the License for the specific language governing permissions and
7931 * limitations under the License.
7932 */
7933function newViewCache(eventCache, serverCache) {
7934 return { eventCache: eventCache, serverCache: serverCache };
7935}
7936function viewCacheUpdateEventSnap(viewCache, eventSnap, complete, filtered) {
7937 return newViewCache(new CacheNode(eventSnap, complete, filtered), viewCache.serverCache);
7938}
7939function viewCacheUpdateServerSnap(viewCache, serverSnap, complete, filtered) {
7940 return newViewCache(viewCache.eventCache, new CacheNode(serverSnap, complete, filtered));
7941}
7942function viewCacheGetCompleteEventSnap(viewCache) {
7943 return viewCache.eventCache.isFullyInitialized()
7944 ? viewCache.eventCache.getNode()
7945 : null;
7946}
7947function viewCacheGetCompleteServerSnap(viewCache) {
7948 return viewCache.serverCache.isFullyInitialized()
7949 ? viewCache.serverCache.getNode()
7950 : null;
7951}
7952
7953/**
7954 * @license
7955 * Copyright 2017 Google LLC
7956 *
7957 * Licensed under the Apache License, Version 2.0 (the "License");
7958 * you may not use this file except in compliance with the License.
7959 * You may obtain a copy of the License at
7960 *
7961 * http://www.apache.org/licenses/LICENSE-2.0
7962 *
7963 * Unless required by applicable law or agreed to in writing, software
7964 * distributed under the License is distributed on an "AS IS" BASIS,
7965 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7966 * See the License for the specific language governing permissions and
7967 * limitations under the License.
7968 */
7969var emptyChildrenSingleton;
7970/**
7971 * Singleton empty children collection.
7972 *
7973 */
7974var EmptyChildren = function () {
7975 if (!emptyChildrenSingleton) {
7976 emptyChildrenSingleton = new SortedMap(stringCompare);
7977 }
7978 return emptyChildrenSingleton;
7979};
7980/**
7981 * A tree with immutable elements.
7982 */
7983var ImmutableTree = /** @class */ (function () {
7984 function ImmutableTree(value, children) {
7985 if (children === void 0) { children = EmptyChildren(); }
7986 this.value = value;
7987 this.children = children;
7988 }
7989 ImmutableTree.fromObject = function (obj) {
7990 var tree = new ImmutableTree(null);
7991 each(obj, function (childPath, childSnap) {
7992 tree = tree.set(new Path(childPath), childSnap);
7993 });
7994 return tree;
7995 };
7996 /**
7997 * True if the value is empty and there are no children
7998 */
7999 ImmutableTree.prototype.isEmpty = function () {
8000 return this.value === null && this.children.isEmpty();
8001 };
8002 /**
8003 * Given a path and predicate, return the first node and the path to that node
8004 * where the predicate returns true.
8005 *
8006 * TODO Do a perf test -- If we're creating a bunch of `{path: value:}`
8007 * objects on the way back out, it may be better to pass down a pathSoFar obj.
8008 *
8009 * @param relativePath - The remainder of the path
8010 * @param predicate - The predicate to satisfy to return a node
8011 */
8012 ImmutableTree.prototype.findRootMostMatchingPathAndValue = function (relativePath, predicate) {
8013 if (this.value != null && predicate(this.value)) {
8014 return { path: newEmptyPath(), value: this.value };
8015 }
8016 else {
8017 if (pathIsEmpty(relativePath)) {
8018 return null;
8019 }
8020 else {
8021 var front = pathGetFront(relativePath);
8022 var child = this.children.get(front);
8023 if (child !== null) {
8024 var childExistingPathAndValue = child.findRootMostMatchingPathAndValue(pathPopFront(relativePath), predicate);
8025 if (childExistingPathAndValue != null) {
8026 var fullPath = pathChild(new Path(front), childExistingPathAndValue.path);
8027 return { path: fullPath, value: childExistingPathAndValue.value };
8028 }
8029 else {
8030 return null;
8031 }
8032 }
8033 else {
8034 return null;
8035 }
8036 }
8037 }
8038 };
8039 /**
8040 * Find, if it exists, the shortest subpath of the given path that points a defined
8041 * value in the tree
8042 */
8043 ImmutableTree.prototype.findRootMostValueAndPath = function (relativePath) {
8044 return this.findRootMostMatchingPathAndValue(relativePath, function () { return true; });
8045 };
8046 /**
8047 * @returns The subtree at the given path
8048 */
8049 ImmutableTree.prototype.subtree = function (relativePath) {
8050 if (pathIsEmpty(relativePath)) {
8051 return this;
8052 }
8053 else {
8054 var front = pathGetFront(relativePath);
8055 var childTree = this.children.get(front);
8056 if (childTree !== null) {
8057 return childTree.subtree(pathPopFront(relativePath));
8058 }
8059 else {
8060 return new ImmutableTree(null);
8061 }
8062 }
8063 };
8064 /**
8065 * Sets a value at the specified path.
8066 *
8067 * @param relativePath - Path to set value at.
8068 * @param toSet - Value to set.
8069 * @returns Resulting tree.
8070 */
8071 ImmutableTree.prototype.set = function (relativePath, toSet) {
8072 if (pathIsEmpty(relativePath)) {
8073 return new ImmutableTree(toSet, this.children);
8074 }
8075 else {
8076 var front = pathGetFront(relativePath);
8077 var child = this.children.get(front) || new ImmutableTree(null);
8078 var newChild = child.set(pathPopFront(relativePath), toSet);
8079 var newChildren = this.children.insert(front, newChild);
8080 return new ImmutableTree(this.value, newChildren);
8081 }
8082 };
8083 /**
8084 * Removes the value at the specified path.
8085 *
8086 * @param relativePath - Path to value to remove.
8087 * @returns Resulting tree.
8088 */
8089 ImmutableTree.prototype.remove = function (relativePath) {
8090 if (pathIsEmpty(relativePath)) {
8091 if (this.children.isEmpty()) {
8092 return new ImmutableTree(null);
8093 }
8094 else {
8095 return new ImmutableTree(null, this.children);
8096 }
8097 }
8098 else {
8099 var front = pathGetFront(relativePath);
8100 var child = this.children.get(front);
8101 if (child) {
8102 var newChild = child.remove(pathPopFront(relativePath));
8103 var newChildren = void 0;
8104 if (newChild.isEmpty()) {
8105 newChildren = this.children.remove(front);
8106 }
8107 else {
8108 newChildren = this.children.insert(front, newChild);
8109 }
8110 if (this.value === null && newChildren.isEmpty()) {
8111 return new ImmutableTree(null);
8112 }
8113 else {
8114 return new ImmutableTree(this.value, newChildren);
8115 }
8116 }
8117 else {
8118 return this;
8119 }
8120 }
8121 };
8122 /**
8123 * Gets a value from the tree.
8124 *
8125 * @param relativePath - Path to get value for.
8126 * @returns Value at path, or null.
8127 */
8128 ImmutableTree.prototype.get = function (relativePath) {
8129 if (pathIsEmpty(relativePath)) {
8130 return this.value;
8131 }
8132 else {
8133 var front = pathGetFront(relativePath);
8134 var child = this.children.get(front);
8135 if (child) {
8136 return child.get(pathPopFront(relativePath));
8137 }
8138 else {
8139 return null;
8140 }
8141 }
8142 };
8143 /**
8144 * Replace the subtree at the specified path with the given new tree.
8145 *
8146 * @param relativePath - Path to replace subtree for.
8147 * @param newTree - New tree.
8148 * @returns Resulting tree.
8149 */
8150 ImmutableTree.prototype.setTree = function (relativePath, newTree) {
8151 if (pathIsEmpty(relativePath)) {
8152 return newTree;
8153 }
8154 else {
8155 var front = pathGetFront(relativePath);
8156 var child = this.children.get(front) || new ImmutableTree(null);
8157 var newChild = child.setTree(pathPopFront(relativePath), newTree);
8158 var newChildren = void 0;
8159 if (newChild.isEmpty()) {
8160 newChildren = this.children.remove(front);
8161 }
8162 else {
8163 newChildren = this.children.insert(front, newChild);
8164 }
8165 return new ImmutableTree(this.value, newChildren);
8166 }
8167 };
8168 /**
8169 * Performs a depth first fold on this tree. Transforms a tree into a single
8170 * value, given a function that operates on the path to a node, an optional
8171 * current value, and a map of child names to folded subtrees
8172 */
8173 ImmutableTree.prototype.fold = function (fn) {
8174 return this.fold_(newEmptyPath(), fn);
8175 };
8176 /**
8177 * Recursive helper for public-facing fold() method
8178 */
8179 ImmutableTree.prototype.fold_ = function (pathSoFar, fn) {
8180 var accum = {};
8181 this.children.inorderTraversal(function (childKey, childTree) {
8182 accum[childKey] = childTree.fold_(pathChild(pathSoFar, childKey), fn);
8183 });
8184 return fn(pathSoFar, this.value, accum);
8185 };
8186 /**
8187 * Find the first matching value on the given path. Return the result of applying f to it.
8188 */
8189 ImmutableTree.prototype.findOnPath = function (path, f) {
8190 return this.findOnPath_(path, newEmptyPath(), f);
8191 };
8192 ImmutableTree.prototype.findOnPath_ = function (pathToFollow, pathSoFar, f) {
8193 var result = this.value ? f(pathSoFar, this.value) : false;
8194 if (result) {
8195 return result;
8196 }
8197 else {
8198 if (pathIsEmpty(pathToFollow)) {
8199 return null;
8200 }
8201 else {
8202 var front = pathGetFront(pathToFollow);
8203 var nextChild = this.children.get(front);
8204 if (nextChild) {
8205 return nextChild.findOnPath_(pathPopFront(pathToFollow), pathChild(pathSoFar, front), f);
8206 }
8207 else {
8208 return null;
8209 }
8210 }
8211 }
8212 };
8213 ImmutableTree.prototype.foreachOnPath = function (path, f) {
8214 return this.foreachOnPath_(path, newEmptyPath(), f);
8215 };
8216 ImmutableTree.prototype.foreachOnPath_ = function (pathToFollow, currentRelativePath, f) {
8217 if (pathIsEmpty(pathToFollow)) {
8218 return this;
8219 }
8220 else {
8221 if (this.value) {
8222 f(currentRelativePath, this.value);
8223 }
8224 var front = pathGetFront(pathToFollow);
8225 var nextChild = this.children.get(front);
8226 if (nextChild) {
8227 return nextChild.foreachOnPath_(pathPopFront(pathToFollow), pathChild(currentRelativePath, front), f);
8228 }
8229 else {
8230 return new ImmutableTree(null);
8231 }
8232 }
8233 };
8234 /**
8235 * Calls the given function for each node in the tree that has a value.
8236 *
8237 * @param f - A function to be called with the path from the root of the tree to
8238 * a node, and the value at that node. Called in depth-first order.
8239 */
8240 ImmutableTree.prototype.foreach = function (f) {
8241 this.foreach_(newEmptyPath(), f);
8242 };
8243 ImmutableTree.prototype.foreach_ = function (currentRelativePath, f) {
8244 this.children.inorderTraversal(function (childName, childTree) {
8245 childTree.foreach_(pathChild(currentRelativePath, childName), f);
8246 });
8247 if (this.value) {
8248 f(currentRelativePath, this.value);
8249 }
8250 };
8251 ImmutableTree.prototype.foreachChild = function (f) {
8252 this.children.inorderTraversal(function (childName, childTree) {
8253 if (childTree.value) {
8254 f(childName, childTree.value);
8255 }
8256 });
8257 };
8258 return ImmutableTree;
8259}());
8260
8261/**
8262 * @license
8263 * Copyright 2017 Google LLC
8264 *
8265 * Licensed under the Apache License, Version 2.0 (the "License");
8266 * you may not use this file except in compliance with the License.
8267 * You may obtain a copy of the License at
8268 *
8269 * http://www.apache.org/licenses/LICENSE-2.0
8270 *
8271 * Unless required by applicable law or agreed to in writing, software
8272 * distributed under the License is distributed on an "AS IS" BASIS,
8273 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8274 * See the License for the specific language governing permissions and
8275 * limitations under the License.
8276 */
8277/**
8278 * This class holds a collection of writes that can be applied to nodes in unison. It abstracts away the logic with
8279 * dealing with priority writes and multiple nested writes. At any given path there is only allowed to be one write
8280 * modifying that path. Any write to an existing path or shadowing an existing path will modify that existing write
8281 * to reflect the write added.
8282 */
8283var CompoundWrite = /** @class */ (function () {
8284 function CompoundWrite(writeTree_) {
8285 this.writeTree_ = writeTree_;
8286 }
8287 CompoundWrite.empty = function () {
8288 return new CompoundWrite(new ImmutableTree(null));
8289 };
8290 return CompoundWrite;
8291}());
8292function compoundWriteAddWrite(compoundWrite, path, node) {
8293 if (pathIsEmpty(path)) {
8294 return new CompoundWrite(new ImmutableTree(node));
8295 }
8296 else {
8297 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8298 if (rootmost != null) {
8299 var rootMostPath = rootmost.path;
8300 var value = rootmost.value;
8301 var relativePath = newRelativePath(rootMostPath, path);
8302 value = value.updateChild(relativePath, node);
8303 return new CompoundWrite(compoundWrite.writeTree_.set(rootMostPath, value));
8304 }
8305 else {
8306 var subtree = new ImmutableTree(node);
8307 var newWriteTree = compoundWrite.writeTree_.setTree(path, subtree);
8308 return new CompoundWrite(newWriteTree);
8309 }
8310 }
8311}
8312function compoundWriteAddWrites(compoundWrite, path, updates) {
8313 var newWrite = compoundWrite;
8314 each(updates, function (childKey, node) {
8315 newWrite = compoundWriteAddWrite(newWrite, pathChild(path, childKey), node);
8316 });
8317 return newWrite;
8318}
8319/**
8320 * Will remove a write at the given path and deeper paths. This will <em>not</em> modify a write at a higher
8321 * location, which must be removed by calling this method with that path.
8322 *
8323 * @param compoundWrite - The CompoundWrite to remove.
8324 * @param path - The path at which a write and all deeper writes should be removed
8325 * @returns The new CompoundWrite with the removed path
8326 */
8327function compoundWriteRemoveWrite(compoundWrite, path) {
8328 if (pathIsEmpty(path)) {
8329 return CompoundWrite.empty();
8330 }
8331 else {
8332 var newWriteTree = compoundWrite.writeTree_.setTree(path, new ImmutableTree(null));
8333 return new CompoundWrite(newWriteTree);
8334 }
8335}
8336/**
8337 * Returns whether this CompoundWrite will fully overwrite a node at a given location and can therefore be
8338 * considered "complete".
8339 *
8340 * @param compoundWrite - The CompoundWrite to check.
8341 * @param path - The path to check for
8342 * @returns Whether there is a complete write at that path
8343 */
8344function compoundWriteHasCompleteWrite(compoundWrite, path) {
8345 return compoundWriteGetCompleteNode(compoundWrite, path) != null;
8346}
8347/**
8348 * Returns a node for a path if and only if the node is a "complete" overwrite at that path. This will not aggregate
8349 * writes from deeper paths, but will return child nodes from a more shallow path.
8350 *
8351 * @param compoundWrite - The CompoundWrite to get the node from.
8352 * @param path - The path to get a complete write
8353 * @returns The node if complete at that path, or null otherwise.
8354 */
8355function compoundWriteGetCompleteNode(compoundWrite, path) {
8356 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8357 if (rootmost != null) {
8358 return compoundWrite.writeTree_
8359 .get(rootmost.path)
8360 .getChild(newRelativePath(rootmost.path, path));
8361 }
8362 else {
8363 return null;
8364 }
8365}
8366/**
8367 * Returns all children that are guaranteed to be a complete overwrite.
8368 *
8369 * @param compoundWrite - The CompoundWrite to get children from.
8370 * @returns A list of all complete children.
8371 */
8372function compoundWriteGetCompleteChildren(compoundWrite) {
8373 var children = [];
8374 var node = compoundWrite.writeTree_.value;
8375 if (node != null) {
8376 // If it's a leaf node, it has no children; so nothing to do.
8377 if (!node.isLeafNode()) {
8378 node.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8379 children.push(new NamedNode(childName, childNode));
8380 });
8381 }
8382 }
8383 else {
8384 compoundWrite.writeTree_.children.inorderTraversal(function (childName, childTree) {
8385 if (childTree.value != null) {
8386 children.push(new NamedNode(childName, childTree.value));
8387 }
8388 });
8389 }
8390 return children;
8391}
8392function compoundWriteChildCompoundWrite(compoundWrite, path) {
8393 if (pathIsEmpty(path)) {
8394 return compoundWrite;
8395 }
8396 else {
8397 var shadowingNode = compoundWriteGetCompleteNode(compoundWrite, path);
8398 if (shadowingNode != null) {
8399 return new CompoundWrite(new ImmutableTree(shadowingNode));
8400 }
8401 else {
8402 return new CompoundWrite(compoundWrite.writeTree_.subtree(path));
8403 }
8404 }
8405}
8406/**
8407 * Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
8408 * @returns Whether this CompoundWrite is empty
8409 */
8410function compoundWriteIsEmpty(compoundWrite) {
8411 return compoundWrite.writeTree_.isEmpty();
8412}
8413/**
8414 * Applies this CompoundWrite to a node. The node is returned with all writes from this CompoundWrite applied to the
8415 * node
8416 * @param node - The node to apply this CompoundWrite to
8417 * @returns The node with all writes applied
8418 */
8419function compoundWriteApply(compoundWrite, node) {
8420 return applySubtreeWrite(newEmptyPath(), compoundWrite.writeTree_, node);
8421}
8422function applySubtreeWrite(relativePath, writeTree, node) {
8423 if (writeTree.value != null) {
8424 // Since there a write is always a leaf, we're done here
8425 return node.updateChild(relativePath, writeTree.value);
8426 }
8427 else {
8428 var priorityWrite_1 = null;
8429 writeTree.children.inorderTraversal(function (childKey, childTree) {
8430 if (childKey === '.priority') {
8431 // Apply priorities at the end so we don't update priorities for either empty nodes or forget
8432 // to apply priorities to empty nodes that are later filled
8433 assert(childTree.value !== null, 'Priority writes must always be leaf nodes');
8434 priorityWrite_1 = childTree.value;
8435 }
8436 else {
8437 node = applySubtreeWrite(pathChild(relativePath, childKey), childTree, node);
8438 }
8439 });
8440 // If there was a priority write, we only apply it if the node is not empty
8441 if (!node.getChild(relativePath).isEmpty() && priorityWrite_1 !== null) {
8442 node = node.updateChild(pathChild(relativePath, '.priority'), priorityWrite_1);
8443 }
8444 return node;
8445 }
8446}
8447
8448/**
8449 * @license
8450 * Copyright 2017 Google LLC
8451 *
8452 * Licensed under the Apache License, Version 2.0 (the "License");
8453 * you may not use this file except in compliance with the License.
8454 * You may obtain a copy of the License at
8455 *
8456 * http://www.apache.org/licenses/LICENSE-2.0
8457 *
8458 * Unless required by applicable law or agreed to in writing, software
8459 * distributed under the License is distributed on an "AS IS" BASIS,
8460 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8461 * See the License for the specific language governing permissions and
8462 * limitations under the License.
8463 */
8464/**
8465 * Create a new WriteTreeRef for the given path. For use with a new sync point at the given path.
8466 *
8467 */
8468function writeTreeChildWrites(writeTree, path) {
8469 return newWriteTreeRef(path, writeTree);
8470}
8471/**
8472 * Record a new overwrite from user code.
8473 *
8474 * @param visible - This is set to false by some transactions. It should be excluded from event caches
8475 */
8476function writeTreeAddOverwrite(writeTree, path, snap, writeId, visible) {
8477 assert(writeId > writeTree.lastWriteId, 'Stacking an older write on top of newer ones');
8478 if (visible === undefined) {
8479 visible = true;
8480 }
8481 writeTree.allWrites.push({
8482 path: path,
8483 snap: snap,
8484 writeId: writeId,
8485 visible: visible
8486 });
8487 if (visible) {
8488 writeTree.visibleWrites = compoundWriteAddWrite(writeTree.visibleWrites, path, snap);
8489 }
8490 writeTree.lastWriteId = writeId;
8491}
8492/**
8493 * Record a new merge from user code.
8494 */
8495function writeTreeAddMerge(writeTree, path, changedChildren, writeId) {
8496 assert(writeId > writeTree.lastWriteId, 'Stacking an older merge on top of newer ones');
8497 writeTree.allWrites.push({
8498 path: path,
8499 children: changedChildren,
8500 writeId: writeId,
8501 visible: true
8502 });
8503 writeTree.visibleWrites = compoundWriteAddWrites(writeTree.visibleWrites, path, changedChildren);
8504 writeTree.lastWriteId = writeId;
8505}
8506function writeTreeGetWrite(writeTree, writeId) {
8507 for (var i = 0; i < writeTree.allWrites.length; i++) {
8508 var record = writeTree.allWrites[i];
8509 if (record.writeId === writeId) {
8510 return record;
8511 }
8512 }
8513 return null;
8514}
8515/**
8516 * Remove a write (either an overwrite or merge) that has been successfully acknowledge by the server. Recalculates
8517 * the tree if necessary. We return true if it may have been visible, meaning views need to reevaluate.
8518 *
8519 * @returns true if the write may have been visible (meaning we'll need to reevaluate / raise
8520 * events as a result).
8521 */
8522function writeTreeRemoveWrite(writeTree, writeId) {
8523 // Note: disabling this check. It could be a transaction that preempted another transaction, and thus was applied
8524 // out of order.
8525 //const validClear = revert || this.allWrites_.length === 0 || writeId <= this.allWrites_[0].writeId;
8526 //assert(validClear, "Either we don't have this write, or it's the first one in the queue");
8527 var idx = writeTree.allWrites.findIndex(function (s) {
8528 return s.writeId === writeId;
8529 });
8530 assert(idx >= 0, 'removeWrite called with nonexistent writeId.');
8531 var writeToRemove = writeTree.allWrites[idx];
8532 writeTree.allWrites.splice(idx, 1);
8533 var removedWriteWasVisible = writeToRemove.visible;
8534 var removedWriteOverlapsWithOtherWrites = false;
8535 var i = writeTree.allWrites.length - 1;
8536 while (removedWriteWasVisible && i >= 0) {
8537 var currentWrite = writeTree.allWrites[i];
8538 if (currentWrite.visible) {
8539 if (i >= idx &&
8540 writeTreeRecordContainsPath_(currentWrite, writeToRemove.path)) {
8541 // The removed write was completely shadowed by a subsequent write.
8542 removedWriteWasVisible = false;
8543 }
8544 else if (pathContains(writeToRemove.path, currentWrite.path)) {
8545 // Either we're covering some writes or they're covering part of us (depending on which came first).
8546 removedWriteOverlapsWithOtherWrites = true;
8547 }
8548 }
8549 i--;
8550 }
8551 if (!removedWriteWasVisible) {
8552 return false;
8553 }
8554 else if (removedWriteOverlapsWithOtherWrites) {
8555 // There's some shadowing going on. Just rebuild the visible writes from scratch.
8556 writeTreeResetTree_(writeTree);
8557 return true;
8558 }
8559 else {
8560 // There's no shadowing. We can safely just remove the write(s) from visibleWrites.
8561 if (writeToRemove.snap) {
8562 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, writeToRemove.path);
8563 }
8564 else {
8565 var children = writeToRemove.children;
8566 each(children, function (childName) {
8567 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, pathChild(writeToRemove.path, childName));
8568 });
8569 }
8570 return true;
8571 }
8572}
8573function writeTreeRecordContainsPath_(writeRecord, path) {
8574 if (writeRecord.snap) {
8575 return pathContains(writeRecord.path, path);
8576 }
8577 else {
8578 for (var childName in writeRecord.children) {
8579 if (writeRecord.children.hasOwnProperty(childName) &&
8580 pathContains(pathChild(writeRecord.path, childName), path)) {
8581 return true;
8582 }
8583 }
8584 return false;
8585 }
8586}
8587/**
8588 * Re-layer the writes and merges into a tree so we can efficiently calculate event snapshots
8589 */
8590function writeTreeResetTree_(writeTree) {
8591 writeTree.visibleWrites = writeTreeLayerTree_(writeTree.allWrites, writeTreeDefaultFilter_, newEmptyPath());
8592 if (writeTree.allWrites.length > 0) {
8593 writeTree.lastWriteId =
8594 writeTree.allWrites[writeTree.allWrites.length - 1].writeId;
8595 }
8596 else {
8597 writeTree.lastWriteId = -1;
8598 }
8599}
8600/**
8601 * The default filter used when constructing the tree. Keep everything that's visible.
8602 */
8603function writeTreeDefaultFilter_(write) {
8604 return write.visible;
8605}
8606/**
8607 * Static method. Given an array of WriteRecords, a filter for which ones to include, and a path, construct the tree of
8608 * event data at that path.
8609 */
8610function writeTreeLayerTree_(writes, filter, treeRoot) {
8611 var compoundWrite = CompoundWrite.empty();
8612 for (var i = 0; i < writes.length; ++i) {
8613 var write = writes[i];
8614 // Theory, a later set will either:
8615 // a) abort a relevant transaction, so no need to worry about excluding it from calculating that transaction
8616 // b) not be relevant to a transaction (separate branch), so again will not affect the data for that transaction
8617 if (filter(write)) {
8618 var writePath = write.path;
8619 var relativePath = void 0;
8620 if (write.snap) {
8621 if (pathContains(treeRoot, writePath)) {
8622 relativePath = newRelativePath(treeRoot, writePath);
8623 compoundWrite = compoundWriteAddWrite(compoundWrite, relativePath, write.snap);
8624 }
8625 else if (pathContains(writePath, treeRoot)) {
8626 relativePath = newRelativePath(writePath, treeRoot);
8627 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), write.snap.getChild(relativePath));
8628 }
8629 else ;
8630 }
8631 else if (write.children) {
8632 if (pathContains(treeRoot, writePath)) {
8633 relativePath = newRelativePath(treeRoot, writePath);
8634 compoundWrite = compoundWriteAddWrites(compoundWrite, relativePath, write.children);
8635 }
8636 else if (pathContains(writePath, treeRoot)) {
8637 relativePath = newRelativePath(writePath, treeRoot);
8638 if (pathIsEmpty(relativePath)) {
8639 compoundWrite = compoundWriteAddWrites(compoundWrite, newEmptyPath(), write.children);
8640 }
8641 else {
8642 var child = safeGet(write.children, pathGetFront(relativePath));
8643 if (child) {
8644 // There exists a child in this node that matches the root path
8645 var deepNode = child.getChild(pathPopFront(relativePath));
8646 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), deepNode);
8647 }
8648 }
8649 }
8650 else ;
8651 }
8652 else {
8653 throw assertionError('WriteRecord should have .snap or .children');
8654 }
8655 }
8656 }
8657 return compoundWrite;
8658}
8659/**
8660 * Given optional, underlying server data, and an optional set of constraints (exclude some sets, include hidden
8661 * writes), attempt to calculate a complete snapshot for the given path
8662 *
8663 * @param writeIdsToExclude - An optional set to be excluded
8664 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8665 */
8666function writeTreeCalcCompleteEventCache(writeTree, treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8667 if (!writeIdsToExclude && !includeHiddenWrites) {
8668 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8669 if (shadowingNode != null) {
8670 return shadowingNode;
8671 }
8672 else {
8673 var subMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8674 if (compoundWriteIsEmpty(subMerge)) {
8675 return completeServerCache;
8676 }
8677 else if (completeServerCache == null &&
8678 !compoundWriteHasCompleteWrite(subMerge, newEmptyPath())) {
8679 // We wouldn't have a complete snapshot, since there's no underlying data and no complete shadow
8680 return null;
8681 }
8682 else {
8683 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8684 return compoundWriteApply(subMerge, layeredCache);
8685 }
8686 }
8687 }
8688 else {
8689 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8690 if (!includeHiddenWrites && compoundWriteIsEmpty(merge)) {
8691 return completeServerCache;
8692 }
8693 else {
8694 // If the server cache is null, and we don't have a complete cache, we need to return null
8695 if (!includeHiddenWrites &&
8696 completeServerCache == null &&
8697 !compoundWriteHasCompleteWrite(merge, newEmptyPath())) {
8698 return null;
8699 }
8700 else {
8701 var filter = function (write) {
8702 return ((write.visible || includeHiddenWrites) &&
8703 (!writeIdsToExclude ||
8704 !~writeIdsToExclude.indexOf(write.writeId)) &&
8705 (pathContains(write.path, treePath) ||
8706 pathContains(treePath, write.path)));
8707 };
8708 var mergeAtPath = writeTreeLayerTree_(writeTree.allWrites, filter, treePath);
8709 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8710 return compoundWriteApply(mergeAtPath, layeredCache);
8711 }
8712 }
8713 }
8714}
8715/**
8716 * With optional, underlying server data, attempt to return a children node of children that we have complete data for.
8717 * Used when creating new views, to pre-fill their complete event children snapshot.
8718 */
8719function writeTreeCalcCompleteEventChildren(writeTree, treePath, completeServerChildren) {
8720 var completeChildren = ChildrenNode.EMPTY_NODE;
8721 var topLevelSet = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8722 if (topLevelSet) {
8723 if (!topLevelSet.isLeafNode()) {
8724 // we're shadowing everything. Return the children.
8725 topLevelSet.forEachChild(PRIORITY_INDEX, function (childName, childSnap) {
8726 completeChildren = completeChildren.updateImmediateChild(childName, childSnap);
8727 });
8728 }
8729 return completeChildren;
8730 }
8731 else if (completeServerChildren) {
8732 // Layer any children we have on top of this
8733 // We know we don't have a top-level set, so just enumerate existing children
8734 var merge_1 = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8735 completeServerChildren.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8736 var node = compoundWriteApply(compoundWriteChildCompoundWrite(merge_1, new Path(childName)), childNode);
8737 completeChildren = completeChildren.updateImmediateChild(childName, node);
8738 });
8739 // Add any complete children we have from the set
8740 compoundWriteGetCompleteChildren(merge_1).forEach(function (namedNode) {
8741 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8742 });
8743 return completeChildren;
8744 }
8745 else {
8746 // We don't have anything to layer on top of. Layer on any children we have
8747 // Note that we can return an empty snap if we have a defined delete
8748 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8749 compoundWriteGetCompleteChildren(merge).forEach(function (namedNode) {
8750 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8751 });
8752 return completeChildren;
8753 }
8754}
8755/**
8756 * Given that the underlying server data has updated, determine what, if anything, needs to be
8757 * applied to the event cache.
8758 *
8759 * Possibilities:
8760 *
8761 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8762 *
8763 * 2. Some write is completely shadowing. No events to be raised
8764 *
8765 * 3. Is partially shadowed. Events
8766 *
8767 * Either existingEventSnap or existingServerSnap must exist
8768 */
8769function writeTreeCalcEventCacheAfterServerOverwrite(writeTree, treePath, childPath, existingEventSnap, existingServerSnap) {
8770 assert(existingEventSnap || existingServerSnap, 'Either existingEventSnap or existingServerSnap must exist');
8771 var path = pathChild(treePath, childPath);
8772 if (compoundWriteHasCompleteWrite(writeTree.visibleWrites, path)) {
8773 // At this point we can probably guarantee that we're in case 2, meaning no events
8774 // May need to check visibility while doing the findRootMostValueAndPath call
8775 return null;
8776 }
8777 else {
8778 // No complete shadowing. We're either partially shadowing or not shadowing at all.
8779 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8780 if (compoundWriteIsEmpty(childMerge)) {
8781 // We're not shadowing at all. Case 1
8782 return existingServerSnap.getChild(childPath);
8783 }
8784 else {
8785 // This could be more efficient if the serverNode + updates doesn't change the eventSnap
8786 // However this is tricky to find out, since user updates don't necessary change the server
8787 // snap, e.g. priority updates on empty nodes, or deep deletes. Another special case is if the server
8788 // adds nodes, but doesn't change any existing writes. It is therefore not enough to
8789 // only check if the updates change the serverNode.
8790 // Maybe check if the merge tree contains these special cases and only do a full overwrite in that case?
8791 return compoundWriteApply(childMerge, existingServerSnap.getChild(childPath));
8792 }
8793 }
8794}
8795/**
8796 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8797 * complete child for this ChildKey.
8798 */
8799function writeTreeCalcCompleteChild(writeTree, treePath, childKey, existingServerSnap) {
8800 var path = pathChild(treePath, childKey);
8801 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8802 if (shadowingNode != null) {
8803 return shadowingNode;
8804 }
8805 else {
8806 if (existingServerSnap.isCompleteForChild(childKey)) {
8807 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8808 return compoundWriteApply(childMerge, existingServerSnap.getNode().getImmediateChild(childKey));
8809 }
8810 else {
8811 return null;
8812 }
8813 }
8814}
8815/**
8816 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8817 * a higher path, this will return the child of that write relative to the write and this path.
8818 * Returns null if there is no write at this path.
8819 */
8820function writeTreeShadowingWrite(writeTree, path) {
8821 return compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8822}
8823/**
8824 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8825 * the window, but may now be in the window.
8826 */
8827function writeTreeCalcIndexedSlice(writeTree, treePath, completeServerData, startPost, count, reverse, index) {
8828 var toIterate;
8829 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8830 var shadowingNode = compoundWriteGetCompleteNode(merge, newEmptyPath());
8831 if (shadowingNode != null) {
8832 toIterate = shadowingNode;
8833 }
8834 else if (completeServerData != null) {
8835 toIterate = compoundWriteApply(merge, completeServerData);
8836 }
8837 else {
8838 // no children to iterate on
8839 return [];
8840 }
8841 toIterate = toIterate.withIndex(index);
8842 if (!toIterate.isEmpty() && !toIterate.isLeafNode()) {
8843 var nodes = [];
8844 var cmp = index.getCompare();
8845 var iter = reverse
8846 ? toIterate.getReverseIteratorFrom(startPost, index)
8847 : toIterate.getIteratorFrom(startPost, index);
8848 var next = iter.getNext();
8849 while (next && nodes.length < count) {
8850 if (cmp(next, startPost) !== 0) {
8851 nodes.push(next);
8852 }
8853 next = iter.getNext();
8854 }
8855 return nodes;
8856 }
8857 else {
8858 return [];
8859 }
8860}
8861function newWriteTree() {
8862 return {
8863 visibleWrites: CompoundWrite.empty(),
8864 allWrites: [],
8865 lastWriteId: -1
8866 };
8867}
8868/**
8869 * If possible, returns a complete event cache, using the underlying server data if possible. In addition, can be used
8870 * to get a cache that includes hidden writes, and excludes arbitrary writes. Note that customizing the returned node
8871 * can lead to a more expensive calculation.
8872 *
8873 * @param writeIdsToExclude - Optional writes to exclude.
8874 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8875 */
8876function writeTreeRefCalcCompleteEventCache(writeTreeRef, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8877 return writeTreeCalcCompleteEventCache(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites);
8878}
8879/**
8880 * If possible, returns a children node containing all of the complete children we have data for. The returned data is a
8881 * mix of the given server data and write data.
8882 *
8883 */
8884function writeTreeRefCalcCompleteEventChildren(writeTreeRef, completeServerChildren) {
8885 return writeTreeCalcCompleteEventChildren(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerChildren);
8886}
8887/**
8888 * Given that either the underlying server data has updated or the outstanding writes have updated, determine what,
8889 * if anything, needs to be applied to the event cache.
8890 *
8891 * Possibilities:
8892 *
8893 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8894 *
8895 * 2. Some write is completely shadowing. No events to be raised
8896 *
8897 * 3. Is partially shadowed. Events should be raised
8898 *
8899 * Either existingEventSnap or existingServerSnap must exist, this is validated via an assert
8900 *
8901 *
8902 */
8903function writeTreeRefCalcEventCacheAfterServerOverwrite(writeTreeRef, path, existingEventSnap, existingServerSnap) {
8904 return writeTreeCalcEventCacheAfterServerOverwrite(writeTreeRef.writeTree, writeTreeRef.treePath, path, existingEventSnap, existingServerSnap);
8905}
8906/**
8907 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8908 * a higher path, this will return the child of that write relative to the write and this path.
8909 * Returns null if there is no write at this path.
8910 *
8911 */
8912function writeTreeRefShadowingWrite(writeTreeRef, path) {
8913 return writeTreeShadowingWrite(writeTreeRef.writeTree, pathChild(writeTreeRef.treePath, path));
8914}
8915/**
8916 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8917 * the window, but may now be in the window
8918 */
8919function writeTreeRefCalcIndexedSlice(writeTreeRef, completeServerData, startPost, count, reverse, index) {
8920 return writeTreeCalcIndexedSlice(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerData, startPost, count, reverse, index);
8921}
8922/**
8923 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8924 * complete child for this ChildKey.
8925 */
8926function writeTreeRefCalcCompleteChild(writeTreeRef, childKey, existingServerCache) {
8927 return writeTreeCalcCompleteChild(writeTreeRef.writeTree, writeTreeRef.treePath, childKey, existingServerCache);
8928}
8929/**
8930 * Return a WriteTreeRef for a child.
8931 */
8932function writeTreeRefChild(writeTreeRef, childName) {
8933 return newWriteTreeRef(pathChild(writeTreeRef.treePath, childName), writeTreeRef.writeTree);
8934}
8935function newWriteTreeRef(path, writeTree) {
8936 return {
8937 treePath: path,
8938 writeTree: writeTree
8939 };
8940}
8941
8942/**
8943 * @license
8944 * Copyright 2017 Google LLC
8945 *
8946 * Licensed under the Apache License, Version 2.0 (the "License");
8947 * you may not use this file except in compliance with the License.
8948 * You may obtain a copy of the License at
8949 *
8950 * http://www.apache.org/licenses/LICENSE-2.0
8951 *
8952 * Unless required by applicable law or agreed to in writing, software
8953 * distributed under the License is distributed on an "AS IS" BASIS,
8954 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8955 * See the License for the specific language governing permissions and
8956 * limitations under the License.
8957 */
8958var ChildChangeAccumulator = /** @class */ (function () {
8959 function ChildChangeAccumulator() {
8960 this.changeMap = new Map();
8961 }
8962 ChildChangeAccumulator.prototype.trackChildChange = function (change) {
8963 var type = change.type;
8964 var childKey = change.childName;
8965 assert(type === "child_added" /* CHILD_ADDED */ ||
8966 type === "child_changed" /* CHILD_CHANGED */ ||
8967 type === "child_removed" /* CHILD_REMOVED */, 'Only child changes supported for tracking');
8968 assert(childKey !== '.priority', 'Only non-priority child changes can be tracked.');
8969 var oldChange = this.changeMap.get(childKey);
8970 if (oldChange) {
8971 var oldType = oldChange.type;
8972 if (type === "child_added" /* CHILD_ADDED */ &&
8973 oldType === "child_removed" /* CHILD_REMOVED */) {
8974 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.snapshotNode));
8975 }
8976 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8977 oldType === "child_added" /* CHILD_ADDED */) {
8978 this.changeMap.delete(childKey);
8979 }
8980 else if (type === "child_removed" /* CHILD_REMOVED */ &&
8981 oldType === "child_changed" /* CHILD_CHANGED */) {
8982 this.changeMap.set(childKey, changeChildRemoved(childKey, oldChange.oldSnap));
8983 }
8984 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8985 oldType === "child_added" /* CHILD_ADDED */) {
8986 this.changeMap.set(childKey, changeChildAdded(childKey, change.snapshotNode));
8987 }
8988 else if (type === "child_changed" /* CHILD_CHANGED */ &&
8989 oldType === "child_changed" /* CHILD_CHANGED */) {
8990 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.oldSnap));
8991 }
8992 else {
8993 throw assertionError('Illegal combination of changes: ' +
8994 change +
8995 ' occurred after ' +
8996 oldChange);
8997 }
8998 }
8999 else {
9000 this.changeMap.set(childKey, change);
9001 }
9002 };
9003 ChildChangeAccumulator.prototype.getChanges = function () {
9004 return Array.from(this.changeMap.values());
9005 };
9006 return ChildChangeAccumulator;
9007}());
9008
9009/**
9010 * @license
9011 * Copyright 2017 Google LLC
9012 *
9013 * Licensed under the Apache License, Version 2.0 (the "License");
9014 * you may not use this file except in compliance with the License.
9015 * You may obtain a copy of the License at
9016 *
9017 * http://www.apache.org/licenses/LICENSE-2.0
9018 *
9019 * Unless required by applicable law or agreed to in writing, software
9020 * distributed under the License is distributed on an "AS IS" BASIS,
9021 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9022 * See the License for the specific language governing permissions and
9023 * limitations under the License.
9024 */
9025/**
9026 * An implementation of CompleteChildSource that never returns any additional children
9027 */
9028// eslint-disable-next-line @typescript-eslint/naming-convention
9029var NoCompleteChildSource_ = /** @class */ (function () {
9030 function NoCompleteChildSource_() {
9031 }
9032 NoCompleteChildSource_.prototype.getCompleteChild = function (childKey) {
9033 return null;
9034 };
9035 NoCompleteChildSource_.prototype.getChildAfterChild = function (index, child, reverse) {
9036 return null;
9037 };
9038 return NoCompleteChildSource_;
9039}());
9040/**
9041 * Singleton instance.
9042 */
9043var NO_COMPLETE_CHILD_SOURCE = new NoCompleteChildSource_();
9044/**
9045 * An implementation of CompleteChildSource that uses a WriteTree in addition to any other server data or
9046 * old event caches available to calculate complete children.
9047 */
9048var WriteTreeCompleteChildSource = /** @class */ (function () {
9049 function WriteTreeCompleteChildSource(writes_, viewCache_, optCompleteServerCache_) {
9050 if (optCompleteServerCache_ === void 0) { optCompleteServerCache_ = null; }
9051 this.writes_ = writes_;
9052 this.viewCache_ = viewCache_;
9053 this.optCompleteServerCache_ = optCompleteServerCache_;
9054 }
9055 WriteTreeCompleteChildSource.prototype.getCompleteChild = function (childKey) {
9056 var node = this.viewCache_.eventCache;
9057 if (node.isCompleteForChild(childKey)) {
9058 return node.getNode().getImmediateChild(childKey);
9059 }
9060 else {
9061 var serverNode = this.optCompleteServerCache_ != null
9062 ? new CacheNode(this.optCompleteServerCache_, true, false)
9063 : this.viewCache_.serverCache;
9064 return writeTreeRefCalcCompleteChild(this.writes_, childKey, serverNode);
9065 }
9066 };
9067 WriteTreeCompleteChildSource.prototype.getChildAfterChild = function (index, child, reverse) {
9068 var completeServerData = this.optCompleteServerCache_ != null
9069 ? this.optCompleteServerCache_
9070 : viewCacheGetCompleteServerSnap(this.viewCache_);
9071 var nodes = writeTreeRefCalcIndexedSlice(this.writes_, completeServerData, child, 1, reverse, index);
9072 if (nodes.length === 0) {
9073 return null;
9074 }
9075 else {
9076 return nodes[0];
9077 }
9078 };
9079 return WriteTreeCompleteChildSource;
9080}());
9081
9082/**
9083 * @license
9084 * Copyright 2017 Google LLC
9085 *
9086 * Licensed under the Apache License, Version 2.0 (the "License");
9087 * you may not use this file except in compliance with the License.
9088 * You may obtain a copy of the License at
9089 *
9090 * http://www.apache.org/licenses/LICENSE-2.0
9091 *
9092 * Unless required by applicable law or agreed to in writing, software
9093 * distributed under the License is distributed on an "AS IS" BASIS,
9094 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9095 * See the License for the specific language governing permissions and
9096 * limitations under the License.
9097 */
9098function newViewProcessor(filter) {
9099 return { filter: filter };
9100}
9101function viewProcessorAssertIndexed(viewProcessor, viewCache) {
9102 assert(viewCache.eventCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Event snap not indexed');
9103 assert(viewCache.serverCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Server snap not indexed');
9104}
9105function viewProcessorApplyOperation(viewProcessor, oldViewCache, operation, writesCache, completeCache) {
9106 var accumulator = new ChildChangeAccumulator();
9107 var newViewCache, filterServerNode;
9108 if (operation.type === OperationType.OVERWRITE) {
9109 var overwrite = operation;
9110 if (overwrite.source.fromUser) {
9111 newViewCache = viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, accumulator);
9112 }
9113 else {
9114 assert(overwrite.source.fromServer, 'Unknown source.');
9115 // We filter the node if it's a tagged update or the node has been previously filtered and the
9116 // update is not at the root in which case it is ok (and necessary) to mark the node unfiltered
9117 // again
9118 filterServerNode =
9119 overwrite.source.tagged ||
9120 (oldViewCache.serverCache.isFiltered() && !pathIsEmpty(overwrite.path));
9121 newViewCache = viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, filterServerNode, accumulator);
9122 }
9123 }
9124 else if (operation.type === OperationType.MERGE) {
9125 var merge = operation;
9126 if (merge.source.fromUser) {
9127 newViewCache = viewProcessorApplyUserMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, accumulator);
9128 }
9129 else {
9130 assert(merge.source.fromServer, 'Unknown source.');
9131 // We filter the node if it's a tagged update or the node has been previously filtered
9132 filterServerNode =
9133 merge.source.tagged || oldViewCache.serverCache.isFiltered();
9134 newViewCache = viewProcessorApplyServerMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, filterServerNode, accumulator);
9135 }
9136 }
9137 else if (operation.type === OperationType.ACK_USER_WRITE) {
9138 var ackUserWrite = operation;
9139 if (!ackUserWrite.revert) {
9140 newViewCache = viewProcessorAckUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, ackUserWrite.affectedTree, writesCache, completeCache, accumulator);
9141 }
9142 else {
9143 newViewCache = viewProcessorRevertUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, writesCache, completeCache, accumulator);
9144 }
9145 }
9146 else if (operation.type === OperationType.LISTEN_COMPLETE) {
9147 newViewCache = viewProcessorListenComplete(viewProcessor, oldViewCache, operation.path, writesCache, accumulator);
9148 }
9149 else {
9150 throw assertionError('Unknown operation type: ' + operation.type);
9151 }
9152 var changes = accumulator.getChanges();
9153 viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, changes);
9154 return { viewCache: newViewCache, changes: changes };
9155}
9156function viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, accumulator) {
9157 var eventSnap = newViewCache.eventCache;
9158 if (eventSnap.isFullyInitialized()) {
9159 var isLeafOrEmpty = eventSnap.getNode().isLeafNode() || eventSnap.getNode().isEmpty();
9160 var oldCompleteSnap = viewCacheGetCompleteEventSnap(oldViewCache);
9161 if (accumulator.length > 0 ||
9162 !oldViewCache.eventCache.isFullyInitialized() ||
9163 (isLeafOrEmpty && !eventSnap.getNode().equals(oldCompleteSnap)) ||
9164 !eventSnap.getNode().getPriority().equals(oldCompleteSnap.getPriority())) {
9165 accumulator.push(changeValue(viewCacheGetCompleteEventSnap(newViewCache)));
9166 }
9167 }
9168}
9169function viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, viewCache, changePath, writesCache, source, accumulator) {
9170 var oldEventSnap = viewCache.eventCache;
9171 if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
9172 // we have a shadowing write, ignore changes
9173 return viewCache;
9174 }
9175 else {
9176 var newEventCache = void 0, serverNode = void 0;
9177 if (pathIsEmpty(changePath)) {
9178 // TODO: figure out how this plays with "sliding ack windows"
9179 assert(viewCache.serverCache.isFullyInitialized(), 'If change path is empty, we must have complete server data');
9180 if (viewCache.serverCache.isFiltered()) {
9181 // We need to special case this, because we need to only apply writes to complete children, or
9182 // we might end up raising events for incomplete children. If the server data is filtered deep
9183 // writes cannot be guaranteed to be complete
9184 var serverCache = viewCacheGetCompleteServerSnap(viewCache);
9185 var completeChildren = serverCache instanceof ChildrenNode
9186 ? serverCache
9187 : ChildrenNode.EMPTY_NODE;
9188 var completeEventChildren = writeTreeRefCalcCompleteEventChildren(writesCache, completeChildren);
9189 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeEventChildren, accumulator);
9190 }
9191 else {
9192 var completeNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9193 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeNode, accumulator);
9194 }
9195 }
9196 else {
9197 var childKey = pathGetFront(changePath);
9198 if (childKey === '.priority') {
9199 assert(pathGetLength(changePath) === 1, "Can't have a priority with additional path components");
9200 var oldEventNode = oldEventSnap.getNode();
9201 serverNode = viewCache.serverCache.getNode();
9202 // we might have overwrites for this priority
9203 var updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventNode, serverNode);
9204 if (updatedPriority != null) {
9205 newEventCache = viewProcessor.filter.updatePriority(oldEventNode, updatedPriority);
9206 }
9207 else {
9208 // priority didn't change, keep old node
9209 newEventCache = oldEventSnap.getNode();
9210 }
9211 }
9212 else {
9213 var childChangePath = pathPopFront(changePath);
9214 // update child
9215 var newEventChild = void 0;
9216 if (oldEventSnap.isCompleteForChild(childKey)) {
9217 serverNode = viewCache.serverCache.getNode();
9218 var eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventSnap.getNode(), serverNode);
9219 if (eventChildUpdate != null) {
9220 newEventChild = oldEventSnap
9221 .getNode()
9222 .getImmediateChild(childKey)
9223 .updateChild(childChangePath, eventChildUpdate);
9224 }
9225 else {
9226 // Nothing changed, just keep the old child
9227 newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
9228 }
9229 }
9230 else {
9231 newEventChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9232 }
9233 if (newEventChild != null) {
9234 newEventCache = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newEventChild, childChangePath, source, accumulator);
9235 }
9236 else {
9237 // no complete child available or no change
9238 newEventCache = oldEventSnap.getNode();
9239 }
9240 }
9241 }
9242 return viewCacheUpdateEventSnap(viewCache, newEventCache, oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath), viewProcessor.filter.filtersNodes());
9243 }
9244}
9245function viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, filterServerNode, accumulator) {
9246 var oldServerSnap = oldViewCache.serverCache;
9247 var newServerCache;
9248 var serverFilter = filterServerNode
9249 ? viewProcessor.filter
9250 : viewProcessor.filter.getIndexedFilter();
9251 if (pathIsEmpty(changePath)) {
9252 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), changedSnap, null);
9253 }
9254 else if (serverFilter.filtersNodes() && !oldServerSnap.isFiltered()) {
9255 // we want to filter the server node, but we didn't filter the server node yet, so simulate a full update
9256 var newServerNode = oldServerSnap
9257 .getNode()
9258 .updateChild(changePath, changedSnap);
9259 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), newServerNode, null);
9260 }
9261 else {
9262 var childKey = pathGetFront(changePath);
9263 if (!oldServerSnap.isCompleteForPath(changePath) &&
9264 pathGetLength(changePath) > 1) {
9265 // We don't update incomplete nodes with updates intended for other listeners
9266 return oldViewCache;
9267 }
9268 var childChangePath = pathPopFront(changePath);
9269 var childNode = oldServerSnap.getNode().getImmediateChild(childKey);
9270 var newChildNode = childNode.updateChild(childChangePath, changedSnap);
9271 if (childKey === '.priority') {
9272 newServerCache = serverFilter.updatePriority(oldServerSnap.getNode(), newChildNode);
9273 }
9274 else {
9275 newServerCache = serverFilter.updateChild(oldServerSnap.getNode(), childKey, newChildNode, childChangePath, NO_COMPLETE_CHILD_SOURCE, null);
9276 }
9277 }
9278 var newViewCache = viewCacheUpdateServerSnap(oldViewCache, newServerCache, oldServerSnap.isFullyInitialized() || pathIsEmpty(changePath), serverFilter.filtersNodes());
9279 var source = new WriteTreeCompleteChildSource(writesCache, newViewCache, completeCache);
9280 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, changePath, writesCache, source, accumulator);
9281}
9282function viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, accumulator) {
9283 var oldEventSnap = oldViewCache.eventCache;
9284 var newViewCache, newEventCache;
9285 var source = new WriteTreeCompleteChildSource(writesCache, oldViewCache, completeCache);
9286 if (pathIsEmpty(changePath)) {
9287 newEventCache = viewProcessor.filter.updateFullNode(oldViewCache.eventCache.getNode(), changedSnap, accumulator);
9288 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, true, viewProcessor.filter.filtersNodes());
9289 }
9290 else {
9291 var childKey = pathGetFront(changePath);
9292 if (childKey === '.priority') {
9293 newEventCache = viewProcessor.filter.updatePriority(oldViewCache.eventCache.getNode(), changedSnap);
9294 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, oldEventSnap.isFullyInitialized(), oldEventSnap.isFiltered());
9295 }
9296 else {
9297 var childChangePath = pathPopFront(changePath);
9298 var oldChild = oldEventSnap.getNode().getImmediateChild(childKey);
9299 var newChild = void 0;
9300 if (pathIsEmpty(childChangePath)) {
9301 // Child overwrite, we can replace the child
9302 newChild = changedSnap;
9303 }
9304 else {
9305 var childNode = source.getCompleteChild(childKey);
9306 if (childNode != null) {
9307 if (pathGetBack(childChangePath) === '.priority' &&
9308 childNode.getChild(pathParent(childChangePath)).isEmpty()) {
9309 // This is a priority update on an empty node. If this node exists on the server, the
9310 // server will send down the priority in the update, so ignore for now
9311 newChild = childNode;
9312 }
9313 else {
9314 newChild = childNode.updateChild(childChangePath, changedSnap);
9315 }
9316 }
9317 else {
9318 // There is no complete child node available
9319 newChild = ChildrenNode.EMPTY_NODE;
9320 }
9321 }
9322 if (!oldChild.equals(newChild)) {
9323 var newEventSnap = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newChild, childChangePath, source, accumulator);
9324 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventSnap, oldEventSnap.isFullyInitialized(), viewProcessor.filter.filtersNodes());
9325 }
9326 else {
9327 newViewCache = oldViewCache;
9328 }
9329 }
9330 }
9331 return newViewCache;
9332}
9333function viewProcessorCacheHasChild(viewCache, childKey) {
9334 return viewCache.eventCache.isCompleteForChild(childKey);
9335}
9336function viewProcessorApplyUserMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, accumulator) {
9337 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9338 // window leaving room for new items. It's important we process these changes first, so we
9339 // iterate the changes twice, first processing any that affect items currently in view.
9340 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9341 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9342 // not the other.
9343 var curViewCache = viewCache;
9344 changedChildren.foreach(function (relativePath, childNode) {
9345 var writePath = pathChild(path, relativePath);
9346 if (viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9347 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9348 }
9349 });
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 return curViewCache;
9357}
9358function viewProcessorApplyMerge(viewProcessor, node, merge) {
9359 merge.foreach(function (relativePath, childNode) {
9360 node = node.updateChild(relativePath, childNode);
9361 });
9362 return node;
9363}
9364function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, filterServerNode, accumulator) {
9365 // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
9366 // wait for the complete data update coming soon.
9367 if (viewCache.serverCache.getNode().isEmpty() &&
9368 !viewCache.serverCache.isFullyInitialized()) {
9369 return viewCache;
9370 }
9371 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9372 // window leaving room for new items. It's important we process these changes first, so we
9373 // iterate the changes twice, first processing any that affect items currently in view.
9374 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9375 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9376 // not the other.
9377 var curViewCache = viewCache;
9378 var viewMergeTree;
9379 if (pathIsEmpty(path)) {
9380 viewMergeTree = changedChildren;
9381 }
9382 else {
9383 viewMergeTree = new ImmutableTree(null).setTree(path, changedChildren);
9384 }
9385 var serverNode = viewCache.serverCache.getNode();
9386 viewMergeTree.children.inorderTraversal(function (childKey, childTree) {
9387 if (serverNode.hasChild(childKey)) {
9388 var serverChild = viewCache.serverCache
9389 .getNode()
9390 .getImmediateChild(childKey);
9391 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childTree);
9392 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9393 }
9394 });
9395 viewMergeTree.children.inorderTraversal(function (childKey, childMergeTree) {
9396 var isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9397 childMergeTree.value === undefined;
9398 if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9399 var serverChild = viewCache.serverCache
9400 .getNode()
9401 .getImmediateChild(childKey);
9402 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childMergeTree);
9403 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9404 }
9405 });
9406 return curViewCache;
9407}
9408function viewProcessorAckUserWrite(viewProcessor, viewCache, ackPath, affectedTree, writesCache, completeCache, accumulator) {
9409 if (writeTreeRefShadowingWrite(writesCache, ackPath) != null) {
9410 return viewCache;
9411 }
9412 // Only filter server node if it is currently filtered
9413 var filterServerNode = viewCache.serverCache.isFiltered();
9414 // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
9415 // now that it won't be shadowed.
9416 var serverCache = viewCache.serverCache;
9417 if (affectedTree.value != null) {
9418 // This is an overwrite.
9419 if ((pathIsEmpty(ackPath) && serverCache.isFullyInitialized()) ||
9420 serverCache.isCompleteForPath(ackPath)) {
9421 return viewProcessorApplyServerOverwrite(viewProcessor, viewCache, ackPath, serverCache.getNode().getChild(ackPath), writesCache, completeCache, filterServerNode, accumulator);
9422 }
9423 else if (pathIsEmpty(ackPath)) {
9424 // This is a goofy edge case where we are acking data at this location but don't have full data. We
9425 // should just re-apply whatever we have in our cache as a merge.
9426 var changedChildren_1 = new ImmutableTree(null);
9427 serverCache.getNode().forEachChild(KEY_INDEX, function (name, node) {
9428 changedChildren_1 = changedChildren_1.set(new Path(name), node);
9429 });
9430 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_1, writesCache, completeCache, filterServerNode, accumulator);
9431 }
9432 else {
9433 return viewCache;
9434 }
9435 }
9436 else {
9437 // This is a merge.
9438 var changedChildren_2 = new ImmutableTree(null);
9439 affectedTree.foreach(function (mergePath, value) {
9440 var serverCachePath = pathChild(ackPath, mergePath);
9441 if (serverCache.isCompleteForPath(serverCachePath)) {
9442 changedChildren_2 = changedChildren_2.set(mergePath, serverCache.getNode().getChild(serverCachePath));
9443 }
9444 });
9445 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_2, writesCache, completeCache, filterServerNode, accumulator);
9446 }
9447}
9448function viewProcessorListenComplete(viewProcessor, viewCache, path, writesCache, accumulator) {
9449 var oldServerNode = viewCache.serverCache;
9450 var newViewCache = viewCacheUpdateServerSnap(viewCache, oldServerNode.getNode(), oldServerNode.isFullyInitialized() || pathIsEmpty(path), oldServerNode.isFiltered());
9451 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, path, writesCache, NO_COMPLETE_CHILD_SOURCE, accumulator);
9452}
9453function viewProcessorRevertUserWrite(viewProcessor, viewCache, path, writesCache, completeServerCache, accumulator) {
9454 var complete;
9455 if (writeTreeRefShadowingWrite(writesCache, path) != null) {
9456 return viewCache;
9457 }
9458 else {
9459 var source = new WriteTreeCompleteChildSource(writesCache, viewCache, completeServerCache);
9460 var oldEventCache = viewCache.eventCache.getNode();
9461 var newEventCache = void 0;
9462 if (pathIsEmpty(path) || pathGetFront(path) === '.priority') {
9463 var newNode = void 0;
9464 if (viewCache.serverCache.isFullyInitialized()) {
9465 newNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9466 }
9467 else {
9468 var serverChildren = viewCache.serverCache.getNode();
9469 assert(serverChildren instanceof ChildrenNode, 'serverChildren would be complete if leaf node');
9470 newNode = writeTreeRefCalcCompleteEventChildren(writesCache, serverChildren);
9471 }
9472 newNode = newNode;
9473 newEventCache = viewProcessor.filter.updateFullNode(oldEventCache, newNode, accumulator);
9474 }
9475 else {
9476 var childKey = pathGetFront(path);
9477 var newChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9478 if (newChild == null &&
9479 viewCache.serverCache.isCompleteForChild(childKey)) {
9480 newChild = oldEventCache.getImmediateChild(childKey);
9481 }
9482 if (newChild != null) {
9483 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, newChild, pathPopFront(path), source, accumulator);
9484 }
9485 else if (viewCache.eventCache.getNode().hasChild(childKey)) {
9486 // No complete child available, delete the existing one, if any
9487 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, ChildrenNode.EMPTY_NODE, pathPopFront(path), source, accumulator);
9488 }
9489 else {
9490 newEventCache = oldEventCache;
9491 }
9492 if (newEventCache.isEmpty() &&
9493 viewCache.serverCache.isFullyInitialized()) {
9494 // We might have reverted all child writes. Maybe the old event was a leaf node
9495 complete = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9496 if (complete.isLeafNode()) {
9497 newEventCache = viewProcessor.filter.updateFullNode(newEventCache, complete, accumulator);
9498 }
9499 }
9500 }
9501 complete =
9502 viewCache.serverCache.isFullyInitialized() ||
9503 writeTreeRefShadowingWrite(writesCache, newEmptyPath()) != null;
9504 return viewCacheUpdateEventSnap(viewCache, newEventCache, complete, viewProcessor.filter.filtersNodes());
9505 }
9506}
9507
9508/**
9509 * @license
9510 * Copyright 2017 Google LLC
9511 *
9512 * Licensed under the Apache License, Version 2.0 (the "License");
9513 * you may not use this file except in compliance with the License.
9514 * You may obtain a copy of the License at
9515 *
9516 * http://www.apache.org/licenses/LICENSE-2.0
9517 *
9518 * Unless required by applicable law or agreed to in writing, software
9519 * distributed under the License is distributed on an "AS IS" BASIS,
9520 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9521 * See the License for the specific language governing permissions and
9522 * limitations under the License.
9523 */
9524/**
9525 * A view represents a specific location and query that has 1 or more event registrations.
9526 *
9527 * It does several things:
9528 * - Maintains the list of event registrations for this location/query.
9529 * - Maintains a cache of the data visible for this location/query.
9530 * - Applies new operations (via applyOperation), updates the cache, and based on the event
9531 * registrations returns the set of events to be raised.
9532 */
9533var View = /** @class */ (function () {
9534 function View(query_, initialViewCache) {
9535 this.query_ = query_;
9536 this.eventRegistrations_ = [];
9537 var params = this.query_._queryParams;
9538 var indexFilter = new IndexedFilter(params.getIndex());
9539 var filter = queryParamsGetNodeFilter(params);
9540 this.processor_ = newViewProcessor(filter);
9541 var initialServerCache = initialViewCache.serverCache;
9542 var initialEventCache = initialViewCache.eventCache;
9543 // Don't filter server node with other filter than index, wait for tagged listen
9544 var serverSnap = indexFilter.updateFullNode(ChildrenNode.EMPTY_NODE, initialServerCache.getNode(), null);
9545 var eventSnap = filter.updateFullNode(ChildrenNode.EMPTY_NODE, initialEventCache.getNode(), null);
9546 var newServerCache = new CacheNode(serverSnap, initialServerCache.isFullyInitialized(), indexFilter.filtersNodes());
9547 var newEventCache = new CacheNode(eventSnap, initialEventCache.isFullyInitialized(), filter.filtersNodes());
9548 this.viewCache_ = newViewCache(newEventCache, newServerCache);
9549 this.eventGenerator_ = new EventGenerator(this.query_);
9550 }
9551 Object.defineProperty(View.prototype, "query", {
9552 get: function () {
9553 return this.query_;
9554 },
9555 enumerable: false,
9556 configurable: true
9557 });
9558 return View;
9559}());
9560function viewGetServerCache(view) {
9561 return view.viewCache_.serverCache.getNode();
9562}
9563function viewGetCompleteNode(view) {
9564 return viewCacheGetCompleteEventSnap(view.viewCache_);
9565}
9566function viewGetCompleteServerCache(view, path) {
9567 var cache = viewCacheGetCompleteServerSnap(view.viewCache_);
9568 if (cache) {
9569 // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
9570 // we need to see if it contains the child we're interested in.
9571 if (view.query._queryParams.loadsAllData() ||
9572 (!pathIsEmpty(path) &&
9573 !cache.getImmediateChild(pathGetFront(path)).isEmpty())) {
9574 return cache.getChild(path);
9575 }
9576 }
9577 return null;
9578}
9579function viewIsEmpty(view) {
9580 return view.eventRegistrations_.length === 0;
9581}
9582function viewAddEventRegistration(view, eventRegistration) {
9583 view.eventRegistrations_.push(eventRegistration);
9584}
9585/**
9586 * @param eventRegistration - If null, remove all callbacks.
9587 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9588 * @returns Cancel events, if cancelError was provided.
9589 */
9590function viewRemoveEventRegistration(view, eventRegistration, cancelError) {
9591 var cancelEvents = [];
9592 if (cancelError) {
9593 assert(eventRegistration == null, 'A cancel should cancel all event registrations.');
9594 var path_1 = view.query._path;
9595 view.eventRegistrations_.forEach(function (registration) {
9596 var maybeEvent = registration.createCancelEvent(cancelError, path_1);
9597 if (maybeEvent) {
9598 cancelEvents.push(maybeEvent);
9599 }
9600 });
9601 }
9602 if (eventRegistration) {
9603 var remaining = [];
9604 for (var i = 0; i < view.eventRegistrations_.length; ++i) {
9605 var existing = view.eventRegistrations_[i];
9606 if (!existing.matches(eventRegistration)) {
9607 remaining.push(existing);
9608 }
9609 else if (eventRegistration.hasAnyCallback()) {
9610 // We're removing just this one
9611 remaining = remaining.concat(view.eventRegistrations_.slice(i + 1));
9612 break;
9613 }
9614 }
9615 view.eventRegistrations_ = remaining;
9616 }
9617 else {
9618 view.eventRegistrations_ = [];
9619 }
9620 return cancelEvents;
9621}
9622/**
9623 * Applies the given Operation, updates our cache, and returns the appropriate events.
9624 */
9625function viewApplyOperation(view, operation, writesCache, completeServerCache) {
9626 if (operation.type === OperationType.MERGE &&
9627 operation.source.queryId !== null) {
9628 assert(viewCacheGetCompleteServerSnap(view.viewCache_), 'We should always have a full cache before handling merges');
9629 assert(viewCacheGetCompleteEventSnap(view.viewCache_), 'Missing event cache, even though we have a server cache');
9630 }
9631 var oldViewCache = view.viewCache_;
9632 var result = viewProcessorApplyOperation(view.processor_, oldViewCache, operation, writesCache, completeServerCache);
9633 viewProcessorAssertIndexed(view.processor_, result.viewCache);
9634 assert(result.viewCache.serverCache.isFullyInitialized() ||
9635 !oldViewCache.serverCache.isFullyInitialized(), 'Once a server snap is complete, it should never go back');
9636 view.viewCache_ = result.viewCache;
9637 return viewGenerateEventsForChanges_(view, result.changes, result.viewCache.eventCache.getNode(), null);
9638}
9639function viewGetInitialEvents(view, registration) {
9640 var eventSnap = view.viewCache_.eventCache;
9641 var initialChanges = [];
9642 if (!eventSnap.getNode().isLeafNode()) {
9643 var eventNode = eventSnap.getNode();
9644 eventNode.forEachChild(PRIORITY_INDEX, function (key, childNode) {
9645 initialChanges.push(changeChildAdded(key, childNode));
9646 });
9647 }
9648 if (eventSnap.isFullyInitialized()) {
9649 initialChanges.push(changeValue(eventSnap.getNode()));
9650 }
9651 return viewGenerateEventsForChanges_(view, initialChanges, eventSnap.getNode(), registration);
9652}
9653function viewGenerateEventsForChanges_(view, changes, eventCache, eventRegistration) {
9654 var registrations = eventRegistration
9655 ? [eventRegistration]
9656 : view.eventRegistrations_;
9657 return eventGeneratorGenerateEventsForChanges(view.eventGenerator_, changes, eventCache, registrations);
9658}
9659
9660/**
9661 * @license
9662 * Copyright 2017 Google LLC
9663 *
9664 * Licensed under the Apache License, Version 2.0 (the "License");
9665 * you may not use this file except in compliance with the License.
9666 * You may obtain a copy of the License at
9667 *
9668 * http://www.apache.org/licenses/LICENSE-2.0
9669 *
9670 * Unless required by applicable law or agreed to in writing, software
9671 * distributed under the License is distributed on an "AS IS" BASIS,
9672 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9673 * See the License for the specific language governing permissions and
9674 * limitations under the License.
9675 */
9676var referenceConstructor$1;
9677/**
9678 * SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
9679 * maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
9680 * and user writes (set, transaction, update).
9681 *
9682 * It's responsible for:
9683 * - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
9684 * - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
9685 * applyUserOverwrite, etc.)
9686 */
9687var SyncPoint = /** @class */ (function () {
9688 function SyncPoint() {
9689 /**
9690 * The Views being tracked at this location in the tree, stored as a map where the key is a
9691 * queryId and the value is the View for that query.
9692 *
9693 * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
9694 */
9695 this.views = new Map();
9696 }
9697 return SyncPoint;
9698}());
9699function syncPointSetReferenceConstructor(val) {
9700 assert(!referenceConstructor$1, '__referenceConstructor has already been defined');
9701 referenceConstructor$1 = val;
9702}
9703function syncPointGetReferenceConstructor() {
9704 assert(referenceConstructor$1, 'Reference.ts has not been loaded');
9705 return referenceConstructor$1;
9706}
9707function syncPointIsEmpty(syncPoint) {
9708 return syncPoint.views.size === 0;
9709}
9710function syncPointApplyOperation(syncPoint, operation, writesCache, optCompleteServerCache) {
9711 var e_1, _a;
9712 var queryId = operation.source.queryId;
9713 if (queryId !== null) {
9714 var view = syncPoint.views.get(queryId);
9715 assert(view != null, 'SyncTree gave us an op for an invalid query.');
9716 return viewApplyOperation(view, operation, writesCache, optCompleteServerCache);
9717 }
9718 else {
9719 var events = [];
9720 try {
9721 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9722 var view = _c.value;
9723 events = events.concat(viewApplyOperation(view, operation, writesCache, optCompleteServerCache));
9724 }
9725 }
9726 catch (e_1_1) { e_1 = { error: e_1_1 }; }
9727 finally {
9728 try {
9729 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9730 }
9731 finally { if (e_1) throw e_1.error; }
9732 }
9733 return events;
9734 }
9735}
9736/**
9737 * Get a view for the specified query.
9738 *
9739 * @param query - The query to return a view for
9740 * @param writesCache
9741 * @param serverCache
9742 * @param serverCacheComplete
9743 * @returns Events to raise.
9744 */
9745function syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete) {
9746 var queryId = query._queryIdentifier;
9747 var view = syncPoint.views.get(queryId);
9748 if (!view) {
9749 // TODO: make writesCache take flag for complete server node
9750 var eventCache = writeTreeRefCalcCompleteEventCache(writesCache, serverCacheComplete ? serverCache : null);
9751 var eventCacheComplete = false;
9752 if (eventCache) {
9753 eventCacheComplete = true;
9754 }
9755 else if (serverCache instanceof ChildrenNode) {
9756 eventCache = writeTreeRefCalcCompleteEventChildren(writesCache, serverCache);
9757 eventCacheComplete = false;
9758 }
9759 else {
9760 eventCache = ChildrenNode.EMPTY_NODE;
9761 eventCacheComplete = false;
9762 }
9763 var viewCache = newViewCache(new CacheNode(eventCache, eventCacheComplete, false), new CacheNode(serverCache, serverCacheComplete, false));
9764 return new View(query, viewCache);
9765 }
9766 return view;
9767}
9768/**
9769 * Add an event callback for the specified query.
9770 *
9771 * @param query
9772 * @param eventRegistration
9773 * @param writesCache
9774 * @param serverCache - Complete server cache, if we have it.
9775 * @param serverCacheComplete
9776 * @returns Events to raise.
9777 */
9778function syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete) {
9779 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9780 if (!syncPoint.views.has(query._queryIdentifier)) {
9781 syncPoint.views.set(query._queryIdentifier, view);
9782 }
9783 // This is guaranteed to exist now, we just created anything that was missing
9784 viewAddEventRegistration(view, eventRegistration);
9785 return viewGetInitialEvents(view, eventRegistration);
9786}
9787/**
9788 * Remove event callback(s). Return cancelEvents if a cancelError is specified.
9789 *
9790 * If query is the default query, we'll check all views for the specified eventRegistration.
9791 * If eventRegistration is null, we'll remove all callbacks for the specified view(s).
9792 *
9793 * @param eventRegistration - If null, remove all callbacks.
9794 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9795 * @returns removed queries and any cancel events
9796 */
9797function syncPointRemoveEventRegistration(syncPoint, query, eventRegistration, cancelError) {
9798 var e_2, _a;
9799 var queryId = query._queryIdentifier;
9800 var removed = [];
9801 var cancelEvents = [];
9802 var hadCompleteView = syncPointHasCompleteView(syncPoint);
9803 if (queryId === 'default') {
9804 try {
9805 // When you do ref.off(...), we search all views for the registration to remove.
9806 for (var _b = __values(syncPoint.views.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
9807 var _d = __read(_c.value, 2), viewQueryId = _d[0], view = _d[1];
9808 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9809 if (viewIsEmpty(view)) {
9810 syncPoint.views.delete(viewQueryId);
9811 // We'll deal with complete views later.
9812 if (!view.query._queryParams.loadsAllData()) {
9813 removed.push(view.query);
9814 }
9815 }
9816 }
9817 }
9818 catch (e_2_1) { e_2 = { error: e_2_1 }; }
9819 finally {
9820 try {
9821 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9822 }
9823 finally { if (e_2) throw e_2.error; }
9824 }
9825 }
9826 else {
9827 // remove the callback from the specific view.
9828 var view = syncPoint.views.get(queryId);
9829 if (view) {
9830 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9831 if (viewIsEmpty(view)) {
9832 syncPoint.views.delete(queryId);
9833 // We'll deal with complete views later.
9834 if (!view.query._queryParams.loadsAllData()) {
9835 removed.push(view.query);
9836 }
9837 }
9838 }
9839 }
9840 if (hadCompleteView && !syncPointHasCompleteView(syncPoint)) {
9841 // We removed our last complete view.
9842 removed.push(new (syncPointGetReferenceConstructor())(query._repo, query._path));
9843 }
9844 return { removed: removed, events: cancelEvents };
9845}
9846function syncPointGetQueryViews(syncPoint) {
9847 var e_3, _a;
9848 var result = [];
9849 try {
9850 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9851 var view = _c.value;
9852 if (!view.query._queryParams.loadsAllData()) {
9853 result.push(view);
9854 }
9855 }
9856 }
9857 catch (e_3_1) { e_3 = { error: e_3_1 }; }
9858 finally {
9859 try {
9860 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9861 }
9862 finally { if (e_3) throw e_3.error; }
9863 }
9864 return result;
9865}
9866/**
9867 * @param path - The path to the desired complete snapshot
9868 * @returns A complete cache, if it exists
9869 */
9870function syncPointGetCompleteServerCache(syncPoint, path) {
9871 var e_4, _a;
9872 var serverCache = null;
9873 try {
9874 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9875 var view = _c.value;
9876 serverCache = serverCache || viewGetCompleteServerCache(view, path);
9877 }
9878 }
9879 catch (e_4_1) { e_4 = { error: e_4_1 }; }
9880 finally {
9881 try {
9882 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9883 }
9884 finally { if (e_4) throw e_4.error; }
9885 }
9886 return serverCache;
9887}
9888function syncPointViewForQuery(syncPoint, query) {
9889 var params = query._queryParams;
9890 if (params.loadsAllData()) {
9891 return syncPointGetCompleteView(syncPoint);
9892 }
9893 else {
9894 var queryId = query._queryIdentifier;
9895 return syncPoint.views.get(queryId);
9896 }
9897}
9898function syncPointViewExistsForQuery(syncPoint, query) {
9899 return syncPointViewForQuery(syncPoint, query) != null;
9900}
9901function syncPointHasCompleteView(syncPoint) {
9902 return syncPointGetCompleteView(syncPoint) != null;
9903}
9904function syncPointGetCompleteView(syncPoint) {
9905 var e_5, _a;
9906 try {
9907 for (var _b = __values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9908 var view = _c.value;
9909 if (view.query._queryParams.loadsAllData()) {
9910 return view;
9911 }
9912 }
9913 }
9914 catch (e_5_1) { e_5 = { error: e_5_1 }; }
9915 finally {
9916 try {
9917 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9918 }
9919 finally { if (e_5) throw e_5.error; }
9920 }
9921 return null;
9922}
9923
9924/**
9925 * @license
9926 * Copyright 2017 Google LLC
9927 *
9928 * Licensed under the Apache License, Version 2.0 (the "License");
9929 * you may not use this file except in compliance with the License.
9930 * You may obtain a copy of the License at
9931 *
9932 * http://www.apache.org/licenses/LICENSE-2.0
9933 *
9934 * Unless required by applicable law or agreed to in writing, software
9935 * distributed under the License is distributed on an "AS IS" BASIS,
9936 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9937 * See the License for the specific language governing permissions and
9938 * limitations under the License.
9939 */
9940var referenceConstructor;
9941function syncTreeSetReferenceConstructor(val) {
9942 assert(!referenceConstructor, '__referenceConstructor has already been defined');
9943 referenceConstructor = val;
9944}
9945function syncTreeGetReferenceConstructor() {
9946 assert(referenceConstructor, 'Reference.ts has not been loaded');
9947 return referenceConstructor;
9948}
9949/**
9950 * Static tracker for next query tag.
9951 */
9952var syncTreeNextQueryTag_ = 1;
9953/**
9954 * SyncTree is the central class for managing event callback registration, data caching, views
9955 * (query processing), and event generation. There are typically two SyncTree instances for
9956 * each Repo, one for the normal Firebase data, and one for the .info data.
9957 *
9958 * It has a number of responsibilities, including:
9959 * - Tracking all user event callbacks (registered via addEventRegistration() and removeEventRegistration()).
9960 * - Applying and caching data changes for user set(), transaction(), and update() calls
9961 * (applyUserOverwrite(), applyUserMerge()).
9962 * - Applying and caching data changes for server data changes (applyServerOverwrite(),
9963 * applyServerMerge()).
9964 * - Generating user-facing events for server and user changes (all of the apply* methods
9965 * return the set of events that need to be raised as a result).
9966 * - Maintaining the appropriate set of server listens to ensure we are always subscribed
9967 * to the correct set of paths and queries to satisfy the current set of user event
9968 * callbacks (listens are started/stopped using the provided listenProvider).
9969 *
9970 * NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
9971 * events are returned to the caller rather than raised synchronously.
9972 *
9973 */
9974var SyncTree = /** @class */ (function () {
9975 /**
9976 * @param listenProvider_ - Used by SyncTree to start / stop listening
9977 * to server data.
9978 */
9979 function SyncTree(listenProvider_) {
9980 this.listenProvider_ = listenProvider_;
9981 /**
9982 * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
9983 */
9984 this.syncPointTree_ = new ImmutableTree(null);
9985 /**
9986 * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
9987 */
9988 this.pendingWriteTree_ = newWriteTree();
9989 this.tagToQueryMap = new Map();
9990 this.queryToTagMap = new Map();
9991 }
9992 return SyncTree;
9993}());
9994/**
9995 * Apply the data changes for a user-generated set() or transaction() call.
9996 *
9997 * @returns Events to raise.
9998 */
9999function syncTreeApplyUserOverwrite(syncTree, path, newData, writeId, visible) {
10000 // Record pending write.
10001 writeTreeAddOverwrite(syncTree.pendingWriteTree_, path, newData, writeId, visible);
10002 if (!visible) {
10003 return [];
10004 }
10005 else {
10006 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceUser(), path, newData));
10007 }
10008}
10009/**
10010 * Apply the data from a user-generated update() call
10011 *
10012 * @returns Events to raise.
10013 */
10014function syncTreeApplyUserMerge(syncTree, path, changedChildren, writeId) {
10015 // Record pending merge.
10016 writeTreeAddMerge(syncTree.pendingWriteTree_, path, changedChildren, writeId);
10017 var changeTree = ImmutableTree.fromObject(changedChildren);
10018 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceUser(), path, changeTree));
10019}
10020/**
10021 * Acknowledge a pending user write that was previously registered with applyUserOverwrite() or applyUserMerge().
10022 *
10023 * @param revert - True if the given write failed and needs to be reverted
10024 * @returns Events to raise.
10025 */
10026function syncTreeAckUserWrite(syncTree, writeId, revert) {
10027 if (revert === void 0) { revert = false; }
10028 var write = writeTreeGetWrite(syncTree.pendingWriteTree_, writeId);
10029 var needToReevaluate = writeTreeRemoveWrite(syncTree.pendingWriteTree_, writeId);
10030 if (!needToReevaluate) {
10031 return [];
10032 }
10033 else {
10034 var affectedTree_1 = new ImmutableTree(null);
10035 if (write.snap != null) {
10036 // overwrite
10037 affectedTree_1 = affectedTree_1.set(newEmptyPath(), true);
10038 }
10039 else {
10040 each(write.children, function (pathString) {
10041 affectedTree_1 = affectedTree_1.set(new Path(pathString), true);
10042 });
10043 }
10044 return syncTreeApplyOperationToSyncPoints_(syncTree, new AckUserWrite(write.path, affectedTree_1, revert));
10045 }
10046}
10047/**
10048 * Apply new server data for the specified path..
10049 *
10050 * @returns Events to raise.
10051 */
10052function syncTreeApplyServerOverwrite(syncTree, path, newData) {
10053 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceServer(), path, newData));
10054}
10055/**
10056 * Apply new server data to be merged in at the specified path.
10057 *
10058 * @returns Events to raise.
10059 */
10060function syncTreeApplyServerMerge(syncTree, path, changedChildren) {
10061 var changeTree = ImmutableTree.fromObject(changedChildren);
10062 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceServer(), path, changeTree));
10063}
10064/**
10065 * Apply a listen complete for a query
10066 *
10067 * @returns Events to raise.
10068 */
10069function syncTreeApplyListenComplete(syncTree, path) {
10070 return syncTreeApplyOperationToSyncPoints_(syncTree, new ListenComplete(newOperationSourceServer(), path));
10071}
10072/**
10073 * Apply a listen complete for a tagged query
10074 *
10075 * @returns Events to raise.
10076 */
10077function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
10078 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10079 if (queryKey) {
10080 var r = syncTreeParseQueryKey_(queryKey);
10081 var queryPath = r.path, queryId = r.queryId;
10082 var relativePath = newRelativePath(queryPath, path);
10083 var op = new ListenComplete(newOperationSourceServerTaggedQuery(queryId), relativePath);
10084 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10085 }
10086 else {
10087 // We've already removed the query. No big deal, ignore the update
10088 return [];
10089 }
10090}
10091/**
10092 * Remove event callback(s).
10093 *
10094 * If query is the default query, we'll check all queries for the specified eventRegistration.
10095 * If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
10096 *
10097 * @param eventRegistration - If null, all callbacks are removed.
10098 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
10099 * @returns Cancel events, if cancelError was provided.
10100 */
10101function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError) {
10102 // Find the syncPoint first. Then deal with whether or not it has matching listeners
10103 var path = query._path;
10104 var maybeSyncPoint = syncTree.syncPointTree_.get(path);
10105 var cancelEvents = [];
10106 // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
10107 // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
10108 // not loadsAllData().
10109 if (maybeSyncPoint &&
10110 (query._queryIdentifier === 'default' ||
10111 syncPointViewExistsForQuery(maybeSyncPoint, query))) {
10112 var removedAndEvents = syncPointRemoveEventRegistration(maybeSyncPoint, query, eventRegistration, cancelError);
10113 if (syncPointIsEmpty(maybeSyncPoint)) {
10114 syncTree.syncPointTree_ = syncTree.syncPointTree_.remove(path);
10115 }
10116 var removed = removedAndEvents.removed;
10117 cancelEvents = removedAndEvents.events;
10118 // We may have just removed one of many listeners and can short-circuit this whole process
10119 // We may also not have removed a default listener, in which case all of the descendant listeners should already be
10120 // properly set up.
10121 //
10122 // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
10123 // queryId === 'default'
10124 var removingDefault = -1 !==
10125 removed.findIndex(function (query) {
10126 return query._queryParams.loadsAllData();
10127 });
10128 var covered = syncTree.syncPointTree_.findOnPath(path, function (relativePath, parentSyncPoint) {
10129 return syncPointHasCompleteView(parentSyncPoint);
10130 });
10131 if (removingDefault && !covered) {
10132 var subtree = syncTree.syncPointTree_.subtree(path);
10133 // There are potentially child listeners. Determine what if any listens we need to send before executing the
10134 // removal
10135 if (!subtree.isEmpty()) {
10136 // We need to fold over our subtree and collect the listeners to send
10137 var newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
10138 // Ok, we've collected all the listens we need. Set them up.
10139 for (var i = 0; i < newViews.length; ++i) {
10140 var view = newViews[i], newQuery = view.query;
10141 var listener = syncTreeCreateListenerForView_(syncTree, view);
10142 syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery_(syncTree, newQuery), listener.hashFn, listener.onComplete);
10143 }
10144 }
10145 }
10146 // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
10147 // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
10148 // Also, note that if we have a cancelError, it's already been removed at the provider level.
10149 if (!covered && removed.length > 0 && !cancelError) {
10150 // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
10151 // default. Otherwise, we need to iterate through and cancel each individual query
10152 if (removingDefault) {
10153 // We don't tag default listeners
10154 var defaultTag = null;
10155 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
10156 }
10157 else {
10158 removed.forEach(function (queryToRemove) {
10159 var tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
10160 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
10161 });
10162 }
10163 }
10164 // Now, clear all of the tags we're tracking for the removed listens
10165 syncTreeRemoveTags_(syncTree, removed);
10166 }
10167 return cancelEvents;
10168}
10169/**
10170 * Apply new server data for the specified tagged query.
10171 *
10172 * @returns Events to raise.
10173 */
10174function syncTreeApplyTaggedQueryOverwrite(syncTree, path, snap, tag) {
10175 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10176 if (queryKey != null) {
10177 var r = syncTreeParseQueryKey_(queryKey);
10178 var queryPath = r.path, queryId = r.queryId;
10179 var relativePath = newRelativePath(queryPath, path);
10180 var op = new Overwrite(newOperationSourceServerTaggedQuery(queryId), relativePath, snap);
10181 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10182 }
10183 else {
10184 // Query must have been removed already
10185 return [];
10186 }
10187}
10188/**
10189 * Apply server data to be merged in for the specified tagged query.
10190 *
10191 * @returns Events to raise.
10192 */
10193function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
10194 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10195 if (queryKey) {
10196 var r = syncTreeParseQueryKey_(queryKey);
10197 var queryPath = r.path, queryId = r.queryId;
10198 var relativePath = newRelativePath(queryPath, path);
10199 var changeTree = ImmutableTree.fromObject(changedChildren);
10200 var op = new Merge(newOperationSourceServerTaggedQuery(queryId), relativePath, changeTree);
10201 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10202 }
10203 else {
10204 // We've already removed the query. No big deal, ignore the update
10205 return [];
10206 }
10207}
10208/**
10209 * Add an event callback for the specified query.
10210 *
10211 * @returns Events to raise.
10212 */
10213function syncTreeAddEventRegistration(syncTree, query, eventRegistration) {
10214 var path = query._path;
10215 var serverCache = null;
10216 var foundAncestorDefaultView = false;
10217 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10218 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10219 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10220 var relativePath = newRelativePath(pathToSyncPoint, path);
10221 serverCache =
10222 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10223 foundAncestorDefaultView =
10224 foundAncestorDefaultView || syncPointHasCompleteView(sp);
10225 });
10226 var syncPoint = syncTree.syncPointTree_.get(path);
10227 if (!syncPoint) {
10228 syncPoint = new SyncPoint();
10229 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10230 }
10231 else {
10232 foundAncestorDefaultView =
10233 foundAncestorDefaultView || syncPointHasCompleteView(syncPoint);
10234 serverCache =
10235 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10236 }
10237 var serverCacheComplete;
10238 if (serverCache != null) {
10239 serverCacheComplete = true;
10240 }
10241 else {
10242 serverCacheComplete = false;
10243 serverCache = ChildrenNode.EMPTY_NODE;
10244 var subtree = syncTree.syncPointTree_.subtree(path);
10245 subtree.foreachChild(function (childName, childSyncPoint) {
10246 var completeCache = syncPointGetCompleteServerCache(childSyncPoint, newEmptyPath());
10247 if (completeCache) {
10248 serverCache = serverCache.updateImmediateChild(childName, completeCache);
10249 }
10250 });
10251 }
10252 var viewAlreadyExists = syncPointViewExistsForQuery(syncPoint, query);
10253 if (!viewAlreadyExists && !query._queryParams.loadsAllData()) {
10254 // We need to track a tag for this query
10255 var queryKey = syncTreeMakeQueryKey_(query);
10256 assert(!syncTree.queryToTagMap.has(queryKey), 'View does not exist, but we have a tag');
10257 var tag = syncTreeGetNextQueryTag_();
10258 syncTree.queryToTagMap.set(queryKey, tag);
10259 syncTree.tagToQueryMap.set(tag, queryKey);
10260 }
10261 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
10262 var events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
10263 if (!viewAlreadyExists && !foundAncestorDefaultView) {
10264 var view = syncPointViewForQuery(syncPoint, query);
10265 events = events.concat(syncTreeSetupListener_(syncTree, query, view));
10266 }
10267 return events;
10268}
10269/**
10270 * Returns a complete cache, if we have one, of the data at a particular path. If the location does not have a
10271 * listener above it, we will get a false "null". This shouldn't be a problem because transactions will always
10272 * have a listener above, and atomic operations would correctly show a jitter of <increment value> ->
10273 * <incremented total> as the write is applied locally and then acknowledged at the server.
10274 *
10275 * Note: this method will *include* hidden writes from transaction with applyLocally set to false.
10276 *
10277 * @param path - The path to the data we want
10278 * @param writeIdsToExclude - A specific set to be excluded
10279 */
10280function syncTreeCalcCompleteEventCache(syncTree, path, writeIdsToExclude) {
10281 var includeHiddenSets = true;
10282 var writeTree = syncTree.pendingWriteTree_;
10283 var serverCache = syncTree.syncPointTree_.findOnPath(path, function (pathSoFar, syncPoint) {
10284 var relativePath = newRelativePath(pathSoFar, path);
10285 var serverCache = syncPointGetCompleteServerCache(syncPoint, relativePath);
10286 if (serverCache) {
10287 return serverCache;
10288 }
10289 });
10290 return writeTreeCalcCompleteEventCache(writeTree, path, serverCache, writeIdsToExclude, includeHiddenSets);
10291}
10292function syncTreeGetServerValue(syncTree, query) {
10293 var path = query._path;
10294 var serverCache = null;
10295 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10296 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10297 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10298 var relativePath = newRelativePath(pathToSyncPoint, path);
10299 serverCache =
10300 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10301 });
10302 var syncPoint = syncTree.syncPointTree_.get(path);
10303 if (!syncPoint) {
10304 syncPoint = new SyncPoint();
10305 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10306 }
10307 else {
10308 serverCache =
10309 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10310 }
10311 var serverCacheComplete = serverCache != null;
10312 var serverCacheNode = serverCacheComplete
10313 ? new CacheNode(serverCache, true, false)
10314 : null;
10315 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, query._path);
10316 var view = syncPointGetView(syncPoint, query, writesCache, serverCacheComplete ? serverCacheNode.getNode() : ChildrenNode.EMPTY_NODE, serverCacheComplete);
10317 return viewGetCompleteNode(view);
10318}
10319/**
10320 * A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
10321 *
10322 * NOTES:
10323 * - Descendant SyncPoints will be visited first (since we raise events depth-first).
10324 *
10325 * - We call applyOperation() on each SyncPoint passing three things:
10326 * 1. A version of the Operation that has been made relative to the SyncPoint location.
10327 * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
10328 * 3. A snapshot Node with cached server data, if we have it.
10329 *
10330 * - We concatenate all of the events returned by each SyncPoint and return the result.
10331 */
10332function syncTreeApplyOperationToSyncPoints_(syncTree, operation) {
10333 return syncTreeApplyOperationHelper_(operation, syncTree.syncPointTree_,
10334 /*serverCache=*/ null, writeTreeChildWrites(syncTree.pendingWriteTree_, newEmptyPath()));
10335}
10336/**
10337 * Recursive helper for applyOperationToSyncPoints_
10338 */
10339function syncTreeApplyOperationHelper_(operation, syncPointTree, serverCache, writesCache) {
10340 if (pathIsEmpty(operation.path)) {
10341 return syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache);
10342 }
10343 else {
10344 var syncPoint = syncPointTree.get(newEmptyPath());
10345 // If we don't have cached server data, see if we can get it from this SyncPoint.
10346 if (serverCache == null && syncPoint != null) {
10347 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10348 }
10349 var events = [];
10350 var childName = pathGetFront(operation.path);
10351 var childOperation = operation.operationForChild(childName);
10352 var childTree = syncPointTree.children.get(childName);
10353 if (childTree && childOperation) {
10354 var childServerCache = serverCache
10355 ? serverCache.getImmediateChild(childName)
10356 : null;
10357 var childWritesCache = writeTreeRefChild(writesCache, childName);
10358 events = events.concat(syncTreeApplyOperationHelper_(childOperation, childTree, childServerCache, childWritesCache));
10359 }
10360 if (syncPoint) {
10361 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10362 }
10363 return events;
10364 }
10365}
10366/**
10367 * Recursive helper for applyOperationToSyncPoints_
10368 */
10369function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache) {
10370 var syncPoint = syncPointTree.get(newEmptyPath());
10371 // If we don't have cached server data, see if we can get it from this SyncPoint.
10372 if (serverCache == null && syncPoint != null) {
10373 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10374 }
10375 var events = [];
10376 syncPointTree.children.inorderTraversal(function (childName, childTree) {
10377 var childServerCache = serverCache
10378 ? serverCache.getImmediateChild(childName)
10379 : null;
10380 var childWritesCache = writeTreeRefChild(writesCache, childName);
10381 var childOperation = operation.operationForChild(childName);
10382 if (childOperation) {
10383 events = events.concat(syncTreeApplyOperationDescendantsHelper_(childOperation, childTree, childServerCache, childWritesCache));
10384 }
10385 });
10386 if (syncPoint) {
10387 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10388 }
10389 return events;
10390}
10391function syncTreeCreateListenerForView_(syncTree, view) {
10392 var query = view.query;
10393 var tag = syncTreeTagForQuery_(syncTree, query);
10394 return {
10395 hashFn: function () {
10396 var cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
10397 return cache.hash();
10398 },
10399 onComplete: function (status) {
10400 if (status === 'ok') {
10401 if (tag) {
10402 return syncTreeApplyTaggedListenComplete(syncTree, query._path, tag);
10403 }
10404 else {
10405 return syncTreeApplyListenComplete(syncTree, query._path);
10406 }
10407 }
10408 else {
10409 // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
10410 // Note that this may need to be scoped to just this listener if we change permissions on filtered children
10411 var error = errorForServerCode(status, query);
10412 return syncTreeRemoveEventRegistration(syncTree, query,
10413 /*eventRegistration*/ null, error);
10414 }
10415 }
10416 };
10417}
10418/**
10419 * Return the tag associated with the given query.
10420 */
10421function syncTreeTagForQuery_(syncTree, query) {
10422 var queryKey = syncTreeMakeQueryKey_(query);
10423 return syncTree.queryToTagMap.get(queryKey);
10424}
10425/**
10426 * Given a query, computes a "queryKey" suitable for use in our queryToTagMap_.
10427 */
10428function syncTreeMakeQueryKey_(query) {
10429 return query._path.toString() + '$' + query._queryIdentifier;
10430}
10431/**
10432 * Return the query associated with the given tag, if we have one
10433 */
10434function syncTreeQueryKeyForTag_(syncTree, tag) {
10435 return syncTree.tagToQueryMap.get(tag);
10436}
10437/**
10438 * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId.
10439 */
10440function syncTreeParseQueryKey_(queryKey) {
10441 var splitIndex = queryKey.indexOf('$');
10442 assert(splitIndex !== -1 && splitIndex < queryKey.length - 1, 'Bad queryKey.');
10443 return {
10444 queryId: queryKey.substr(splitIndex + 1),
10445 path: new Path(queryKey.substr(0, splitIndex))
10446 };
10447}
10448/**
10449 * A helper method to apply tagged operations
10450 */
10451function syncTreeApplyTaggedOperation_(syncTree, queryPath, operation) {
10452 var syncPoint = syncTree.syncPointTree_.get(queryPath);
10453 assert(syncPoint, "Missing sync point for query tag that we're tracking");
10454 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, queryPath);
10455 return syncPointApplyOperation(syncPoint, operation, writesCache, null);
10456}
10457/**
10458 * This collapses multiple unfiltered views into a single view, since we only need a single
10459 * listener for them.
10460 */
10461function syncTreeCollectDistinctViewsForSubTree_(subtree) {
10462 return subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10463 if (maybeChildSyncPoint && syncPointHasCompleteView(maybeChildSyncPoint)) {
10464 var completeView = syncPointGetCompleteView(maybeChildSyncPoint);
10465 return [completeView];
10466 }
10467 else {
10468 // No complete view here, flatten any deeper listens into an array
10469 var views_1 = [];
10470 if (maybeChildSyncPoint) {
10471 views_1 = syncPointGetQueryViews(maybeChildSyncPoint);
10472 }
10473 each(childMap, function (_key, childViews) {
10474 views_1 = views_1.concat(childViews);
10475 });
10476 return views_1;
10477 }
10478 });
10479}
10480/**
10481 * Normalizes a query to a query we send the server for listening
10482 *
10483 * @returns The normalized query
10484 */
10485function syncTreeQueryForListening_(query) {
10486 if (query._queryParams.loadsAllData() && !query._queryParams.isDefault()) {
10487 // We treat queries that load all data as default queries
10488 // Cast is necessary because ref() technically returns Firebase which is actually fb.api.Firebase which inherits
10489 // from Query
10490 return new (syncTreeGetReferenceConstructor())(query._repo, query._path);
10491 }
10492 else {
10493 return query;
10494 }
10495}
10496function syncTreeRemoveTags_(syncTree, queries) {
10497 for (var j = 0; j < queries.length; ++j) {
10498 var removedQuery = queries[j];
10499 if (!removedQuery._queryParams.loadsAllData()) {
10500 // We should have a tag for this
10501 var removedQueryKey = syncTreeMakeQueryKey_(removedQuery);
10502 var removedQueryTag = syncTree.queryToTagMap.get(removedQueryKey);
10503 syncTree.queryToTagMap.delete(removedQueryKey);
10504 syncTree.tagToQueryMap.delete(removedQueryTag);
10505 }
10506 }
10507}
10508/**
10509 * Static accessor for query tags.
10510 */
10511function syncTreeGetNextQueryTag_() {
10512 return syncTreeNextQueryTag_++;
10513}
10514/**
10515 * For a given new listen, manage the de-duplication of outstanding subscriptions.
10516 *
10517 * @returns This method can return events to support synchronous data sources
10518 */
10519function syncTreeSetupListener_(syncTree, query, view) {
10520 var path = query._path;
10521 var tag = syncTreeTagForQuery_(syncTree, query);
10522 var listener = syncTreeCreateListenerForView_(syncTree, view);
10523 var events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10524 var subtree = syncTree.syncPointTree_.subtree(path);
10525 // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
10526 // may need to shadow other listens as well.
10527 if (tag) {
10528 assert(!syncPointHasCompleteView(subtree.value), "If we're adding a query, it shouldn't be shadowed");
10529 }
10530 else {
10531 // Shadow everything at or below this location, this is a default listener.
10532 var queriesToStop = subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10533 if (!pathIsEmpty(relativePath) &&
10534 maybeChildSyncPoint &&
10535 syncPointHasCompleteView(maybeChildSyncPoint)) {
10536 return [syncPointGetCompleteView(maybeChildSyncPoint).query];
10537 }
10538 else {
10539 // No default listener here, flatten any deeper queries into an array
10540 var queries_1 = [];
10541 if (maybeChildSyncPoint) {
10542 queries_1 = queries_1.concat(syncPointGetQueryViews(maybeChildSyncPoint).map(function (view) { return view.query; }));
10543 }
10544 each(childMap, function (_key, childQueries) {
10545 queries_1 = queries_1.concat(childQueries);
10546 });
10547 return queries_1;
10548 }
10549 });
10550 for (var i = 0; i < queriesToStop.length; ++i) {
10551 var queryToStop = queriesToStop[i];
10552 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery_(syncTree, queryToStop));
10553 }
10554 }
10555 return events;
10556}
10557
10558/**
10559 * @license
10560 * Copyright 2017 Google LLC
10561 *
10562 * Licensed under the Apache License, Version 2.0 (the "License");
10563 * you may not use this file except in compliance with the License.
10564 * You may obtain a copy of the License at
10565 *
10566 * http://www.apache.org/licenses/LICENSE-2.0
10567 *
10568 * Unless required by applicable law or agreed to in writing, software
10569 * distributed under the License is distributed on an "AS IS" BASIS,
10570 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10571 * See the License for the specific language governing permissions and
10572 * limitations under the License.
10573 */
10574var ExistingValueProvider = /** @class */ (function () {
10575 function ExistingValueProvider(node_) {
10576 this.node_ = node_;
10577 }
10578 ExistingValueProvider.prototype.getImmediateChild = function (childName) {
10579 var child = this.node_.getImmediateChild(childName);
10580 return new ExistingValueProvider(child);
10581 };
10582 ExistingValueProvider.prototype.node = function () {
10583 return this.node_;
10584 };
10585 return ExistingValueProvider;
10586}());
10587var DeferredValueProvider = /** @class */ (function () {
10588 function DeferredValueProvider(syncTree, path) {
10589 this.syncTree_ = syncTree;
10590 this.path_ = path;
10591 }
10592 DeferredValueProvider.prototype.getImmediateChild = function (childName) {
10593 var childPath = pathChild(this.path_, childName);
10594 return new DeferredValueProvider(this.syncTree_, childPath);
10595 };
10596 DeferredValueProvider.prototype.node = function () {
10597 return syncTreeCalcCompleteEventCache(this.syncTree_, this.path_);
10598 };
10599 return DeferredValueProvider;
10600}());
10601/**
10602 * Generate placeholders for deferred values.
10603 */
10604var generateWithValues = function (values) {
10605 values = values || {};
10606 values['timestamp'] = values['timestamp'] || new Date().getTime();
10607 return values;
10608};
10609/**
10610 * Value to use when firing local events. When writing server values, fire
10611 * local events with an approximate value, otherwise return value as-is.
10612 */
10613var resolveDeferredLeafValue = function (value, existingVal, serverValues) {
10614 if (!value || typeof value !== 'object') {
10615 return value;
10616 }
10617 assert('.sv' in value, 'Unexpected leaf node or priority contents');
10618 if (typeof value['.sv'] === 'string') {
10619 return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
10620 }
10621 else if (typeof value['.sv'] === 'object') {
10622 return resolveComplexDeferredValue(value['.sv'], existingVal);
10623 }
10624 else {
10625 assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
10626 }
10627};
10628var resolveScalarDeferredValue = function (op, existing, serverValues) {
10629 switch (op) {
10630 case 'timestamp':
10631 return serverValues['timestamp'];
10632 default:
10633 assert(false, 'Unexpected server value: ' + op);
10634 }
10635};
10636var resolveComplexDeferredValue = function (op, existing, unused) {
10637 if (!op.hasOwnProperty('increment')) {
10638 assert(false, 'Unexpected server value: ' + JSON.stringify(op, null, 2));
10639 }
10640 var delta = op['increment'];
10641 if (typeof delta !== 'number') {
10642 assert(false, 'Unexpected increment value: ' + delta);
10643 }
10644 var existingNode = existing.node();
10645 assert(existingNode !== null && typeof existingNode !== 'undefined', 'Expected ChildrenNode.EMPTY_NODE for nulls');
10646 // Incrementing a non-number sets the value to the incremented amount
10647 if (!existingNode.isLeafNode()) {
10648 return delta;
10649 }
10650 var leaf = existingNode;
10651 var existingVal = leaf.getValue();
10652 if (typeof existingVal !== 'number') {
10653 return delta;
10654 }
10655 // No need to do over/underflow arithmetic here because JS only handles floats under the covers
10656 return existingVal + delta;
10657};
10658/**
10659 * Recursively replace all deferred values and priorities in the tree with the
10660 * specified generated replacement values.
10661 * @param path - path to which write is relative
10662 * @param node - new data written at path
10663 * @param syncTree - current data
10664 */
10665var resolveDeferredValueTree = function (path, node, syncTree, serverValues) {
10666 return resolveDeferredValue(node, new DeferredValueProvider(syncTree, path), serverValues);
10667};
10668/**
10669 * Recursively replace all deferred values and priorities in the node with the
10670 * specified generated replacement values. If there are no server values in the node,
10671 * it'll be returned as-is.
10672 */
10673var resolveDeferredValueSnapshot = function (node, existing, serverValues) {
10674 return resolveDeferredValue(node, new ExistingValueProvider(existing), serverValues);
10675};
10676function resolveDeferredValue(node, existingVal, serverValues) {
10677 var rawPri = node.getPriority().val();
10678 var priority = resolveDeferredLeafValue(rawPri, existingVal.getImmediateChild('.priority'), serverValues);
10679 var newNode;
10680 if (node.isLeafNode()) {
10681 var leafNode = node;
10682 var value = resolveDeferredLeafValue(leafNode.getValue(), existingVal, serverValues);
10683 if (value !== leafNode.getValue() ||
10684 priority !== leafNode.getPriority().val()) {
10685 return new LeafNode(value, nodeFromJSON(priority));
10686 }
10687 else {
10688 return node;
10689 }
10690 }
10691 else {
10692 var childrenNode = node;
10693 newNode = childrenNode;
10694 if (priority !== childrenNode.getPriority().val()) {
10695 newNode = newNode.updatePriority(new LeafNode(priority));
10696 }
10697 childrenNode.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
10698 var newChildNode = resolveDeferredValue(childNode, existingVal.getImmediateChild(childName), serverValues);
10699 if (newChildNode !== childNode) {
10700 newNode = newNode.updateImmediateChild(childName, newChildNode);
10701 }
10702 });
10703 return newNode;
10704 }
10705}
10706
10707/**
10708 * @license
10709 * Copyright 2017 Google LLC
10710 *
10711 * Licensed under the Apache License, Version 2.0 (the "License");
10712 * you may not use this file except in compliance with the License.
10713 * You may obtain a copy of the License at
10714 *
10715 * http://www.apache.org/licenses/LICENSE-2.0
10716 *
10717 * Unless required by applicable law or agreed to in writing, software
10718 * distributed under the License is distributed on an "AS IS" BASIS,
10719 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10720 * See the License for the specific language governing permissions and
10721 * limitations under the License.
10722 */
10723/**
10724 * A light-weight tree, traversable by path. Nodes can have both values and children.
10725 * Nodes are not enumerated (by forEachChild) unless they have a value or non-empty
10726 * children.
10727 */
10728var Tree = /** @class */ (function () {
10729 /**
10730 * @param name - Optional name of the node.
10731 * @param parent - Optional parent node.
10732 * @param node - Optional node to wrap.
10733 */
10734 function Tree(name, parent, node) {
10735 if (name === void 0) { name = ''; }
10736 if (parent === void 0) { parent = null; }
10737 if (node === void 0) { node = { children: {}, childCount: 0 }; }
10738 this.name = name;
10739 this.parent = parent;
10740 this.node = node;
10741 }
10742 return Tree;
10743}());
10744/**
10745 * Returns a sub-Tree for the given path.
10746 *
10747 * @param pathObj - Path to look up.
10748 * @returns Tree for path.
10749 */
10750function treeSubTree(tree, pathObj) {
10751 // TODO: Require pathObj to be Path?
10752 var path = pathObj instanceof Path ? pathObj : new Path(pathObj);
10753 var child = tree, next = pathGetFront(path);
10754 while (next !== null) {
10755 var childNode = safeGet(child.node.children, next) || {
10756 children: {},
10757 childCount: 0
10758 };
10759 child = new Tree(next, child, childNode);
10760 path = pathPopFront(path);
10761 next = pathGetFront(path);
10762 }
10763 return child;
10764}
10765/**
10766 * Returns the data associated with this tree node.
10767 *
10768 * @returns The data or null if no data exists.
10769 */
10770function treeGetValue(tree) {
10771 return tree.node.value;
10772}
10773/**
10774 * Sets data to this tree node.
10775 *
10776 * @param value - Value to set.
10777 */
10778function treeSetValue(tree, value) {
10779 tree.node.value = value;
10780 treeUpdateParents(tree);
10781}
10782/**
10783 * @returns Whether the tree has any children.
10784 */
10785function treeHasChildren(tree) {
10786 return tree.node.childCount > 0;
10787}
10788/**
10789 * @returns Whethe rthe tree is empty (no value or children).
10790 */
10791function treeIsEmpty(tree) {
10792 return treeGetValue(tree) === undefined && !treeHasChildren(tree);
10793}
10794/**
10795 * Calls action for each child of this tree node.
10796 *
10797 * @param action - Action to be called for each child.
10798 */
10799function treeForEachChild(tree, action) {
10800 each(tree.node.children, function (child, childTree) {
10801 action(new Tree(child, tree, childTree));
10802 });
10803}
10804/**
10805 * Does a depth-first traversal of this node's descendants, calling action for each one.
10806 *
10807 * @param action - Action to be called for each child.
10808 * @param includeSelf - Whether to call action on this node as well. Defaults to
10809 * false.
10810 * @param childrenFirst - Whether to call action on children before calling it on
10811 * parent.
10812 */
10813function treeForEachDescendant(tree, action, includeSelf, childrenFirst) {
10814 if (includeSelf && !childrenFirst) {
10815 action(tree);
10816 }
10817 treeForEachChild(tree, function (child) {
10818 treeForEachDescendant(child, action, true, childrenFirst);
10819 });
10820 if (includeSelf && childrenFirst) {
10821 action(tree);
10822 }
10823}
10824/**
10825 * Calls action on each ancestor node.
10826 *
10827 * @param action - Action to be called on each parent; return
10828 * true to abort.
10829 * @param includeSelf - Whether to call action on this node as well.
10830 * @returns true if the action callback returned true.
10831 */
10832function treeForEachAncestor(tree, action, includeSelf) {
10833 var node = includeSelf ? tree : tree.parent;
10834 while (node !== null) {
10835 if (action(node)) {
10836 return true;
10837 }
10838 node = node.parent;
10839 }
10840 return false;
10841}
10842/**
10843 * @returns The path of this tree node, as a Path.
10844 */
10845function treeGetPath(tree) {
10846 return new Path(tree.parent === null
10847 ? tree.name
10848 : treeGetPath(tree.parent) + '/' + tree.name);
10849}
10850/**
10851 * Adds or removes this child from its parent based on whether it's empty or not.
10852 */
10853function treeUpdateParents(tree) {
10854 if (tree.parent !== null) {
10855 treeUpdateChild(tree.parent, tree.name, tree);
10856 }
10857}
10858/**
10859 * Adds or removes the passed child to this tree node, depending on whether it's empty.
10860 *
10861 * @param childName - The name of the child to update.
10862 * @param child - The child to update.
10863 */
10864function treeUpdateChild(tree, childName, child) {
10865 var childEmpty = treeIsEmpty(child);
10866 var childExists = contains(tree.node.children, childName);
10867 if (childEmpty && childExists) {
10868 delete tree.node.children[childName];
10869 tree.node.childCount--;
10870 treeUpdateParents(tree);
10871 }
10872 else if (!childEmpty && !childExists) {
10873 tree.node.children[childName] = child.node;
10874 tree.node.childCount++;
10875 treeUpdateParents(tree);
10876 }
10877}
10878
10879/**
10880 * @license
10881 * Copyright 2017 Google LLC
10882 *
10883 * Licensed under the Apache License, Version 2.0 (the "License");
10884 * you may not use this file except in compliance with the License.
10885 * You may obtain a copy of the License at
10886 *
10887 * http://www.apache.org/licenses/LICENSE-2.0
10888 *
10889 * Unless required by applicable law or agreed to in writing, software
10890 * distributed under the License is distributed on an "AS IS" BASIS,
10891 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10892 * See the License for the specific language governing permissions and
10893 * limitations under the License.
10894 */
10895/**
10896 * True for invalid Firebase keys
10897 */
10898var INVALID_KEY_REGEX_ = /[\[\].#$\/\u0000-\u001F\u007F]/;
10899/**
10900 * True for invalid Firebase paths.
10901 * Allows '/' in paths.
10902 */
10903var INVALID_PATH_REGEX_ = /[\[\].#$\u0000-\u001F\u007F]/;
10904/**
10905 * Maximum number of characters to allow in leaf value
10906 */
10907var MAX_LEAF_SIZE_ = 10 * 1024 * 1024;
10908var isValidKey = function (key) {
10909 return (typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX_.test(key));
10910};
10911var isValidPathString = function (pathString) {
10912 return (typeof pathString === 'string' &&
10913 pathString.length !== 0 &&
10914 !INVALID_PATH_REGEX_.test(pathString));
10915};
10916var isValidRootPathString = function (pathString) {
10917 if (pathString) {
10918 // Allow '/.info/' at the beginning.
10919 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10920 }
10921 return isValidPathString(pathString);
10922};
10923var isValidPriority = function (priority) {
10924 return (priority === null ||
10925 typeof priority === 'string' ||
10926 (typeof priority === 'number' && !isInvalidJSONNumber(priority)) ||
10927 (priority &&
10928 typeof priority === 'object' &&
10929 // eslint-disable-next-line @typescript-eslint/no-explicit-any
10930 contains(priority, '.sv')));
10931};
10932/**
10933 * Pre-validate a datum passed as an argument to Firebase function.
10934 */
10935var validateFirebaseDataArg = function (fnName, value, path, optional) {
10936 if (optional && value === undefined) {
10937 return;
10938 }
10939 validateFirebaseData(errorPrefix(fnName, 'value'), value, path);
10940};
10941/**
10942 * Validate a data object client-side before sending to server.
10943 */
10944var validateFirebaseData = function (errorPrefix, data, path_) {
10945 var path = path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;
10946 if (data === undefined) {
10947 throw new Error(errorPrefix + 'contains undefined ' + validationPathToErrorString(path));
10948 }
10949 if (typeof data === 'function') {
10950 throw new Error(errorPrefix +
10951 'contains a function ' +
10952 validationPathToErrorString(path) +
10953 ' with contents = ' +
10954 data.toString());
10955 }
10956 if (isInvalidJSONNumber(data)) {
10957 throw new Error(errorPrefix +
10958 'contains ' +
10959 data.toString() +
10960 ' ' +
10961 validationPathToErrorString(path));
10962 }
10963 // Check max leaf size, but try to avoid the utf8 conversion if we can.
10964 if (typeof data === 'string' &&
10965 data.length > MAX_LEAF_SIZE_ / 3 &&
10966 stringLength(data) > MAX_LEAF_SIZE_) {
10967 throw new Error(errorPrefix +
10968 'contains a string greater than ' +
10969 MAX_LEAF_SIZE_ +
10970 ' utf8 bytes ' +
10971 validationPathToErrorString(path) +
10972 " ('" +
10973 data.substring(0, 50) +
10974 "...')");
10975 }
10976 // TODO = Perf = Consider combining the recursive validation of keys into NodeFromJSON
10977 // to save extra walking of large objects.
10978 if (data && typeof data === 'object') {
10979 var hasDotValue_1 = false;
10980 var hasActualChild_1 = false;
10981 each(data, function (key, value) {
10982 if (key === '.value') {
10983 hasDotValue_1 = true;
10984 }
10985 else if (key !== '.priority' && key !== '.sv') {
10986 hasActualChild_1 = true;
10987 if (!isValidKey(key)) {
10988 throw new Error(errorPrefix +
10989 ' contains an invalid key (' +
10990 key +
10991 ') ' +
10992 validationPathToErrorString(path) +
10993 '. Keys must be non-empty strings ' +
10994 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
10995 }
10996 }
10997 validationPathPush(path, key);
10998 validateFirebaseData(errorPrefix, value, path);
10999 validationPathPop(path);
11000 });
11001 if (hasDotValue_1 && hasActualChild_1) {
11002 throw new Error(errorPrefix +
11003 ' contains ".value" child ' +
11004 validationPathToErrorString(path) +
11005 ' in addition to actual children.');
11006 }
11007 }
11008};
11009/**
11010 * Pre-validate paths passed in the firebase function.
11011 */
11012var validateFirebaseMergePaths = function (errorPrefix, mergePaths) {
11013 var i, curPath;
11014 for (i = 0; i < mergePaths.length; i++) {
11015 curPath = mergePaths[i];
11016 var keys = pathSlice(curPath);
11017 for (var j = 0; j < keys.length; j++) {
11018 if (keys[j] === '.priority' && j === keys.length - 1) ;
11019 else if (!isValidKey(keys[j])) {
11020 throw new Error(errorPrefix +
11021 'contains an invalid key (' +
11022 keys[j] +
11023 ') in path ' +
11024 curPath.toString() +
11025 '. Keys must be non-empty strings ' +
11026 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
11027 }
11028 }
11029 }
11030 // Check that update keys are not descendants of each other.
11031 // We rely on the property that sorting guarantees that ancestors come
11032 // right before descendants.
11033 mergePaths.sort(pathCompare);
11034 var prevPath = null;
11035 for (i = 0; i < mergePaths.length; i++) {
11036 curPath = mergePaths[i];
11037 if (prevPath !== null && pathContains(prevPath, curPath)) {
11038 throw new Error(errorPrefix +
11039 'contains a path ' +
11040 prevPath.toString() +
11041 ' that is ancestor of another path ' +
11042 curPath.toString());
11043 }
11044 prevPath = curPath;
11045 }
11046};
11047/**
11048 * pre-validate an object passed as an argument to firebase function (
11049 * must be an object - e.g. for firebase.update()).
11050 */
11051var validateFirebaseMergeDataArg = function (fnName, data, path, optional) {
11052 if (optional && data === undefined) {
11053 return;
11054 }
11055 var errorPrefix$1 = errorPrefix(fnName, 'values');
11056 if (!(data && typeof data === 'object') || Array.isArray(data)) {
11057 throw new Error(errorPrefix$1 + ' must be an object containing the children to replace.');
11058 }
11059 var mergePaths = [];
11060 each(data, function (key, value) {
11061 var curPath = new Path(key);
11062 validateFirebaseData(errorPrefix$1, value, pathChild(path, curPath));
11063 if (pathGetBack(curPath) === '.priority') {
11064 if (!isValidPriority(value)) {
11065 throw new Error(errorPrefix$1 +
11066 "contains an invalid value for '" +
11067 curPath.toString() +
11068 "', which must be a valid " +
11069 'Firebase priority (a string, finite number, server value, or null).');
11070 }
11071 }
11072 mergePaths.push(curPath);
11073 });
11074 validateFirebaseMergePaths(errorPrefix$1, mergePaths);
11075};
11076var validatePriority = function (fnName, priority, optional) {
11077 if (optional && priority === undefined) {
11078 return;
11079 }
11080 if (isInvalidJSONNumber(priority)) {
11081 throw new Error(errorPrefix(fnName, 'priority') +
11082 'is ' +
11083 priority.toString() +
11084 ', but must be a valid Firebase priority (a string, finite number, ' +
11085 'server value, or null).');
11086 }
11087 // Special case to allow importing data with a .sv.
11088 if (!isValidPriority(priority)) {
11089 throw new Error(errorPrefix(fnName, 'priority') +
11090 'must be a valid Firebase priority ' +
11091 '(a string, finite number, server value, or null).');
11092 }
11093};
11094var validateKey = function (fnName, argumentName, key, optional) {
11095 if (optional && key === undefined) {
11096 return;
11097 }
11098 if (!isValidKey(key)) {
11099 throw new Error(errorPrefix(fnName, argumentName) +
11100 'was an invalid key = "' +
11101 key +
11102 '". Firebase keys must be non-empty strings and ' +
11103 'can\'t contain ".", "#", "$", "/", "[", or "]").');
11104 }
11105};
11106/**
11107 * @internal
11108 */
11109var validatePathString = function (fnName, argumentName, pathString, optional) {
11110 if (optional && pathString === undefined) {
11111 return;
11112 }
11113 if (!isValidPathString(pathString)) {
11114 throw new Error(errorPrefix(fnName, argumentName) +
11115 'was an invalid path = "' +
11116 pathString +
11117 '". Paths must be non-empty strings and ' +
11118 'can\'t contain ".", "#", "$", "[", or "]"');
11119 }
11120};
11121var validateRootPathString = function (fnName, argumentName, pathString, optional) {
11122 if (pathString) {
11123 // Allow '/.info/' at the beginning.
11124 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
11125 }
11126 validatePathString(fnName, argumentName, pathString, optional);
11127};
11128/**
11129 * @internal
11130 */
11131var validateWritablePath = function (fnName, path) {
11132 if (pathGetFront(path) === '.info') {
11133 throw new Error(fnName + " failed = Can't modify data under /.info/");
11134 }
11135};
11136var validateUrl = function (fnName, parsedUrl) {
11137 // TODO = Validate server better.
11138 var pathString = parsedUrl.path.toString();
11139 if (!(typeof parsedUrl.repoInfo.host === 'string') ||
11140 parsedUrl.repoInfo.host.length === 0 ||
11141 (!isValidKey(parsedUrl.repoInfo.namespace) &&
11142 parsedUrl.repoInfo.host.split(':')[0] !== 'localhost') ||
11143 (pathString.length !== 0 && !isValidRootPathString(pathString))) {
11144 throw new Error(errorPrefix(fnName, 'url') +
11145 'must be a valid firebase URL and ' +
11146 'the path can\'t contain ".", "#", "$", "[", or "]".');
11147 }
11148};
11149
11150/**
11151 * @license
11152 * Copyright 2017 Google LLC
11153 *
11154 * Licensed under the Apache License, Version 2.0 (the "License");
11155 * you may not use this file except in compliance with the License.
11156 * You may obtain a copy of the License at
11157 *
11158 * http://www.apache.org/licenses/LICENSE-2.0
11159 *
11160 * Unless required by applicable law or agreed to in writing, software
11161 * distributed under the License is distributed on an "AS IS" BASIS,
11162 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11163 * See the License for the specific language governing permissions and
11164 * limitations under the License.
11165 */
11166/**
11167 * The event queue serves a few purposes:
11168 * 1. It ensures we maintain event order in the face of event callbacks doing operations that result in more
11169 * events being queued.
11170 * 2. raiseQueuedEvents() handles being called reentrantly nicely. That is, if in the course of raising events,
11171 * raiseQueuedEvents() is called again, the "inner" call will pick up raising events where the "outer" call
11172 * left off, ensuring that the events are still raised synchronously and in order.
11173 * 3. You can use raiseEventsAtPath and raiseEventsForChangedPath to ensure only relevant previously-queued
11174 * events are raised synchronously.
11175 *
11176 * NOTE: This can all go away if/when we move to async events.
11177 *
11178 */
11179var EventQueue = /** @class */ (function () {
11180 function EventQueue() {
11181 this.eventLists_ = [];
11182 /**
11183 * Tracks recursion depth of raiseQueuedEvents_, for debugging purposes.
11184 */
11185 this.recursionDepth_ = 0;
11186 }
11187 return EventQueue;
11188}());
11189/**
11190 * @param eventDataList - The new events to queue.
11191 */
11192function eventQueueQueueEvents(eventQueue, eventDataList) {
11193 // We group events by path, storing them in a single EventList, to make it easier to skip over them quickly.
11194 var currList = null;
11195 for (var i = 0; i < eventDataList.length; i++) {
11196 var data = eventDataList[i];
11197 var path = data.getPath();
11198 if (currList !== null && !pathEquals(path, currList.path)) {
11199 eventQueue.eventLists_.push(currList);
11200 currList = null;
11201 }
11202 if (currList === null) {
11203 currList = { events: [], path: path };
11204 }
11205 currList.events.push(data);
11206 }
11207 if (currList) {
11208 eventQueue.eventLists_.push(currList);
11209 }
11210}
11211/**
11212 * Queues the specified events and synchronously raises all events (including previously queued ones)
11213 * for the specified path.
11214 *
11215 * It is assumed that the new events are all for the specified path.
11216 *
11217 * @param path - The path to raise events for.
11218 * @param eventDataList - The new events to raise.
11219 */
11220function eventQueueRaiseEventsAtPath(eventQueue, path, eventDataList) {
11221 eventQueueQueueEvents(eventQueue, eventDataList);
11222 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11223 return pathEquals(eventPath, path);
11224 });
11225}
11226/**
11227 * Queues the specified events and synchronously raises all events (including previously queued ones) for
11228 * locations related to the specified change path (i.e. all ancestors and descendants).
11229 *
11230 * It is assumed that the new events are all related (ancestor or descendant) to the specified path.
11231 *
11232 * @param changedPath - The path to raise events for.
11233 * @param eventDataList - The events to raise
11234 */
11235function eventQueueRaiseEventsForChangedPath(eventQueue, changedPath, eventDataList) {
11236 eventQueueQueueEvents(eventQueue, eventDataList);
11237 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11238 return pathContains(eventPath, changedPath) ||
11239 pathContains(changedPath, eventPath);
11240 });
11241}
11242function eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, predicate) {
11243 eventQueue.recursionDepth_++;
11244 var sentAll = true;
11245 for (var i = 0; i < eventQueue.eventLists_.length; i++) {
11246 var eventList = eventQueue.eventLists_[i];
11247 if (eventList) {
11248 var eventPath = eventList.path;
11249 if (predicate(eventPath)) {
11250 eventListRaise(eventQueue.eventLists_[i]);
11251 eventQueue.eventLists_[i] = null;
11252 }
11253 else {
11254 sentAll = false;
11255 }
11256 }
11257 }
11258 if (sentAll) {
11259 eventQueue.eventLists_ = [];
11260 }
11261 eventQueue.recursionDepth_--;
11262}
11263/**
11264 * Iterates through the list and raises each event
11265 */
11266function eventListRaise(eventList) {
11267 for (var i = 0; i < eventList.events.length; i++) {
11268 var eventData = eventList.events[i];
11269 if (eventData !== null) {
11270 eventList.events[i] = null;
11271 var eventFn = eventData.getEventRunner();
11272 if (logger) {
11273 log('event: ' + eventData.toString());
11274 }
11275 exceptionGuard(eventFn);
11276 }
11277 }
11278}
11279
11280/**
11281 * @license
11282 * Copyright 2017 Google LLC
11283 *
11284 * Licensed under the Apache License, Version 2.0 (the "License");
11285 * you may not use this file except in compliance with the License.
11286 * You may obtain a copy of the License at
11287 *
11288 * http://www.apache.org/licenses/LICENSE-2.0
11289 *
11290 * Unless required by applicable law or agreed to in writing, software
11291 * distributed under the License is distributed on an "AS IS" BASIS,
11292 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11293 * See the License for the specific language governing permissions and
11294 * limitations under the License.
11295 */
11296var INTERRUPT_REASON = 'repo_interrupt';
11297/**
11298 * If a transaction does not succeed after 25 retries, we abort it. Among other
11299 * things this ensure that if there's ever a bug causing a mismatch between
11300 * client / server hashes for some data, we won't retry indefinitely.
11301 */
11302var MAX_TRANSACTION_RETRIES = 25;
11303/**
11304 * A connection to a single data repository.
11305 */
11306var Repo = /** @class */ (function () {
11307 function Repo(repoInfo_, forceRestClient_, authTokenProvider_, appCheckProvider_) {
11308 this.repoInfo_ = repoInfo_;
11309 this.forceRestClient_ = forceRestClient_;
11310 this.authTokenProvider_ = authTokenProvider_;
11311 this.appCheckProvider_ = appCheckProvider_;
11312 this.dataUpdateCount = 0;
11313 this.statsListener_ = null;
11314 this.eventQueue_ = new EventQueue();
11315 this.nextWriteId_ = 1;
11316 this.interceptServerDataCallback_ = null;
11317 /** A list of data pieces and paths to be set when this client disconnects. */
11318 this.onDisconnect_ = newSparseSnapshotTree();
11319 /** Stores queues of outstanding transactions for Firebase locations. */
11320 this.transactionQueueTree_ = new Tree();
11321 // TODO: This should be @private but it's used by test_access.js and internal.js
11322 this.persistentConnection_ = null;
11323 // This key is intentionally not updated if RepoInfo is later changed or replaced
11324 this.key = this.repoInfo_.toURLString();
11325 }
11326 /**
11327 * @returns The URL corresponding to the root of this Firebase.
11328 */
11329 Repo.prototype.toString = function () {
11330 return ((this.repoInfo_.secure ? 'https://' : 'http://') + this.repoInfo_.host);
11331 };
11332 return Repo;
11333}());
11334function repoStart(repo, appId, authOverride) {
11335 repo.stats_ = statsManagerGetCollection(repo.repoInfo_);
11336 if (repo.forceRestClient_ || beingCrawled()) {
11337 repo.server_ = new ReadonlyRestClient(repo.repoInfo_, function (pathString, data, isMerge, tag) {
11338 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11339 }, repo.authTokenProvider_, repo.appCheckProvider_);
11340 // Minor hack: Fire onConnect immediately, since there's no actual connection.
11341 setTimeout(function () { return repoOnConnectStatus(repo, /* connectStatus= */ true); }, 0);
11342 }
11343 else {
11344 // Validate authOverride
11345 if (typeof authOverride !== 'undefined' && authOverride !== null) {
11346 if (typeof authOverride !== 'object') {
11347 throw new Error('Only objects are supported for option databaseAuthVariableOverride');
11348 }
11349 try {
11350 stringify(authOverride);
11351 }
11352 catch (e) {
11353 throw new Error('Invalid authOverride provided: ' + e);
11354 }
11355 }
11356 repo.persistentConnection_ = new PersistentConnection(repo.repoInfo_, appId, function (pathString, data, isMerge, tag) {
11357 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11358 }, function (connectStatus) {
11359 repoOnConnectStatus(repo, connectStatus);
11360 }, function (updates) {
11361 repoOnServerInfoUpdate(repo, updates);
11362 }, repo.authTokenProvider_, repo.appCheckProvider_, authOverride);
11363 repo.server_ = repo.persistentConnection_;
11364 }
11365 repo.authTokenProvider_.addTokenChangeListener(function (token) {
11366 repo.server_.refreshAuthToken(token);
11367 });
11368 repo.appCheckProvider_.addTokenChangeListener(function (result) {
11369 repo.server_.refreshAppCheckToken(result.token);
11370 });
11371 // In the case of multiple Repos for the same repoInfo (i.e. there are multiple Firebase.Contexts being used),
11372 // we only want to create one StatsReporter. As such, we'll report stats over the first Repo created.
11373 repo.statsReporter_ = statsManagerGetOrCreateReporter(repo.repoInfo_, function () { return new StatsReporter(repo.stats_, repo.server_); });
11374 // Used for .info.
11375 repo.infoData_ = new SnapshotHolder();
11376 repo.infoSyncTree_ = new SyncTree({
11377 startListening: function (query, tag, currentHashFn, onComplete) {
11378 var infoEvents = [];
11379 var node = repo.infoData_.getNode(query._path);
11380 // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
11381 // on initial data...
11382 if (!node.isEmpty()) {
11383 infoEvents = syncTreeApplyServerOverwrite(repo.infoSyncTree_, query._path, node);
11384 setTimeout(function () {
11385 onComplete('ok');
11386 }, 0);
11387 }
11388 return infoEvents;
11389 },
11390 stopListening: function () { }
11391 });
11392 repoUpdateInfo(repo, 'connected', false);
11393 repo.serverSyncTree_ = new SyncTree({
11394 startListening: function (query, tag, currentHashFn, onComplete) {
11395 repo.server_.listen(query, currentHashFn, tag, function (status, data) {
11396 var events = onComplete(status, data);
11397 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11398 });
11399 // No synchronous events for network-backed sync trees
11400 return [];
11401 },
11402 stopListening: function (query, tag) {
11403 repo.server_.unlisten(query, tag);
11404 }
11405 });
11406}
11407/**
11408 * @returns The time in milliseconds, taking the server offset into account if we have one.
11409 */
11410function repoServerTime(repo) {
11411 var offsetNode = repo.infoData_.getNode(new Path('.info/serverTimeOffset'));
11412 var offset = offsetNode.val() || 0;
11413 return new Date().getTime() + offset;
11414}
11415/**
11416 * Generate ServerValues using some variables from the repo object.
11417 */
11418function repoGenerateServerValues(repo) {
11419 return generateWithValues({
11420 timestamp: repoServerTime(repo)
11421 });
11422}
11423/**
11424 * Called by realtime when we get new messages from the server.
11425 */
11426function repoOnDataUpdate(repo, pathString, data, isMerge, tag) {
11427 // For testing.
11428 repo.dataUpdateCount++;
11429 var path = new Path(pathString);
11430 data = repo.interceptServerDataCallback_
11431 ? repo.interceptServerDataCallback_(pathString, data)
11432 : data;
11433 var events = [];
11434 if (tag) {
11435 if (isMerge) {
11436 var taggedChildren = map(data, function (raw) { return nodeFromJSON(raw); });
11437 events = syncTreeApplyTaggedQueryMerge(repo.serverSyncTree_, path, taggedChildren, tag);
11438 }
11439 else {
11440 var taggedSnap = nodeFromJSON(data);
11441 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, path, taggedSnap, tag);
11442 }
11443 }
11444 else if (isMerge) {
11445 var changedChildren = map(data, function (raw) { return nodeFromJSON(raw); });
11446 events = syncTreeApplyServerMerge(repo.serverSyncTree_, path, changedChildren);
11447 }
11448 else {
11449 var snap = nodeFromJSON(data);
11450 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap);
11451 }
11452 var affectedPath = path;
11453 if (events.length > 0) {
11454 // Since we have a listener outstanding for each transaction, receiving any events
11455 // is a proxy for some change having occurred.
11456 affectedPath = repoRerunTransactions(repo, path);
11457 }
11458 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, events);
11459}
11460function repoOnConnectStatus(repo, connectStatus) {
11461 repoUpdateInfo(repo, 'connected', connectStatus);
11462 if (connectStatus === false) {
11463 repoRunOnDisconnectEvents(repo);
11464 }
11465}
11466function repoOnServerInfoUpdate(repo, updates) {
11467 each(updates, function (key, value) {
11468 repoUpdateInfo(repo, key, value);
11469 });
11470}
11471function repoUpdateInfo(repo, pathString, value) {
11472 var path = new Path('/.info/' + pathString);
11473 var newNode = nodeFromJSON(value);
11474 repo.infoData_.updateSnapshot(path, newNode);
11475 var events = syncTreeApplyServerOverwrite(repo.infoSyncTree_, path, newNode);
11476 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11477}
11478function repoGetNextWriteId(repo) {
11479 return repo.nextWriteId_++;
11480}
11481/**
11482 * The purpose of `getValue` is to return the latest known value
11483 * satisfying `query`.
11484 *
11485 * This method will first check for in-memory cached values
11486 * belonging to active listeners. If they are found, such values
11487 * are considered to be the most up-to-date.
11488 *
11489 * If the client is not connected, this method will try to
11490 * establish a connection and request the value for `query`. If
11491 * the client is not able to retrieve the query result, it reports
11492 * an error.
11493 *
11494 * @param query - The query to surface a value for.
11495 */
11496function repoGetValue(repo, query) {
11497 // Only active queries are cached. There is no persisted cache.
11498 var cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11499 if (cached != null) {
11500 return Promise.resolve(cached);
11501 }
11502 return repo.server_.get(query).then(function (payload) {
11503 var node = nodeFromJSON(payload).withIndex(query._queryParams.getIndex());
11504 var events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11505 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11506 return Promise.resolve(node);
11507 }, function (err) {
11508 repoLog(repo, 'get for query ' + stringify(query) + ' failed: ' + err);
11509 return Promise.reject(new Error(err));
11510 });
11511}
11512function repoSetWithPriority(repo, path, newVal, newPriority, onComplete) {
11513 repoLog(repo, 'set', {
11514 path: path.toString(),
11515 value: newVal,
11516 priority: newPriority
11517 });
11518 // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
11519 // (b) store unresolved paths on JSON parse
11520 var serverValues = repoGenerateServerValues(repo);
11521 var newNodeUnresolved = nodeFromJSON(newVal, newPriority);
11522 var existing = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path);
11523 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, existing, serverValues);
11524 var writeId = repoGetNextWriteId(repo);
11525 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, writeId, true);
11526 eventQueueQueueEvents(repo.eventQueue_, events);
11527 repo.server_.put(path.toString(), newNodeUnresolved.val(/*export=*/ true), function (status, errorReason) {
11528 var success = status === 'ok';
11529 if (!success) {
11530 warn('set at ' + path + ' failed: ' + status);
11531 }
11532 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId, !success);
11533 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, clearEvents);
11534 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11535 });
11536 var affectedPath = repoAbortTransactions(repo, path);
11537 repoRerunTransactions(repo, affectedPath);
11538 // We queued the events above, so just flush the queue here
11539 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, []);
11540}
11541function repoUpdate(repo, path, childrenToMerge, onComplete) {
11542 repoLog(repo, 'update', { path: path.toString(), value: childrenToMerge });
11543 // Start with our existing data and merge each child into it.
11544 var empty = true;
11545 var serverValues = repoGenerateServerValues(repo);
11546 var changedChildren = {};
11547 each(childrenToMerge, function (changedKey, changedValue) {
11548 empty = false;
11549 changedChildren[changedKey] = resolveDeferredValueTree(pathChild(path, changedKey), nodeFromJSON(changedValue), repo.serverSyncTree_, serverValues);
11550 });
11551 if (!empty) {
11552 var writeId_1 = repoGetNextWriteId(repo);
11553 var events = syncTreeApplyUserMerge(repo.serverSyncTree_, path, changedChildren, writeId_1);
11554 eventQueueQueueEvents(repo.eventQueue_, events);
11555 repo.server_.merge(path.toString(), childrenToMerge, function (status, errorReason) {
11556 var success = status === 'ok';
11557 if (!success) {
11558 warn('update at ' + path + ' failed: ' + status);
11559 }
11560 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId_1, !success);
11561 var affectedPath = clearEvents.length > 0 ? repoRerunTransactions(repo, path) : path;
11562 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, clearEvents);
11563 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11564 });
11565 each(childrenToMerge, function (changedPath) {
11566 var affectedPath = repoAbortTransactions(repo, pathChild(path, changedPath));
11567 repoRerunTransactions(repo, affectedPath);
11568 });
11569 // We queued the events above, so just flush the queue here
11570 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, []);
11571 }
11572 else {
11573 log("update() called with empty data. Don't do anything.");
11574 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11575 }
11576}
11577/**
11578 * Applies all of the changes stored up in the onDisconnect_ tree.
11579 */
11580function repoRunOnDisconnectEvents(repo) {
11581 repoLog(repo, 'onDisconnectEvents');
11582 var serverValues = repoGenerateServerValues(repo);
11583 var resolvedOnDisconnectTree = newSparseSnapshotTree();
11584 sparseSnapshotTreeForEachTree(repo.onDisconnect_, newEmptyPath(), function (path, node) {
11585 var resolved = resolveDeferredValueTree(path, node, repo.serverSyncTree_, serverValues);
11586 sparseSnapshotTreeRemember(resolvedOnDisconnectTree, path, resolved);
11587 });
11588 var events = [];
11589 sparseSnapshotTreeForEachTree(resolvedOnDisconnectTree, newEmptyPath(), function (path, snap) {
11590 events = events.concat(syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap));
11591 var affectedPath = repoAbortTransactions(repo, path);
11592 repoRerunTransactions(repo, affectedPath);
11593 });
11594 repo.onDisconnect_ = newSparseSnapshotTree();
11595 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, newEmptyPath(), events);
11596}
11597function repoOnDisconnectCancel(repo, path, onComplete) {
11598 repo.server_.onDisconnectCancel(path.toString(), function (status, errorReason) {
11599 if (status === 'ok') {
11600 sparseSnapshotTreeForget(repo.onDisconnect_, path);
11601 }
11602 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11603 });
11604}
11605function repoOnDisconnectSet(repo, path, value, onComplete) {
11606 var newNode = nodeFromJSON(value);
11607 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11608 if (status === 'ok') {
11609 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11610 }
11611 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11612 });
11613}
11614function repoOnDisconnectSetWithPriority(repo, path, value, priority, onComplete) {
11615 var newNode = nodeFromJSON(value, priority);
11616 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11617 if (status === 'ok') {
11618 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11619 }
11620 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11621 });
11622}
11623function repoOnDisconnectUpdate(repo, path, childrenToMerge, onComplete) {
11624 if (isEmpty(childrenToMerge)) {
11625 log("onDisconnect().update() called with empty data. Don't do anything.");
11626 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11627 return;
11628 }
11629 repo.server_.onDisconnectMerge(path.toString(), childrenToMerge, function (status, errorReason) {
11630 if (status === 'ok') {
11631 each(childrenToMerge, function (childName, childNode) {
11632 var newChildNode = nodeFromJSON(childNode);
11633 sparseSnapshotTreeRemember(repo.onDisconnect_, pathChild(path, childName), newChildNode);
11634 });
11635 }
11636 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11637 });
11638}
11639function repoAddEventCallbackForQuery(repo, query, eventRegistration) {
11640 var events;
11641 if (pathGetFront(query._path) === '.info') {
11642 events = syncTreeAddEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11643 }
11644 else {
11645 events = syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11646 }
11647 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11648}
11649function repoRemoveEventCallbackForQuery(repo, query, eventRegistration) {
11650 // These are guaranteed not to raise events, since we're not passing in a cancelError. However, we can future-proof
11651 // a little bit by handling the return values anyways.
11652 var events;
11653 if (pathGetFront(query._path) === '.info') {
11654 events = syncTreeRemoveEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11655 }
11656 else {
11657 events = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11658 }
11659 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11660}
11661function repoInterrupt(repo) {
11662 if (repo.persistentConnection_) {
11663 repo.persistentConnection_.interrupt(INTERRUPT_REASON);
11664 }
11665}
11666function repoResume(repo) {
11667 if (repo.persistentConnection_) {
11668 repo.persistentConnection_.resume(INTERRUPT_REASON);
11669 }
11670}
11671function repoLog(repo) {
11672 var varArgs = [];
11673 for (var _i = 1; _i < arguments.length; _i++) {
11674 varArgs[_i - 1] = arguments[_i];
11675 }
11676 var prefix = '';
11677 if (repo.persistentConnection_) {
11678 prefix = repo.persistentConnection_.id + ':';
11679 }
11680 log.apply(void 0, __spreadArray([prefix], __read(varArgs)));
11681}
11682function repoCallOnCompleteCallback(repo, callback, status, errorReason) {
11683 if (callback) {
11684 exceptionGuard(function () {
11685 if (status === 'ok') {
11686 callback(null);
11687 }
11688 else {
11689 var code = (status || 'error').toUpperCase();
11690 var message = code;
11691 if (errorReason) {
11692 message += ': ' + errorReason;
11693 }
11694 var error = new Error(message);
11695 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11696 error.code = code;
11697 callback(error);
11698 }
11699 });
11700 }
11701}
11702/**
11703 * Creates a new transaction, adds it to the transactions we're tracking, and
11704 * sends it to the server if possible.
11705 *
11706 * @param path - Path at which to do transaction.
11707 * @param transactionUpdate - Update callback.
11708 * @param onComplete - Completion callback.
11709 * @param unwatcher - Function that will be called when the transaction no longer
11710 * need data updates for `path`.
11711 * @param applyLocally - Whether or not to make intermediate results visible
11712 */
11713function repoStartTransaction(repo, path, transactionUpdate, onComplete, unwatcher, applyLocally) {
11714 repoLog(repo, 'transaction on ' + path);
11715 // Initialize transaction.
11716 var transaction = {
11717 path: path,
11718 update: transactionUpdate,
11719 onComplete: onComplete,
11720 // One of TransactionStatus enums.
11721 status: null,
11722 // Used when combining transactions at different locations to figure out
11723 // which one goes first.
11724 order: LUIDGenerator(),
11725 // Whether to raise local events for this transaction.
11726 applyLocally: applyLocally,
11727 // Count of how many times we've retried the transaction.
11728 retryCount: 0,
11729 // Function to call to clean up our .on() listener.
11730 unwatcher: unwatcher,
11731 // Stores why a transaction was aborted.
11732 abortReason: null,
11733 currentWriteId: null,
11734 currentInputSnapshot: null,
11735 currentOutputSnapshotRaw: null,
11736 currentOutputSnapshotResolved: null
11737 };
11738 // Run transaction initially.
11739 var currentState = repoGetLatestState(repo, path, undefined);
11740 transaction.currentInputSnapshot = currentState;
11741 var newVal = transaction.update(currentState.val());
11742 if (newVal === undefined) {
11743 // Abort transaction.
11744 transaction.unwatcher();
11745 transaction.currentOutputSnapshotRaw = null;
11746 transaction.currentOutputSnapshotResolved = null;
11747 if (transaction.onComplete) {
11748 transaction.onComplete(null, false, transaction.currentInputSnapshot);
11749 }
11750 }
11751 else {
11752 validateFirebaseData('transaction failed: Data returned ', newVal, transaction.path);
11753 // Mark as run and add to our queue.
11754 transaction.status = 0 /* RUN */;
11755 var queueNode = treeSubTree(repo.transactionQueueTree_, path);
11756 var nodeQueue = treeGetValue(queueNode) || [];
11757 nodeQueue.push(transaction);
11758 treeSetValue(queueNode, nodeQueue);
11759 // Update visibleData and raise events
11760 // Note: We intentionally raise events after updating all of our
11761 // transaction state, since the user could start new transactions from the
11762 // event callbacks.
11763 var priorityForNode = void 0;
11764 if (typeof newVal === 'object' &&
11765 newVal !== null &&
11766 contains(newVal, '.priority')) {
11767 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11768 priorityForNode = safeGet(newVal, '.priority');
11769 assert(isValidPriority(priorityForNode), 'Invalid priority returned by transaction. ' +
11770 'Priority must be a valid string, finite number, server value, or null.');
11771 }
11772 else {
11773 var currentNode = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path) ||
11774 ChildrenNode.EMPTY_NODE;
11775 priorityForNode = currentNode.getPriority().val();
11776 }
11777 var serverValues = repoGenerateServerValues(repo);
11778 var newNodeUnresolved = nodeFromJSON(newVal, priorityForNode);
11779 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, currentState, serverValues);
11780 transaction.currentOutputSnapshotRaw = newNodeUnresolved;
11781 transaction.currentOutputSnapshotResolved = newNode;
11782 transaction.currentWriteId = repoGetNextWriteId(repo);
11783 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, transaction.currentWriteId, transaction.applyLocally);
11784 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11785 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11786 }
11787}
11788/**
11789 * @param excludeSets - A specific set to exclude
11790 */
11791function repoGetLatestState(repo, path, excludeSets) {
11792 return (syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path, excludeSets) ||
11793 ChildrenNode.EMPTY_NODE);
11794}
11795/**
11796 * Sends any already-run transactions that aren't waiting for outstanding
11797 * transactions to complete.
11798 *
11799 * Externally it's called with no arguments, but it calls itself recursively
11800 * with a particular transactionQueueTree node to recurse through the tree.
11801 *
11802 * @param node - transactionQueueTree node to start at.
11803 */
11804function repoSendReadyTransactions(repo, node) {
11805 if (node === void 0) { node = repo.transactionQueueTree_; }
11806 // Before recursing, make sure any completed transactions are removed.
11807 if (!node) {
11808 repoPruneCompletedTransactionsBelowNode(repo, node);
11809 }
11810 if (treeGetValue(node)) {
11811 var queue = repoBuildTransactionQueue(repo, node);
11812 assert(queue.length > 0, 'Sending zero length transaction queue');
11813 var allRun = queue.every(function (transaction) { return transaction.status === 0 /* RUN */; });
11814 // If they're all run (and not sent), we can send them. Else, we must wait.
11815 if (allRun) {
11816 repoSendTransactionQueue(repo, treeGetPath(node), queue);
11817 }
11818 }
11819 else if (treeHasChildren(node)) {
11820 treeForEachChild(node, function (childNode) {
11821 repoSendReadyTransactions(repo, childNode);
11822 });
11823 }
11824}
11825/**
11826 * Given a list of run transactions, send them to the server and then handle
11827 * the result (success or failure).
11828 *
11829 * @param path - The location of the queue.
11830 * @param queue - Queue of transactions under the specified location.
11831 */
11832function repoSendTransactionQueue(repo, path, queue) {
11833 // Mark transactions as sent and increment retry count!
11834 var setsToIgnore = queue.map(function (txn) {
11835 return txn.currentWriteId;
11836 });
11837 var latestState = repoGetLatestState(repo, path, setsToIgnore);
11838 var snapToSend = latestState;
11839 var latestHash = latestState.hash();
11840 for (var i = 0; i < queue.length; i++) {
11841 var txn = queue[i];
11842 assert(txn.status === 0 /* RUN */, 'tryToSendTransactionQueue_: items in queue should all be run.');
11843 txn.status = 1 /* SENT */;
11844 txn.retryCount++;
11845 var relativePath = newRelativePath(path, txn.path);
11846 // If we've gotten to this point, the output snapshot must be defined.
11847 snapToSend = snapToSend.updateChild(relativePath /** @type {!Node} */, txn.currentOutputSnapshotRaw);
11848 }
11849 var dataToSend = snapToSend.val(true);
11850 var pathToSend = path;
11851 // Send the put.
11852 repo.server_.put(pathToSend.toString(), dataToSend, function (status) {
11853 repoLog(repo, 'transaction put response', {
11854 path: pathToSend.toString(),
11855 status: status
11856 });
11857 var events = [];
11858 if (status === 'ok') {
11859 // Queue up the callbacks and fire them after cleaning up all of our
11860 // transaction state, since the callback could trigger more
11861 // transactions or sets.
11862 var callbacks = [];
11863 var _loop_1 = function (i) {
11864 queue[i].status = 2 /* COMPLETED */;
11865 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId));
11866 if (queue[i].onComplete) {
11867 // We never unset the output snapshot, and given that this
11868 // transaction is complete, it should be set
11869 callbacks.push(function () {
11870 return queue[i].onComplete(null, true, queue[i].currentOutputSnapshotResolved);
11871 });
11872 }
11873 queue[i].unwatcher();
11874 };
11875 for (var i = 0; i < queue.length; i++) {
11876 _loop_1(i);
11877 }
11878 // Now remove the completed transactions.
11879 repoPruneCompletedTransactionsBelowNode(repo, treeSubTree(repo.transactionQueueTree_, path));
11880 // There may be pending transactions that we can now send.
11881 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11882 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11883 // Finally, trigger onComplete callbacks.
11884 for (var i = 0; i < callbacks.length; i++) {
11885 exceptionGuard(callbacks[i]);
11886 }
11887 }
11888 else {
11889 // transactions are no longer sent. Update their status appropriately.
11890 if (status === 'datastale') {
11891 for (var i = 0; i < queue.length; i++) {
11892 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) {
11893 queue[i].status = 4 /* NEEDS_ABORT */;
11894 }
11895 else {
11896 queue[i].status = 0 /* RUN */;
11897 }
11898 }
11899 }
11900 else {
11901 warn('transaction at ' + pathToSend.toString() + ' failed: ' + status);
11902 for (var i = 0; i < queue.length; i++) {
11903 queue[i].status = 4 /* NEEDS_ABORT */;
11904 queue[i].abortReason = status;
11905 }
11906 }
11907 repoRerunTransactions(repo, path);
11908 }
11909 }, latestHash);
11910}
11911/**
11912 * Finds all transactions dependent on the data at changedPath and reruns them.
11913 *
11914 * Should be called any time cached data changes.
11915 *
11916 * Return the highest path that was affected by rerunning transactions. This
11917 * is the path at which events need to be raised for.
11918 *
11919 * @param changedPath - The path in mergedData that changed.
11920 * @returns The rootmost path that was affected by rerunning transactions.
11921 */
11922function repoRerunTransactions(repo, changedPath) {
11923 var rootMostTransactionNode = repoGetAncestorTransactionNode(repo, changedPath);
11924 var path = treeGetPath(rootMostTransactionNode);
11925 var queue = repoBuildTransactionQueue(repo, rootMostTransactionNode);
11926 repoRerunTransactionQueue(repo, queue, path);
11927 return path;
11928}
11929/**
11930 * Does all the work of rerunning transactions (as well as cleans up aborted
11931 * transactions and whatnot).
11932 *
11933 * @param queue - The queue of transactions to run.
11934 * @param path - The path the queue is for.
11935 */
11936function repoRerunTransactionQueue(repo, queue, path) {
11937 if (queue.length === 0) {
11938 return; // Nothing to do!
11939 }
11940 // Queue up the callbacks and fire them after cleaning up all of our
11941 // transaction state, since the callback could trigger more transactions or
11942 // sets.
11943 var callbacks = [];
11944 var events = [];
11945 // Ignore all of the sets we're going to re-run.
11946 var txnsToRerun = queue.filter(function (q) {
11947 return q.status === 0 /* RUN */;
11948 });
11949 var setsToIgnore = txnsToRerun.map(function (q) {
11950 return q.currentWriteId;
11951 });
11952 var _loop_2 = function (i) {
11953 var transaction = queue[i];
11954 var relativePath = newRelativePath(path, transaction.path);
11955 var abortTransaction = false, abortReason;
11956 assert(relativePath !== null, 'rerunTransactionsUnderNode_: relativePath should not be null.');
11957 if (transaction.status === 4 /* NEEDS_ABORT */) {
11958 abortTransaction = true;
11959 abortReason = transaction.abortReason;
11960 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11961 }
11962 else if (transaction.status === 0 /* RUN */) {
11963 if (transaction.retryCount >= MAX_TRANSACTION_RETRIES) {
11964 abortTransaction = true;
11965 abortReason = 'maxretry';
11966 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11967 }
11968 else {
11969 // This code reruns a transaction
11970 var currentNode = repoGetLatestState(repo, transaction.path, setsToIgnore);
11971 transaction.currentInputSnapshot = currentNode;
11972 var newData = queue[i].update(currentNode.val());
11973 if (newData !== undefined) {
11974 validateFirebaseData('transaction failed: Data returned ', newData, transaction.path);
11975 var newDataNode = nodeFromJSON(newData);
11976 var hasExplicitPriority = typeof newData === 'object' &&
11977 newData != null &&
11978 contains(newData, '.priority');
11979 if (!hasExplicitPriority) {
11980 // Keep the old priority if there wasn't a priority explicitly specified.
11981 newDataNode = newDataNode.updatePriority(currentNode.getPriority());
11982 }
11983 var oldWriteId = transaction.currentWriteId;
11984 var serverValues = repoGenerateServerValues(repo);
11985 var newNodeResolved = resolveDeferredValueSnapshot(newDataNode, currentNode, serverValues);
11986 transaction.currentOutputSnapshotRaw = newDataNode;
11987 transaction.currentOutputSnapshotResolved = newNodeResolved;
11988 transaction.currentWriteId = repoGetNextWriteId(repo);
11989 // Mutates setsToIgnore in place
11990 setsToIgnore.splice(setsToIgnore.indexOf(oldWriteId), 1);
11991 events = events.concat(syncTreeApplyUserOverwrite(repo.serverSyncTree_, transaction.path, newNodeResolved, transaction.currentWriteId, transaction.applyLocally));
11992 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, oldWriteId, true));
11993 }
11994 else {
11995 abortTransaction = true;
11996 abortReason = 'nodata';
11997 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11998 }
11999 }
12000 }
12001 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
12002 events = [];
12003 if (abortTransaction) {
12004 // Abort.
12005 queue[i].status = 2 /* COMPLETED */;
12006 // Removing a listener can trigger pruning which can muck with
12007 // mergedData/visibleData (as it prunes data). So defer the unwatcher
12008 // until we're done.
12009 (function (unwatcher) {
12010 setTimeout(unwatcher, Math.floor(0));
12011 })(queue[i].unwatcher);
12012 if (queue[i].onComplete) {
12013 if (abortReason === 'nodata') {
12014 callbacks.push(function () {
12015 return queue[i].onComplete(null, false, queue[i].currentInputSnapshot);
12016 });
12017 }
12018 else {
12019 callbacks.push(function () {
12020 return queue[i].onComplete(new Error(abortReason), false, null);
12021 });
12022 }
12023 }
12024 }
12025 };
12026 for (var i = 0; i < queue.length; i++) {
12027 _loop_2(i);
12028 }
12029 // Clean up completed transactions.
12030 repoPruneCompletedTransactionsBelowNode(repo, repo.transactionQueueTree_);
12031 // Now fire callbacks, now that we're in a good, known state.
12032 for (var i = 0; i < callbacks.length; i++) {
12033 exceptionGuard(callbacks[i]);
12034 }
12035 // Try to send the transaction result to the server.
12036 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
12037}
12038/**
12039 * Returns the rootmost ancestor node of the specified path that has a pending
12040 * transaction on it, or just returns the node for the given path if there are
12041 * no pending transactions on any ancestor.
12042 *
12043 * @param path - The location to start at.
12044 * @returns The rootmost node with a transaction.
12045 */
12046function repoGetAncestorTransactionNode(repo, path) {
12047 var front;
12048 // Start at the root and walk deeper into the tree towards path until we
12049 // find a node with pending transactions.
12050 var transactionNode = repo.transactionQueueTree_;
12051 front = pathGetFront(path);
12052 while (front !== null && treeGetValue(transactionNode) === undefined) {
12053 transactionNode = treeSubTree(transactionNode, front);
12054 path = pathPopFront(path);
12055 front = pathGetFront(path);
12056 }
12057 return transactionNode;
12058}
12059/**
12060 * Builds the queue of all transactions at or below the specified
12061 * transactionNode.
12062 *
12063 * @param transactionNode
12064 * @returns The generated queue.
12065 */
12066function repoBuildTransactionQueue(repo, transactionNode) {
12067 // Walk any child transaction queues and aggregate them into a single queue.
12068 var transactionQueue = [];
12069 repoAggregateTransactionQueuesForNode(repo, transactionNode, transactionQueue);
12070 // Sort them by the order the transactions were created.
12071 transactionQueue.sort(function (a, b) { return a.order - b.order; });
12072 return transactionQueue;
12073}
12074function repoAggregateTransactionQueuesForNode(repo, node, queue) {
12075 var nodeQueue = treeGetValue(node);
12076 if (nodeQueue) {
12077 for (var i = 0; i < nodeQueue.length; i++) {
12078 queue.push(nodeQueue[i]);
12079 }
12080 }
12081 treeForEachChild(node, function (child) {
12082 repoAggregateTransactionQueuesForNode(repo, child, queue);
12083 });
12084}
12085/**
12086 * Remove COMPLETED transactions at or below this node in the transactionQueueTree_.
12087 */
12088function repoPruneCompletedTransactionsBelowNode(repo, node) {
12089 var queue = treeGetValue(node);
12090 if (queue) {
12091 var to = 0;
12092 for (var from = 0; from < queue.length; from++) {
12093 if (queue[from].status !== 2 /* COMPLETED */) {
12094 queue[to] = queue[from];
12095 to++;
12096 }
12097 }
12098 queue.length = to;
12099 treeSetValue(node, queue.length > 0 ? queue : undefined);
12100 }
12101 treeForEachChild(node, function (childNode) {
12102 repoPruneCompletedTransactionsBelowNode(repo, childNode);
12103 });
12104}
12105/**
12106 * Aborts all transactions on ancestors or descendants of the specified path.
12107 * Called when doing a set() or update() since we consider them incompatible
12108 * with transactions.
12109 *
12110 * @param path - Path for which we want to abort related transactions.
12111 */
12112function repoAbortTransactions(repo, path) {
12113 var affectedPath = treeGetPath(repoGetAncestorTransactionNode(repo, path));
12114 var transactionNode = treeSubTree(repo.transactionQueueTree_, path);
12115 treeForEachAncestor(transactionNode, function (node) {
12116 repoAbortTransactionsOnNode(repo, node);
12117 });
12118 repoAbortTransactionsOnNode(repo, transactionNode);
12119 treeForEachDescendant(transactionNode, function (node) {
12120 repoAbortTransactionsOnNode(repo, node);
12121 });
12122 return affectedPath;
12123}
12124/**
12125 * Abort transactions stored in this transaction queue node.
12126 *
12127 * @param node - Node to abort transactions for.
12128 */
12129function repoAbortTransactionsOnNode(repo, node) {
12130 var queue = treeGetValue(node);
12131 if (queue) {
12132 // Queue up the callbacks and fire them after cleaning up all of our
12133 // transaction state, since the callback could trigger more transactions
12134 // or sets.
12135 var callbacks = [];
12136 // Go through queue. Any already-sent transactions must be marked for
12137 // abort, while the unsent ones can be immediately aborted and removed.
12138 var events = [];
12139 var lastSent = -1;
12140 for (var i = 0; i < queue.length; i++) {
12141 if (queue[i].status === 3 /* SENT_NEEDS_ABORT */) ;
12142 else if (queue[i].status === 1 /* SENT */) {
12143 assert(lastSent === i - 1, 'All SENT items should be at beginning of queue.');
12144 lastSent = i;
12145 // Mark transaction for abort when it comes back.
12146 queue[i].status = 3 /* SENT_NEEDS_ABORT */;
12147 queue[i].abortReason = 'set';
12148 }
12149 else {
12150 assert(queue[i].status === 0 /* RUN */, 'Unexpected transaction status in abort');
12151 // We can abort it immediately.
12152 queue[i].unwatcher();
12153 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId, true));
12154 if (queue[i].onComplete) {
12155 callbacks.push(queue[i].onComplete.bind(null, new Error('set'), false, null));
12156 }
12157 }
12158 }
12159 if (lastSent === -1) {
12160 // We're not waiting for any sent transactions. We can clear the queue.
12161 treeSetValue(node, undefined);
12162 }
12163 else {
12164 // Remove the transactions we aborted.
12165 queue.length = lastSent + 1;
12166 }
12167 // Now fire the callbacks.
12168 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, treeGetPath(node), events);
12169 for (var i = 0; i < callbacks.length; i++) {
12170 exceptionGuard(callbacks[i]);
12171 }
12172 }
12173}
12174
12175/**
12176 * @license
12177 * Copyright 2017 Google LLC
12178 *
12179 * Licensed under the Apache License, Version 2.0 (the "License");
12180 * you may not use this file except in compliance with the License.
12181 * You may obtain a copy of the License at
12182 *
12183 * http://www.apache.org/licenses/LICENSE-2.0
12184 *
12185 * Unless required by applicable law or agreed to in writing, software
12186 * distributed under the License is distributed on an "AS IS" BASIS,
12187 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12188 * See the License for the specific language governing permissions and
12189 * limitations under the License.
12190 */
12191function decodePath(pathString) {
12192 var pathStringDecoded = '';
12193 var pieces = pathString.split('/');
12194 for (var i = 0; i < pieces.length; i++) {
12195 if (pieces[i].length > 0) {
12196 var piece = pieces[i];
12197 try {
12198 piece = decodeURIComponent(piece.replace(/\+/g, ' '));
12199 }
12200 catch (e) { }
12201 pathStringDecoded += '/' + piece;
12202 }
12203 }
12204 return pathStringDecoded;
12205}
12206/**
12207 * @returns key value hash
12208 */
12209function decodeQuery(queryString) {
12210 var e_1, _a;
12211 var results = {};
12212 if (queryString.charAt(0) === '?') {
12213 queryString = queryString.substring(1);
12214 }
12215 try {
12216 for (var _b = __values(queryString.split('&')), _c = _b.next(); !_c.done; _c = _b.next()) {
12217 var segment = _c.value;
12218 if (segment.length === 0) {
12219 continue;
12220 }
12221 var kv = segment.split('=');
12222 if (kv.length === 2) {
12223 results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
12224 }
12225 else {
12226 warn("Invalid query segment '" + segment + "' in query '" + queryString + "'");
12227 }
12228 }
12229 }
12230 catch (e_1_1) { e_1 = { error: e_1_1 }; }
12231 finally {
12232 try {
12233 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
12234 }
12235 finally { if (e_1) throw e_1.error; }
12236 }
12237 return results;
12238}
12239var parseRepoInfo = function (dataURL, nodeAdmin) {
12240 var parsedUrl = parseDatabaseURL(dataURL), namespace = parsedUrl.namespace;
12241 if (parsedUrl.domain === 'firebase.com') {
12242 fatal(parsedUrl.host +
12243 ' is no longer supported. ' +
12244 'Please use <YOUR FIREBASE>.firebaseio.com instead');
12245 }
12246 // Catch common error of uninitialized namespace value.
12247 if ((!namespace || namespace === 'undefined') &&
12248 parsedUrl.domain !== 'localhost') {
12249 fatal('Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com');
12250 }
12251 if (!parsedUrl.secure) {
12252 warnIfPageIsSecure();
12253 }
12254 var webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
12255 return {
12256 repoInfo: new RepoInfo(parsedUrl.host, parsedUrl.secure, namespace, nodeAdmin, webSocketOnly,
12257 /*persistenceKey=*/ '',
12258 /*includeNamespaceInQueryParams=*/ namespace !== parsedUrl.subdomain),
12259 path: new Path(parsedUrl.pathString)
12260 };
12261};
12262var parseDatabaseURL = function (dataURL) {
12263 // Default to empty strings in the event of a malformed string.
12264 var host = '', domain = '', subdomain = '', pathString = '', namespace = '';
12265 // Always default to SSL, unless otherwise specified.
12266 var secure = true, scheme = 'https', port = 443;
12267 // Don't do any validation here. The caller is responsible for validating the result of parsing.
12268 if (typeof dataURL === 'string') {
12269 // Parse scheme.
12270 var colonInd = dataURL.indexOf('//');
12271 if (colonInd >= 0) {
12272 scheme = dataURL.substring(0, colonInd - 1);
12273 dataURL = dataURL.substring(colonInd + 2);
12274 }
12275 // Parse host, path, and query string.
12276 var slashInd = dataURL.indexOf('/');
12277 if (slashInd === -1) {
12278 slashInd = dataURL.length;
12279 }
12280 var questionMarkInd = dataURL.indexOf('?');
12281 if (questionMarkInd === -1) {
12282 questionMarkInd = dataURL.length;
12283 }
12284 host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
12285 if (slashInd < questionMarkInd) {
12286 // For pathString, questionMarkInd will always come after slashInd
12287 pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
12288 }
12289 var queryParams = decodeQuery(dataURL.substring(Math.min(dataURL.length, questionMarkInd)));
12290 // If we have a port, use scheme for determining if it's secure.
12291 colonInd = host.indexOf(':');
12292 if (colonInd >= 0) {
12293 secure = scheme === 'https' || scheme === 'wss';
12294 port = parseInt(host.substring(colonInd + 1), 10);
12295 }
12296 else {
12297 colonInd = host.length;
12298 }
12299 var hostWithoutPort = host.slice(0, colonInd);
12300 if (hostWithoutPort.toLowerCase() === 'localhost') {
12301 domain = 'localhost';
12302 }
12303 else if (hostWithoutPort.split('.').length <= 2) {
12304 domain = hostWithoutPort;
12305 }
12306 else {
12307 // Interpret the subdomain of a 3 or more component URL as the namespace name.
12308 var dotInd = host.indexOf('.');
12309 subdomain = host.substring(0, dotInd).toLowerCase();
12310 domain = host.substring(dotInd + 1);
12311 // Normalize namespaces to lowercase to share storage / connection.
12312 namespace = subdomain;
12313 }
12314 // Always treat the value of the `ns` as the namespace name if it is present.
12315 if ('ns' in queryParams) {
12316 namespace = queryParams['ns'];
12317 }
12318 }
12319 return {
12320 host: host,
12321 port: port,
12322 domain: domain,
12323 subdomain: subdomain,
12324 secure: secure,
12325 scheme: scheme,
12326 pathString: pathString,
12327 namespace: namespace
12328 };
12329};
12330
12331/**
12332 * @license
12333 * Copyright 2017 Google LLC
12334 *
12335 * Licensed under the Apache License, Version 2.0 (the "License");
12336 * you may not use this file except in compliance with the License.
12337 * You may obtain a copy of the License at
12338 *
12339 * http://www.apache.org/licenses/LICENSE-2.0
12340 *
12341 * Unless required by applicable law or agreed to in writing, software
12342 * distributed under the License is distributed on an "AS IS" BASIS,
12343 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12344 * See the License for the specific language governing permissions and
12345 * limitations under the License.
12346 */
12347/**
12348 * Encapsulates the data needed to raise an event
12349 */
12350var DataEvent = /** @class */ (function () {
12351 /**
12352 * @param eventType - One of: value, child_added, child_changed, child_moved, child_removed
12353 * @param eventRegistration - The function to call to with the event data. User provided
12354 * @param snapshot - The data backing the event
12355 * @param prevName - Optional, the name of the previous child for child_* events.
12356 */
12357 function DataEvent(eventType, eventRegistration, snapshot, prevName) {
12358 this.eventType = eventType;
12359 this.eventRegistration = eventRegistration;
12360 this.snapshot = snapshot;
12361 this.prevName = prevName;
12362 }
12363 DataEvent.prototype.getPath = function () {
12364 var ref = this.snapshot.ref;
12365 if (this.eventType === 'value') {
12366 return ref._path;
12367 }
12368 else {
12369 return ref.parent._path;
12370 }
12371 };
12372 DataEvent.prototype.getEventType = function () {
12373 return this.eventType;
12374 };
12375 DataEvent.prototype.getEventRunner = function () {
12376 return this.eventRegistration.getEventRunner(this);
12377 };
12378 DataEvent.prototype.toString = function () {
12379 return (this.getPath().toString() +
12380 ':' +
12381 this.eventType +
12382 ':' +
12383 stringify(this.snapshot.exportVal()));
12384 };
12385 return DataEvent;
12386}());
12387var CancelEvent = /** @class */ (function () {
12388 function CancelEvent(eventRegistration, error, path) {
12389 this.eventRegistration = eventRegistration;
12390 this.error = error;
12391 this.path = path;
12392 }
12393 CancelEvent.prototype.getPath = function () {
12394 return this.path;
12395 };
12396 CancelEvent.prototype.getEventType = function () {
12397 return 'cancel';
12398 };
12399 CancelEvent.prototype.getEventRunner = function () {
12400 return this.eventRegistration.getEventRunner(this);
12401 };
12402 CancelEvent.prototype.toString = function () {
12403 return this.path.toString() + ':cancel';
12404 };
12405 return CancelEvent;
12406}());
12407
12408/**
12409 * @license
12410 * Copyright 2017 Google LLC
12411 *
12412 * Licensed under the Apache License, Version 2.0 (the "License");
12413 * you may not use this file except in compliance with the License.
12414 * You may obtain a copy of the License at
12415 *
12416 * http://www.apache.org/licenses/LICENSE-2.0
12417 *
12418 * Unless required by applicable law or agreed to in writing, software
12419 * distributed under the License is distributed on an "AS IS" BASIS,
12420 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12421 * See the License for the specific language governing permissions and
12422 * limitations under the License.
12423 */
12424/**
12425 * A wrapper class that converts events from the database@exp SDK to the legacy
12426 * Database SDK. Events are not converted directly as event registration relies
12427 * on reference comparison of the original user callback (see `matches()`) and
12428 * relies on equality of the legacy SDK's `context` object.
12429 */
12430var CallbackContext = /** @class */ (function () {
12431 function CallbackContext(snapshotCallback, cancelCallback) {
12432 this.snapshotCallback = snapshotCallback;
12433 this.cancelCallback = cancelCallback;
12434 }
12435 CallbackContext.prototype.onValue = function (expDataSnapshot, previousChildName) {
12436 this.snapshotCallback.call(null, expDataSnapshot, previousChildName);
12437 };
12438 CallbackContext.prototype.onCancel = function (error) {
12439 assert(this.hasCancelCallback, 'Raising a cancel event on a listener with no cancel callback');
12440 return this.cancelCallback.call(null, error);
12441 };
12442 Object.defineProperty(CallbackContext.prototype, "hasCancelCallback", {
12443 get: function () {
12444 return !!this.cancelCallback;
12445 },
12446 enumerable: false,
12447 configurable: true
12448 });
12449 CallbackContext.prototype.matches = function (other) {
12450 return (this.snapshotCallback === other.snapshotCallback ||
12451 (this.snapshotCallback.userCallback !== undefined &&
12452 this.snapshotCallback.userCallback ===
12453 other.snapshotCallback.userCallback &&
12454 this.snapshotCallback.context === other.snapshotCallback.context));
12455 };
12456 return CallbackContext;
12457}());
12458
12459/**
12460 * @license
12461 * Copyright 2021 Google LLC
12462 *
12463 * Licensed under the Apache License, Version 2.0 (the "License");
12464 * you may not use this file except in compliance with the License.
12465 * You may obtain a copy of the License at
12466 *
12467 * http://www.apache.org/licenses/LICENSE-2.0
12468 *
12469 * Unless required by applicable law or agreed to in writing, software
12470 * distributed under the License is distributed on an "AS IS" BASIS,
12471 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12472 * See the License for the specific language governing permissions and
12473 * limitations under the License.
12474 */
12475/**
12476 * The `onDisconnect` class allows you to write or clear data when your client
12477 * disconnects from the Database server. These updates occur whether your
12478 * client disconnects cleanly or not, so you can rely on them to clean up data
12479 * even if a connection is dropped or a client crashes.
12480 *
12481 * The `onDisconnect` class is most commonly used to manage presence in
12482 * applications where it is useful to detect how many clients are connected and
12483 * when other clients disconnect. See
12484 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12485 * for more information.
12486 *
12487 * To avoid problems when a connection is dropped before the requests can be
12488 * transferred to the Database server, these functions should be called before
12489 * writing any data.
12490 *
12491 * Note that `onDisconnect` operations are only triggered once. If you want an
12492 * operation to occur each time a disconnect occurs, you'll need to re-establish
12493 * the `onDisconnect` operations each time you reconnect.
12494 */
12495var OnDisconnect = /** @class */ (function () {
12496 /** @hideconstructor */
12497 function OnDisconnect(_repo, _path) {
12498 this._repo = _repo;
12499 this._path = _path;
12500 }
12501 /**
12502 * Cancels all previously queued `onDisconnect()` set or update events for this
12503 * location and all children.
12504 *
12505 * If a write has been queued for this location via a `set()` or `update()` at a
12506 * parent location, the write at this location will be canceled, though writes
12507 * to sibling locations will still occur.
12508 *
12509 * @returns Resolves when synchronization to the server is complete.
12510 */
12511 OnDisconnect.prototype.cancel = function () {
12512 var deferred = new Deferred();
12513 repoOnDisconnectCancel(this._repo, this._path, deferred.wrapCallback(function () { }));
12514 return deferred.promise;
12515 };
12516 /**
12517 * Ensures the data at this location is deleted when the client is disconnected
12518 * (due to closing the browser, navigating to a new page, or network issues).
12519 *
12520 * @returns Resolves when synchronization to the server is complete.
12521 */
12522 OnDisconnect.prototype.remove = function () {
12523 validateWritablePath('OnDisconnect.remove', this._path);
12524 var deferred = new Deferred();
12525 repoOnDisconnectSet(this._repo, this._path, null, deferred.wrapCallback(function () { }));
12526 return deferred.promise;
12527 };
12528 /**
12529 * Ensures the data at this location is set to the specified value when the
12530 * client is disconnected (due to closing the browser, navigating to a new page,
12531 * or network issues).
12532 *
12533 * `set()` is especially useful for implementing "presence" systems, where a
12534 * value should be changed or cleared when a user disconnects so that they
12535 * appear "offline" to other users. See
12536 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12537 * for more information.
12538 *
12539 * Note that `onDisconnect` operations are only triggered once. If you want an
12540 * operation to occur each time a disconnect occurs, you'll need to re-establish
12541 * the `onDisconnect` operations each time.
12542 *
12543 * @param value - The value to be written to this location on disconnect (can
12544 * be an object, array, string, number, boolean, or null).
12545 * @returns Resolves when synchronization to the Database is complete.
12546 */
12547 OnDisconnect.prototype.set = function (value) {
12548 validateWritablePath('OnDisconnect.set', this._path);
12549 validateFirebaseDataArg('OnDisconnect.set', value, this._path, false);
12550 var deferred = new Deferred();
12551 repoOnDisconnectSet(this._repo, this._path, value, deferred.wrapCallback(function () { }));
12552 return deferred.promise;
12553 };
12554 /**
12555 * Ensures the data at this location is set to the specified value and priority
12556 * when the client is disconnected (due to closing the browser, navigating to a
12557 * new page, or network issues).
12558 *
12559 * @param value - The value to be written to this location on disconnect (can
12560 * be an object, array, string, number, boolean, or null).
12561 * @param priority - The priority to be written (string, number, or null).
12562 * @returns Resolves when synchronization to the Database is complete.
12563 */
12564 OnDisconnect.prototype.setWithPriority = function (value, priority) {
12565 validateWritablePath('OnDisconnect.setWithPriority', this._path);
12566 validateFirebaseDataArg('OnDisconnect.setWithPriority', value, this._path, false);
12567 validatePriority('OnDisconnect.setWithPriority', priority, false);
12568 var deferred = new Deferred();
12569 repoOnDisconnectSetWithPriority(this._repo, this._path, value, priority, deferred.wrapCallback(function () { }));
12570 return deferred.promise;
12571 };
12572 /**
12573 * Writes multiple values at this location when the client is disconnected (due
12574 * to closing the browser, navigating to a new page, or network issues).
12575 *
12576 * The `values` argument contains multiple property-value pairs that will be
12577 * written to the Database together. Each child property can either be a simple
12578 * property (for example, "name") or a relative path (for example, "name/first")
12579 * from the current location to the data to update.
12580 *
12581 * As opposed to the `set()` method, `update()` can be use to selectively update
12582 * only the referenced properties at the current location (instead of replacing
12583 * all the child properties at the current location).
12584 *
12585 * @param values - Object containing multiple values.
12586 * @returns Resolves when synchronization to the Database is complete.
12587 */
12588 OnDisconnect.prototype.update = function (values) {
12589 validateWritablePath('OnDisconnect.update', this._path);
12590 validateFirebaseMergeDataArg('OnDisconnect.update', values, this._path, false);
12591 var deferred = new Deferred();
12592 repoOnDisconnectUpdate(this._repo, this._path, values, deferred.wrapCallback(function () { }));
12593 return deferred.promise;
12594 };
12595 return OnDisconnect;
12596}());
12597
12598/**
12599 * @license
12600 * Copyright 2020 Google LLC
12601 *
12602 * Licensed under the Apache License, Version 2.0 (the "License");
12603 * you may not use this file except in compliance with the License.
12604 * You may obtain a copy of the License at
12605 *
12606 * http://www.apache.org/licenses/LICENSE-2.0
12607 *
12608 * Unless required by applicable law or agreed to in writing, software
12609 * distributed under the License is distributed on an "AS IS" BASIS,
12610 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12611 * See the License for the specific language governing permissions and
12612 * limitations under the License.
12613 */
12614/**
12615 * @internal
12616 */
12617var QueryImpl = /** @class */ (function () {
12618 /**
12619 * @hideconstructor
12620 */
12621 function QueryImpl(_repo, _path, _queryParams, _orderByCalled) {
12622 this._repo = _repo;
12623 this._path = _path;
12624 this._queryParams = _queryParams;
12625 this._orderByCalled = _orderByCalled;
12626 }
12627 Object.defineProperty(QueryImpl.prototype, "key", {
12628 get: function () {
12629 if (pathIsEmpty(this._path)) {
12630 return null;
12631 }
12632 else {
12633 return pathGetBack(this._path);
12634 }
12635 },
12636 enumerable: false,
12637 configurable: true
12638 });
12639 Object.defineProperty(QueryImpl.prototype, "ref", {
12640 get: function () {
12641 return new ReferenceImpl(this._repo, this._path);
12642 },
12643 enumerable: false,
12644 configurable: true
12645 });
12646 Object.defineProperty(QueryImpl.prototype, "_queryIdentifier", {
12647 get: function () {
12648 var obj = queryParamsGetQueryObject(this._queryParams);
12649 var id = ObjectToUniqueKey(obj);
12650 return id === '{}' ? 'default' : id;
12651 },
12652 enumerable: false,
12653 configurable: true
12654 });
12655 Object.defineProperty(QueryImpl.prototype, "_queryObject", {
12656 /**
12657 * An object representation of the query parameters used by this Query.
12658 */
12659 get: function () {
12660 return queryParamsGetQueryObject(this._queryParams);
12661 },
12662 enumerable: false,
12663 configurable: true
12664 });
12665 QueryImpl.prototype.isEqual = function (other) {
12666 other = getModularInstance(other);
12667 if (!(other instanceof QueryImpl)) {
12668 return false;
12669 }
12670 var sameRepo = this._repo === other._repo;
12671 var samePath = pathEquals(this._path, other._path);
12672 var sameQueryIdentifier = this._queryIdentifier === other._queryIdentifier;
12673 return sameRepo && samePath && sameQueryIdentifier;
12674 };
12675 QueryImpl.prototype.toJSON = function () {
12676 return this.toString();
12677 };
12678 QueryImpl.prototype.toString = function () {
12679 return this._repo.toString() + pathToUrlEncodedString(this._path);
12680 };
12681 return QueryImpl;
12682}());
12683/**
12684 * Validates that no other order by call has been made
12685 */
12686function validateNoPreviousOrderByCall(query, fnName) {
12687 if (query._orderByCalled === true) {
12688 throw new Error(fnName + ": You can't combine multiple orderBy calls.");
12689 }
12690}
12691/**
12692 * Validates start/end values for queries.
12693 */
12694function validateQueryEndpoints(params) {
12695 var startNode = null;
12696 var endNode = null;
12697 if (params.hasStart()) {
12698 startNode = params.getIndexStartValue();
12699 }
12700 if (params.hasEnd()) {
12701 endNode = params.getIndexEndValue();
12702 }
12703 if (params.getIndex() === KEY_INDEX) {
12704 var tooManyArgsError = 'Query: When ordering by key, you may only pass one argument to ' +
12705 'startAt(), endAt(), or equalTo().';
12706 var wrongArgTypeError = 'Query: When ordering by key, the argument passed to startAt(), startAfter(), ' +
12707 'endAt(), endBefore(), or equalTo() must be a string.';
12708 if (params.hasStart()) {
12709 var startName = params.getIndexStartName();
12710 if (startName !== MIN_NAME) {
12711 throw new Error(tooManyArgsError);
12712 }
12713 else if (typeof startNode !== 'string') {
12714 throw new Error(wrongArgTypeError);
12715 }
12716 }
12717 if (params.hasEnd()) {
12718 var endName = params.getIndexEndName();
12719 if (endName !== MAX_NAME) {
12720 throw new Error(tooManyArgsError);
12721 }
12722 else if (typeof endNode !== 'string') {
12723 throw new Error(wrongArgTypeError);
12724 }
12725 }
12726 }
12727 else if (params.getIndex() === PRIORITY_INDEX) {
12728 if ((startNode != null && !isValidPriority(startNode)) ||
12729 (endNode != null && !isValidPriority(endNode))) {
12730 throw new Error('Query: When ordering by priority, the first argument passed to startAt(), ' +
12731 'startAfter() endAt(), endBefore(), or equalTo() must be a valid priority value ' +
12732 '(null, a number, or a string).');
12733 }
12734 }
12735 else {
12736 assert(params.getIndex() instanceof PathIndex ||
12737 params.getIndex() === VALUE_INDEX, 'unknown index type.');
12738 if ((startNode != null && typeof startNode === 'object') ||
12739 (endNode != null && typeof endNode === 'object')) {
12740 throw new Error('Query: First argument passed to startAt(), startAfter(), endAt(), endBefore(), or ' +
12741 'equalTo() cannot be an object.');
12742 }
12743 }
12744}
12745/**
12746 * Validates that limit* has been called with the correct combination of parameters
12747 */
12748function validateLimit(params) {
12749 if (params.hasStart() &&
12750 params.hasEnd() &&
12751 params.hasLimit() &&
12752 !params.hasAnchoredLimit()) {
12753 throw new Error("Query: Can't combine startAt(), startAfter(), endAt(), endBefore(), and limit(). Use " +
12754 'limitToFirst() or limitToLast() instead.');
12755 }
12756}
12757/**
12758 * @internal
12759 */
12760var ReferenceImpl = /** @class */ (function (_super) {
12761 __extends(ReferenceImpl, _super);
12762 /** @hideconstructor */
12763 function ReferenceImpl(repo, path) {
12764 return _super.call(this, repo, path, new QueryParams(), false) || this;
12765 }
12766 Object.defineProperty(ReferenceImpl.prototype, "parent", {
12767 get: function () {
12768 var parentPath = pathParent(this._path);
12769 return parentPath === null
12770 ? null
12771 : new ReferenceImpl(this._repo, parentPath);
12772 },
12773 enumerable: false,
12774 configurable: true
12775 });
12776 Object.defineProperty(ReferenceImpl.prototype, "root", {
12777 get: function () {
12778 var ref = this;
12779 while (ref.parent !== null) {
12780 ref = ref.parent;
12781 }
12782 return ref;
12783 },
12784 enumerable: false,
12785 configurable: true
12786 });
12787 return ReferenceImpl;
12788}(QueryImpl));
12789/**
12790 * A `DataSnapshot` contains data from a Database location.
12791 *
12792 * Any time you read data from the Database, you receive the data as a
12793 * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach
12794 * with `on()` or `once()`. You can extract the contents of the snapshot as a
12795 * JavaScript object by calling the `val()` method. Alternatively, you can
12796 * traverse into the snapshot by calling `child()` to return child snapshots
12797 * (which you could then call `val()` on).
12798 *
12799 * A `DataSnapshot` is an efficiently generated, immutable copy of the data at
12800 * a Database location. It cannot be modified and will never change (to modify
12801 * data, you always call the `set()` method on a `Reference` directly).
12802 */
12803var DataSnapshot = /** @class */ (function () {
12804 /**
12805 * @param _node - A SnapshotNode to wrap.
12806 * @param ref - The location this snapshot came from.
12807 * @param _index - The iteration order for this snapshot
12808 * @hideconstructor
12809 */
12810 function DataSnapshot(_node,
12811 /**
12812 * The location of this DataSnapshot.
12813 */
12814 ref, _index) {
12815 this._node = _node;
12816 this.ref = ref;
12817 this._index = _index;
12818 }
12819 Object.defineProperty(DataSnapshot.prototype, "priority", {
12820 /**
12821 * Gets the priority value of the data in this `DataSnapshot`.
12822 *
12823 * Applications need not use priority but can order collections by
12824 * ordinary properties (see
12825 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data |Sorting and filtering data}
12826 * ).
12827 */
12828 get: function () {
12829 // typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
12830 return this._node.getPriority().val();
12831 },
12832 enumerable: false,
12833 configurable: true
12834 });
12835 Object.defineProperty(DataSnapshot.prototype, "key", {
12836 /**
12837 * The key (last part of the path) of the location of this `DataSnapshot`.
12838 *
12839 * The last token in a Database location is considered its key. For example,
12840 * "ada" is the key for the /users/ada/ node. Accessing the key on any
12841 * `DataSnapshot` will return the key for the location that generated it.
12842 * However, accessing the key on the root URL of a Database will return
12843 * `null`.
12844 */
12845 get: function () {
12846 return this.ref.key;
12847 },
12848 enumerable: false,
12849 configurable: true
12850 });
12851 Object.defineProperty(DataSnapshot.prototype, "size", {
12852 /** Returns the number of child properties of this `DataSnapshot`. */
12853 get: function () {
12854 return this._node.numChildren();
12855 },
12856 enumerable: false,
12857 configurable: true
12858 });
12859 /**
12860 * Gets another `DataSnapshot` for the location at the specified relative path.
12861 *
12862 * Passing a relative path to the `child()` method of a DataSnapshot returns
12863 * another `DataSnapshot` for the location at the specified relative path. The
12864 * relative path can either be a simple child name (for example, "ada") or a
12865 * deeper, slash-separated path (for example, "ada/name/first"). If the child
12866 * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot`
12867 * whose value is `null`) is returned.
12868 *
12869 * @param path - A relative path to the location of child data.
12870 */
12871 DataSnapshot.prototype.child = function (path) {
12872 var childPath = new Path(path);
12873 var childRef = child(this.ref, path);
12874 return new DataSnapshot(this._node.getChild(childPath), childRef, PRIORITY_INDEX);
12875 };
12876 /**
12877 * Returns true if this `DataSnapshot` contains any data. It is slightly more
12878 * efficient than using `snapshot.val() !== null`.
12879 */
12880 DataSnapshot.prototype.exists = function () {
12881 return !this._node.isEmpty();
12882 };
12883 /**
12884 * Exports the entire contents of the DataSnapshot as a JavaScript object.
12885 *
12886 * The `exportVal()` method is similar to `val()`, except priority information
12887 * is included (if available), making it suitable for backing up your data.
12888 *
12889 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12890 * Array, string, number, boolean, or `null`).
12891 */
12892 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12893 DataSnapshot.prototype.exportVal = function () {
12894 return this._node.val(true);
12895 };
12896 /**
12897 * Enumerates the top-level children in the `DataSnapshot`.
12898 *
12899 * Because of the way JavaScript objects work, the ordering of data in the
12900 * JavaScript object returned by `val()` is not guaranteed to match the
12901 * ordering on the server nor the ordering of `onChildAdded()` events. That is
12902 * where `forEach()` comes in handy. It guarantees the children of a
12903 * `DataSnapshot` will be iterated in their query order.
12904 *
12905 * If no explicit `orderBy*()` method is used, results are returned
12906 * ordered by key (unless priorities are used, in which case, results are
12907 * returned by priority).
12908 *
12909 * @param action - A function that will be called for each child DataSnapshot.
12910 * The callback can return true to cancel further enumeration.
12911 * @returns true if enumeration was canceled due to your callback returning
12912 * true.
12913 */
12914 DataSnapshot.prototype.forEach = function (action) {
12915 var _this = this;
12916 if (this._node.isLeafNode()) {
12917 return false;
12918 }
12919 var childrenNode = this._node;
12920 // Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
12921 return !!childrenNode.forEachChild(this._index, function (key, node) {
12922 return action(new DataSnapshot(node, child(_this.ref, key), PRIORITY_INDEX));
12923 });
12924 };
12925 /**
12926 * Returns true if the specified child path has (non-null) data.
12927 *
12928 * @param path - A relative path to the location of a potential child.
12929 * @returns `true` if data exists at the specified child path; else
12930 * `false`.
12931 */
12932 DataSnapshot.prototype.hasChild = function (path) {
12933 var childPath = new Path(path);
12934 return !this._node.getChild(childPath).isEmpty();
12935 };
12936 /**
12937 * Returns whether or not the `DataSnapshot` has any non-`null` child
12938 * properties.
12939 *
12940 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
12941 * children. If it does, you can enumerate them using `forEach()`. If it
12942 * doesn't, then either this snapshot contains a primitive value (which can be
12943 * retrieved with `val()`) or it is empty (in which case, `val()` will return
12944 * `null`).
12945 *
12946 * @returns true if this snapshot has any children; else false.
12947 */
12948 DataSnapshot.prototype.hasChildren = function () {
12949 if (this._node.isLeafNode()) {
12950 return false;
12951 }
12952 else {
12953 return !this._node.isEmpty();
12954 }
12955 };
12956 /**
12957 * Returns a JSON-serializable representation of this object.
12958 */
12959 DataSnapshot.prototype.toJSON = function () {
12960 return this.exportVal();
12961 };
12962 /**
12963 * Extracts a JavaScript value from a `DataSnapshot`.
12964 *
12965 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
12966 * scalar type (string, number, or boolean), an array, or an object. It may
12967 * also return null, indicating that the `DataSnapshot` is empty (contains no
12968 * data).
12969 *
12970 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12971 * Array, string, number, boolean, or `null`).
12972 */
12973 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12974 DataSnapshot.prototype.val = function () {
12975 return this._node.val();
12976 };
12977 return DataSnapshot;
12978}());
12979/**
12980 *
12981 * Returns a `Reference` representing the location in the Database
12982 * corresponding to the provided path. If no path is provided, the `Reference`
12983 * will point to the root of the Database.
12984 *
12985 * @param db - The database instance to obtain a reference for.
12986 * @param path - Optional path representing the location the returned
12987 * `Reference` will point. If not provided, the returned `Reference` will
12988 * point to the root of the Database.
12989 * @returns If a path is provided, a `Reference`
12990 * pointing to the provided path. Otherwise, a `Reference` pointing to the
12991 * root of the Database.
12992 */
12993function ref(db, path) {
12994 db = getModularInstance(db);
12995 db._checkNotDeleted('ref');
12996 return path !== undefined ? child(db._root, path) : db._root;
12997}
12998/**
12999 * Returns a `Reference` representing the location in the Database
13000 * corresponding to the provided Firebase URL.
13001 *
13002 * An exception is thrown if the URL is not a valid Firebase Database URL or it
13003 * has a different domain than the current `Database` instance.
13004 *
13005 * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored
13006 * and are not applied to the returned `Reference`.
13007 *
13008 * @param db - The database instance to obtain a reference for.
13009 * @param url - The Firebase URL at which the returned `Reference` will
13010 * point.
13011 * @returns A `Reference` pointing to the provided
13012 * Firebase URL.
13013 */
13014function refFromURL(db, url) {
13015 db = getModularInstance(db);
13016 db._checkNotDeleted('refFromURL');
13017 var parsedURL = parseRepoInfo(url, db._repo.repoInfo_.nodeAdmin);
13018 validateUrl('refFromURL', parsedURL);
13019 var repoInfo = parsedURL.repoInfo;
13020 if (!db._repo.repoInfo_.isCustomHost() &&
13021 repoInfo.host !== db._repo.repoInfo_.host) {
13022 fatal('refFromURL' +
13023 ': Host name does not match the current database: ' +
13024 '(found ' +
13025 repoInfo.host +
13026 ' but expected ' +
13027 db._repo.repoInfo_.host +
13028 ')');
13029 }
13030 return ref(db, parsedURL.path.toString());
13031}
13032/**
13033 * Gets a `Reference` for the location at the specified relative path.
13034 *
13035 * The relative path can either be a simple child name (for example, "ada") or
13036 * a deeper slash-separated path (for example, "ada/name/first").
13037 *
13038 * @param parent - The parent location.
13039 * @param path - A relative path from this location to the desired child
13040 * location.
13041 * @returns The specified child location.
13042 */
13043function child(parent, path) {
13044 parent = getModularInstance(parent);
13045 if (pathGetFront(parent._path) === null) {
13046 validateRootPathString('child', 'path', path, false);
13047 }
13048 else {
13049 validatePathString('child', 'path', path, false);
13050 }
13051 return new ReferenceImpl(parent._repo, pathChild(parent._path, path));
13052}
13053/**
13054 * Returns an `OnDisconnect` object - see
13055 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
13056 * for more information on how to use it.
13057 *
13058 * @param ref - The reference to add OnDisconnect triggers for.
13059 */
13060function onDisconnect(ref) {
13061 ref = getModularInstance(ref);
13062 return new OnDisconnect(ref._repo, ref._path);
13063}
13064/**
13065 * Generates a new child location using a unique key and returns its
13066 * `Reference`.
13067 *
13068 * This is the most common pattern for adding data to a collection of items.
13069 *
13070 * If you provide a value to `push()`, the value is written to the
13071 * generated location. If you don't pass a value, nothing is written to the
13072 * database and the child remains empty (but you can use the `Reference`
13073 * elsewhere).
13074 *
13075 * The unique keys generated by `push()` are ordered by the current time, so the
13076 * resulting list of items is chronologically sorted. The keys are also
13077 * designed to be unguessable (they contain 72 random bits of entropy).
13078 *
13079 * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}
13080 * </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}
13081 *
13082 * @param parent - The parent location.
13083 * @param value - Optional value to be written at the generated location.
13084 * @returns Combined `Promise` and `Reference`; resolves when write is complete,
13085 * but can be used immediately as the `Reference` to the child location.
13086 */
13087function push(parent, value) {
13088 parent = getModularInstance(parent);
13089 validateWritablePath('push', parent._path);
13090 validateFirebaseDataArg('push', value, parent._path, true);
13091 var now = repoServerTime(parent._repo);
13092 var name = nextPushId(now);
13093 // push() returns a ThennableReference whose promise is fulfilled with a
13094 // regular Reference. We use child() to create handles to two different
13095 // references. The first is turned into a ThennableReference below by adding
13096 // then() and catch() methods and is used as the return value of push(). The
13097 // second remains a regular Reference and is used as the fulfilled value of
13098 // the first ThennableReference.
13099 var thennablePushRef = child(parent, name);
13100 var pushRef = child(parent, name);
13101 var promise;
13102 if (value != null) {
13103 promise = set(pushRef, value).then(function () { return pushRef; });
13104 }
13105 else {
13106 promise = Promise.resolve(pushRef);
13107 }
13108 thennablePushRef.then = promise.then.bind(promise);
13109 thennablePushRef.catch = promise.then.bind(promise, undefined);
13110 return thennablePushRef;
13111}
13112/**
13113 * Removes the data at this Database location.
13114 *
13115 * Any data at child locations will also be deleted.
13116 *
13117 * The effect of the remove will be visible immediately and the corresponding
13118 * event 'value' will be triggered. Synchronization of the remove to the
13119 * Firebase servers will also be started, and the returned Promise will resolve
13120 * when complete. If provided, the onComplete callback will be called
13121 * asynchronously after synchronization has finished.
13122 *
13123 * @param ref - The location to remove.
13124 * @returns Resolves when remove on server is complete.
13125 */
13126function remove(ref) {
13127 validateWritablePath('remove', ref._path);
13128 return set(ref, null);
13129}
13130/**
13131 * Writes data to this Database location.
13132 *
13133 * This will overwrite any data at this location and all child locations.
13134 *
13135 * The effect of the write will be visible immediately, and the corresponding
13136 * events ("value", "child_added", etc.) will be triggered. Synchronization of
13137 * the data to the Firebase servers will also be started, and the returned
13138 * Promise will resolve when complete. If provided, the `onComplete` callback
13139 * will be called asynchronously after synchronization has finished.
13140 *
13141 * Passing `null` for the new value is equivalent to calling `remove()`; namely,
13142 * all data at this location and all child locations will be deleted.
13143 *
13144 * `set()` will remove any priority stored at this location, so if priority is
13145 * meant to be preserved, you need to use `setWithPriority()` instead.
13146 *
13147 * Note that modifying data with `set()` will cancel any pending transactions
13148 * at that location, so extreme care should be taken if mixing `set()` and
13149 * `transaction()` to modify the same data.
13150 *
13151 * A single `set()` will generate a single "value" event at the location where
13152 * the `set()` was performed.
13153 *
13154 * @param ref - The location to write to.
13155 * @param value - The value to be written (string, number, boolean, object,
13156 * array, or null).
13157 * @returns Resolves when write to server is complete.
13158 */
13159function set(ref, value) {
13160 ref = getModularInstance(ref);
13161 validateWritablePath('set', ref._path);
13162 validateFirebaseDataArg('set', value, ref._path, false);
13163 var deferred = new Deferred();
13164 repoSetWithPriority(ref._repo, ref._path, value,
13165 /*priority=*/ null, deferred.wrapCallback(function () { }));
13166 return deferred.promise;
13167}
13168/**
13169 * Sets a priority for the data at this Database location.
13170 *
13171 * Applications need not use priority but can order collections by
13172 * ordinary properties (see
13173 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13174 * ).
13175 *
13176 * @param ref - The location to write to.
13177 * @param priority - The priority to be written (string, number, or null).
13178 * @returns Resolves when write to server is complete.
13179 */
13180function setPriority(ref, priority) {
13181 ref = getModularInstance(ref);
13182 validateWritablePath('setPriority', ref._path);
13183 validatePriority('setPriority', priority, false);
13184 var deferred = new Deferred();
13185 repoSetWithPriority(ref._repo, pathChild(ref._path, '.priority'), priority, null, deferred.wrapCallback(function () { }));
13186 return deferred.promise;
13187}
13188/**
13189 * Writes data the Database location. Like `set()` but also specifies the
13190 * priority for that data.
13191 *
13192 * Applications need not use priority but can order collections by
13193 * ordinary properties (see
13194 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13195 * ).
13196 *
13197 * @param ref - The location to write to.
13198 * @param value - The value to be written (string, number, boolean, object,
13199 * array, or null).
13200 * @param priority - The priority to be written (string, number, or null).
13201 * @returns Resolves when write to server is complete.
13202 */
13203function setWithPriority(ref, value, priority) {
13204 validateWritablePath('setWithPriority', ref._path);
13205 validateFirebaseDataArg('setWithPriority', value, ref._path, false);
13206 validatePriority('setWithPriority', priority, false);
13207 if (ref.key === '.length' || ref.key === '.keys') {
13208 throw 'setWithPriority failed: ' + ref.key + ' is a read-only object.';
13209 }
13210 var deferred = new Deferred();
13211 repoSetWithPriority(ref._repo, ref._path, value, priority, deferred.wrapCallback(function () { }));
13212 return deferred.promise;
13213}
13214/**
13215 * Writes multiple values to the Database at once.
13216 *
13217 * The `values` argument contains multiple property-value pairs that will be
13218 * written to the Database together. Each child property can either be a simple
13219 * property (for example, "name") or a relative path (for example,
13220 * "name/first") from the current location to the data to update.
13221 *
13222 * As opposed to the `set()` method, `update()` can be use to selectively update
13223 * only the referenced properties at the current location (instead of replacing
13224 * all the child properties at the current location).
13225 *
13226 * The effect of the write will be visible immediately, and the corresponding
13227 * events ('value', 'child_added', etc.) will be triggered. Synchronization of
13228 * the data to the Firebase servers will also be started, and the returned
13229 * Promise will resolve when complete. If provided, the `onComplete` callback
13230 * will be called asynchronously after synchronization has finished.
13231 *
13232 * A single `update()` will generate a single "value" event at the location
13233 * where the `update()` was performed, regardless of how many children were
13234 * modified.
13235 *
13236 * Note that modifying data with `update()` will cancel any pending
13237 * transactions at that location, so extreme care should be taken if mixing
13238 * `update()` and `transaction()` to modify the same data.
13239 *
13240 * Passing `null` to `update()` will remove the data at this location.
13241 *
13242 * See
13243 * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}.
13244 *
13245 * @param ref - The location to write to.
13246 * @param values - Object containing multiple values.
13247 * @returns Resolves when update on server is complete.
13248 */
13249function update(ref, values) {
13250 validateFirebaseMergeDataArg('update', values, ref._path, false);
13251 var deferred = new Deferred();
13252 repoUpdate(ref._repo, ref._path, values, deferred.wrapCallback(function () { }));
13253 return deferred.promise;
13254}
13255/**
13256 * Gets the most up-to-date result for this query.
13257 *
13258 * @param query - The query to run.
13259 * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is
13260 * available, or rejects if the client is unable to return a value (e.g., if the
13261 * server is unreachable and there is nothing cached).
13262 */
13263function get(query) {
13264 query = getModularInstance(query);
13265 return repoGetValue(query._repo, query).then(function (node) {
13266 return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
13267 });
13268}
13269/**
13270 * Represents registration for 'value' events.
13271 */
13272var ValueEventRegistration = /** @class */ (function () {
13273 function ValueEventRegistration(callbackContext) {
13274 this.callbackContext = callbackContext;
13275 }
13276 ValueEventRegistration.prototype.respondsTo = function (eventType) {
13277 return eventType === 'value';
13278 };
13279 ValueEventRegistration.prototype.createEvent = function (change, query) {
13280 var index = query._queryParams.getIndex();
13281 return new DataEvent('value', this, new DataSnapshot(change.snapshotNode, new ReferenceImpl(query._repo, query._path), index));
13282 };
13283 ValueEventRegistration.prototype.getEventRunner = function (eventData) {
13284 var _this = this;
13285 if (eventData.getEventType() === 'cancel') {
13286 return function () {
13287 return _this.callbackContext.onCancel(eventData.error);
13288 };
13289 }
13290 else {
13291 return function () {
13292 return _this.callbackContext.onValue(eventData.snapshot, null);
13293 };
13294 }
13295 };
13296 ValueEventRegistration.prototype.createCancelEvent = function (error, path) {
13297 if (this.callbackContext.hasCancelCallback) {
13298 return new CancelEvent(this, error, path);
13299 }
13300 else {
13301 return null;
13302 }
13303 };
13304 ValueEventRegistration.prototype.matches = function (other) {
13305 if (!(other instanceof ValueEventRegistration)) {
13306 return false;
13307 }
13308 else if (!other.callbackContext || !this.callbackContext) {
13309 // If no callback specified, we consider it to match any callback.
13310 return true;
13311 }
13312 else {
13313 return other.callbackContext.matches(this.callbackContext);
13314 }
13315 };
13316 ValueEventRegistration.prototype.hasAnyCallback = function () {
13317 return this.callbackContext !== null;
13318 };
13319 return ValueEventRegistration;
13320}());
13321/**
13322 * Represents the registration of a child_x event.
13323 */
13324var ChildEventRegistration = /** @class */ (function () {
13325 function ChildEventRegistration(eventType, callbackContext) {
13326 this.eventType = eventType;
13327 this.callbackContext = callbackContext;
13328 }
13329 ChildEventRegistration.prototype.respondsTo = function (eventType) {
13330 var eventToCheck = eventType === 'children_added' ? 'child_added' : eventType;
13331 eventToCheck =
13332 eventToCheck === 'children_removed' ? 'child_removed' : eventToCheck;
13333 return this.eventType === eventToCheck;
13334 };
13335 ChildEventRegistration.prototype.createCancelEvent = function (error, path) {
13336 if (this.callbackContext.hasCancelCallback) {
13337 return new CancelEvent(this, error, path);
13338 }
13339 else {
13340 return null;
13341 }
13342 };
13343 ChildEventRegistration.prototype.createEvent = function (change, query) {
13344 assert(change.childName != null, 'Child events should have a childName.');
13345 var childRef = child(new ReferenceImpl(query._repo, query._path), change.childName);
13346 var index = query._queryParams.getIndex();
13347 return new DataEvent(change.type, this, new DataSnapshot(change.snapshotNode, childRef, index), change.prevName);
13348 };
13349 ChildEventRegistration.prototype.getEventRunner = function (eventData) {
13350 var _this = this;
13351 if (eventData.getEventType() === 'cancel') {
13352 return function () {
13353 return _this.callbackContext.onCancel(eventData.error);
13354 };
13355 }
13356 else {
13357 return function () {
13358 return _this.callbackContext.onValue(eventData.snapshot, eventData.prevName);
13359 };
13360 }
13361 };
13362 ChildEventRegistration.prototype.matches = function (other) {
13363 if (other instanceof ChildEventRegistration) {
13364 return (this.eventType === other.eventType &&
13365 (!this.callbackContext ||
13366 !other.callbackContext ||
13367 this.callbackContext.matches(other.callbackContext)));
13368 }
13369 return false;
13370 };
13371 ChildEventRegistration.prototype.hasAnyCallback = function () {
13372 return !!this.callbackContext;
13373 };
13374 return ChildEventRegistration;
13375}());
13376function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) {
13377 var cancelCallback;
13378 if (typeof cancelCallbackOrListenOptions === 'object') {
13379 cancelCallback = undefined;
13380 options = cancelCallbackOrListenOptions;
13381 }
13382 if (typeof cancelCallbackOrListenOptions === 'function') {
13383 cancelCallback = cancelCallbackOrListenOptions;
13384 }
13385 if (options && options.onlyOnce) {
13386 var userCallback_1 = callback;
13387 var onceCallback = function (dataSnapshot, previousChildName) {
13388 repoRemoveEventCallbackForQuery(query._repo, query, container);
13389 userCallback_1(dataSnapshot, previousChildName);
13390 };
13391 onceCallback.userCallback = callback.userCallback;
13392 onceCallback.context = callback.context;
13393 callback = onceCallback;
13394 }
13395 var callbackContext = new CallbackContext(callback, cancelCallback || undefined);
13396 var container = eventType === 'value'
13397 ? new ValueEventRegistration(callbackContext)
13398 : new ChildEventRegistration(eventType, callbackContext);
13399 repoAddEventCallbackForQuery(query._repo, query, container);
13400 return function () { return repoRemoveEventCallbackForQuery(query._repo, query, container); };
13401}
13402function onValue(query, callback, cancelCallbackOrListenOptions, options) {
13403 return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options);
13404}
13405function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) {
13406 return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options);
13407}
13408function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) {
13409 return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options);
13410}
13411function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) {
13412 return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options);
13413}
13414function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) {
13415 return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options);
13416}
13417/**
13418 * Detaches a callback previously attached with `on()`.
13419 *
13420 * Detach a callback previously attached with `on()`. Note that if `on()` was
13421 * called multiple times with the same eventType and callback, the callback
13422 * will be called multiple times for each event, and `off()` must be called
13423 * multiple times to remove the callback. Calling `off()` on a parent listener
13424 * will not automatically remove listeners registered on child nodes, `off()`
13425 * must also be called on any child listeners to remove the callback.
13426 *
13427 * If a callback is not specified, all callbacks for the specified eventType
13428 * will be removed. Similarly, if no eventType is specified, all callbacks
13429 * for the `Reference` will be removed.
13430 *
13431 * Individual listeners can also be removed by invoking their unsubscribe
13432 * callbacks.
13433 *
13434 * @param query - The query that the listener was registered with.
13435 * @param eventType - One of the following strings: "value", "child_added",
13436 * "child_changed", "child_removed", or "child_moved." If omitted, all callbacks
13437 * for the `Reference` will be removed.
13438 * @param callback - The callback function that was passed to `on()` or
13439 * `undefined` to remove all callbacks.
13440 */
13441function off(query, eventType, callback) {
13442 var container = null;
13443 var expCallback = callback ? new CallbackContext(callback) : null;
13444 if (eventType === 'value') {
13445 container = new ValueEventRegistration(expCallback);
13446 }
13447 else if (eventType) {
13448 container = new ChildEventRegistration(eventType, expCallback);
13449 }
13450 repoRemoveEventCallbackForQuery(query._repo, query, container);
13451}
13452/**
13453 * A `QueryConstraint` is used to narrow the set of documents returned by a
13454 * Database query. `QueryConstraint`s are created by invoking {@link endAt},
13455 * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link
13456 * limitToFirst}, {@link limitToLast}, {@link orderByChild},
13457 * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} ,
13458 * {@link orderByValue} or {@link equalTo} and
13459 * can then be passed to {@link query} to create a new query instance that
13460 * also contains this `QueryConstraint`.
13461 */
13462var QueryConstraint = /** @class */ (function () {
13463 function QueryConstraint() {
13464 }
13465 return QueryConstraint;
13466}());
13467var QueryEndAtConstraint = /** @class */ (function (_super) {
13468 __extends(QueryEndAtConstraint, _super);
13469 function QueryEndAtConstraint(_value, _key) {
13470 var _this = _super.call(this) || this;
13471 _this._value = _value;
13472 _this._key = _key;
13473 return _this;
13474 }
13475 QueryEndAtConstraint.prototype._apply = function (query) {
13476 validateFirebaseDataArg('endAt', this._value, query._path, true);
13477 var newParams = queryParamsEndAt(query._queryParams, this._value, this._key);
13478 validateLimit(newParams);
13479 validateQueryEndpoints(newParams);
13480 if (query._queryParams.hasEnd()) {
13481 throw new Error('endAt: Starting point was already set (by another call to endAt, ' +
13482 'endBefore or equalTo).');
13483 }
13484 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13485 };
13486 return QueryEndAtConstraint;
13487}(QueryConstraint));
13488/**
13489 * Creates a `QueryConstraint` with the specified ending point.
13490 *
13491 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13492 * allows you to choose arbitrary starting and ending points for your queries.
13493 *
13494 * The ending point is inclusive, so children with exactly the specified value
13495 * will be included in the query. The optional key argument can be used to
13496 * further limit the range of the query. If it is specified, then children that
13497 * have exactly the specified value must also have a key name less than or equal
13498 * to the specified key.
13499 *
13500 * You can read more about `endAt()` in
13501 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13502 *
13503 * @param value - The value to end at. The argument type depends on which
13504 * `orderBy*()` function was used in this query. Specify a value that matches
13505 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13506 * value must be a string.
13507 * @param key - The child key to end at, among the children with the previously
13508 * specified priority. This argument is only allowed if ordering by child,
13509 * value, or priority.
13510 */
13511function endAt(value, key) {
13512 validateKey('endAt', 'key', key, true);
13513 return new QueryEndAtConstraint(value, key);
13514}
13515var QueryEndBeforeConstraint = /** @class */ (function (_super) {
13516 __extends(QueryEndBeforeConstraint, _super);
13517 function QueryEndBeforeConstraint(_value, _key) {
13518 var _this = _super.call(this) || this;
13519 _this._value = _value;
13520 _this._key = _key;
13521 return _this;
13522 }
13523 QueryEndBeforeConstraint.prototype._apply = function (query) {
13524 validateFirebaseDataArg('endBefore', this._value, query._path, false);
13525 var newParams = queryParamsEndBefore(query._queryParams, this._value, this._key);
13526 validateLimit(newParams);
13527 validateQueryEndpoints(newParams);
13528 if (query._queryParams.hasEnd()) {
13529 throw new Error('endBefore: Starting point was already set (by another call to endAt, ' +
13530 'endBefore or equalTo).');
13531 }
13532 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13533 };
13534 return QueryEndBeforeConstraint;
13535}(QueryConstraint));
13536/**
13537 * Creates a `QueryConstraint` with the specified ending point (exclusive).
13538 *
13539 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13540 * allows you to choose arbitrary starting and ending points for your queries.
13541 *
13542 * The ending point is exclusive. If only a value is provided, children
13543 * with a value less than the specified value will be included in the query.
13544 * If a key is specified, then children must have a value lesss than or equal
13545 * to the specified value and a a key name less than the specified key.
13546 *
13547 * @param value - The value to end before. The argument type depends on which
13548 * `orderBy*()` function was used in this query. Specify a value that matches
13549 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13550 * value must be a string.
13551 * @param key - The child key to end before, among the children with the
13552 * previously specified priority. This argument is only allowed if ordering by
13553 * child, value, or priority.
13554 */
13555function endBefore(value, key) {
13556 validateKey('endBefore', 'key', key, true);
13557 return new QueryEndBeforeConstraint(value, key);
13558}
13559var QueryStartAtConstraint = /** @class */ (function (_super) {
13560 __extends(QueryStartAtConstraint, _super);
13561 function QueryStartAtConstraint(_value, _key) {
13562 var _this = _super.call(this) || this;
13563 _this._value = _value;
13564 _this._key = _key;
13565 return _this;
13566 }
13567 QueryStartAtConstraint.prototype._apply = function (query) {
13568 validateFirebaseDataArg('startAt', this._value, query._path, true);
13569 var newParams = queryParamsStartAt(query._queryParams, this._value, this._key);
13570 validateLimit(newParams);
13571 validateQueryEndpoints(newParams);
13572 if (query._queryParams.hasStart()) {
13573 throw new Error('startAt: Starting point was already set (by another call to startAt, ' +
13574 'startBefore or equalTo).');
13575 }
13576 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13577 };
13578 return QueryStartAtConstraint;
13579}(QueryConstraint));
13580/**
13581 * Creates a `QueryConstraint` with the specified starting point.
13582 *
13583 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13584 * allows you to choose arbitrary starting and ending points for your queries.
13585 *
13586 * The starting point is inclusive, so children with exactly the specified value
13587 * will be included in the query. The optional key argument can be used to
13588 * further limit the range of the query. If it is specified, then children that
13589 * have exactly the specified value must also have a key name greater than or
13590 * equal to the specified key.
13591 *
13592 * You can read more about `startAt()` in
13593 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13594 *
13595 * @param value - The value to start at. The argument type depends on which
13596 * `orderBy*()` function was used in this query. Specify a value that matches
13597 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13598 * value must be a string.
13599 * @param key - The child key to start at. This argument is only allowed if
13600 * ordering by child, value, or priority.
13601 */
13602function startAt(value, key) {
13603 if (value === void 0) { value = null; }
13604 validateKey('startAt', 'key', key, true);
13605 return new QueryStartAtConstraint(value, key);
13606}
13607var QueryStartAfterConstraint = /** @class */ (function (_super) {
13608 __extends(QueryStartAfterConstraint, _super);
13609 function QueryStartAfterConstraint(_value, _key) {
13610 var _this = _super.call(this) || this;
13611 _this._value = _value;
13612 _this._key = _key;
13613 return _this;
13614 }
13615 QueryStartAfterConstraint.prototype._apply = function (query) {
13616 validateFirebaseDataArg('startAfter', this._value, query._path, false);
13617 var newParams = queryParamsStartAfter(query._queryParams, this._value, this._key);
13618 validateLimit(newParams);
13619 validateQueryEndpoints(newParams);
13620 if (query._queryParams.hasStart()) {
13621 throw new Error('startAfter: Starting point was already set (by another call to startAt, ' +
13622 'startAfter, or equalTo).');
13623 }
13624 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13625 };
13626 return QueryStartAfterConstraint;
13627}(QueryConstraint));
13628/**
13629 * Creates a `QueryConstraint` with the specified starting point (exclusive).
13630 *
13631 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13632 * allows you to choose arbitrary starting and ending points for your queries.
13633 *
13634 * The starting point is exclusive. If only a value is provided, children
13635 * with a value greater than the specified value will be included in the query.
13636 * If a key is specified, then children must have a value greater than or equal
13637 * to the specified value and a a key name greater than the specified key.
13638 *
13639 * @param value - The value to start after. The argument type depends on which
13640 * `orderBy*()` function was used in this query. Specify a value that matches
13641 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13642 * value must be a string.
13643 * @param key - The child key to start after. This argument is only allowed if
13644 * ordering by child, value, or priority.
13645 */
13646function startAfter(value, key) {
13647 validateKey('startAfter', 'key', key, true);
13648 return new QueryStartAfterConstraint(value, key);
13649}
13650var QueryLimitToFirstConstraint = /** @class */ (function (_super) {
13651 __extends(QueryLimitToFirstConstraint, _super);
13652 function QueryLimitToFirstConstraint(_limit) {
13653 var _this = _super.call(this) || this;
13654 _this._limit = _limit;
13655 return _this;
13656 }
13657 QueryLimitToFirstConstraint.prototype._apply = function (query) {
13658 if (query._queryParams.hasLimit()) {
13659 throw new Error('limitToFirst: Limit was already set (by another call to limitToFirst ' +
13660 'or limitToLast).');
13661 }
13662 return new QueryImpl(query._repo, query._path, queryParamsLimitToFirst(query._queryParams, this._limit), query._orderByCalled);
13663 };
13664 return QueryLimitToFirstConstraint;
13665}(QueryConstraint));
13666/**
13667 * Creates a new `QueryConstraint` that if limited to the first specific number
13668 * of children.
13669 *
13670 * The `limitToFirst()` method is used to set a maximum number of children to be
13671 * synced for a given callback. If we set a limit of 100, we will initially only
13672 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13673 * stored in our Database, a `child_added` event will fire for each message.
13674 * However, if we have over 100 messages, we will only receive a `child_added`
13675 * event for the first 100 ordered messages. As items change, we will receive
13676 * `child_removed` events for each item that drops out of the active list so
13677 * that the total number stays at 100.
13678 *
13679 * You can read more about `limitToFirst()` in
13680 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13681 *
13682 * @param limit - The maximum number of nodes to include in this query.
13683 */
13684function limitToFirst(limit) {
13685 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13686 throw new Error('limitToFirst: First argument must be a positive integer.');
13687 }
13688 return new QueryLimitToFirstConstraint(limit);
13689}
13690var QueryLimitToLastConstraint = /** @class */ (function (_super) {
13691 __extends(QueryLimitToLastConstraint, _super);
13692 function QueryLimitToLastConstraint(_limit) {
13693 var _this = _super.call(this) || this;
13694 _this._limit = _limit;
13695 return _this;
13696 }
13697 QueryLimitToLastConstraint.prototype._apply = function (query) {
13698 if (query._queryParams.hasLimit()) {
13699 throw new Error('limitToLast: Limit was already set (by another call to limitToFirst ' +
13700 'or limitToLast).');
13701 }
13702 return new QueryImpl(query._repo, query._path, queryParamsLimitToLast(query._queryParams, this._limit), query._orderByCalled);
13703 };
13704 return QueryLimitToLastConstraint;
13705}(QueryConstraint));
13706/**
13707 * Creates a new `QueryConstraint` that is limited to return only the last
13708 * specified number of children.
13709 *
13710 * The `limitToLast()` method is used to set a maximum number of children to be
13711 * synced for a given callback. If we set a limit of 100, we will initially only
13712 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13713 * stored in our Database, a `child_added` event will fire for each message.
13714 * However, if we have over 100 messages, we will only receive a `child_added`
13715 * event for the last 100 ordered messages. As items change, we will receive
13716 * `child_removed` events for each item that drops out of the active list so
13717 * that the total number stays at 100.
13718 *
13719 * You can read more about `limitToLast()` in
13720 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13721 *
13722 * @param limit - The maximum number of nodes to include in this query.
13723 */
13724function limitToLast(limit) {
13725 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13726 throw new Error('limitToLast: First argument must be a positive integer.');
13727 }
13728 return new QueryLimitToLastConstraint(limit);
13729}
13730var QueryOrderByChildConstraint = /** @class */ (function (_super) {
13731 __extends(QueryOrderByChildConstraint, _super);
13732 function QueryOrderByChildConstraint(_path) {
13733 var _this = _super.call(this) || this;
13734 _this._path = _path;
13735 return _this;
13736 }
13737 QueryOrderByChildConstraint.prototype._apply = function (query) {
13738 validateNoPreviousOrderByCall(query, 'orderByChild');
13739 var parsedPath = new Path(this._path);
13740 if (pathIsEmpty(parsedPath)) {
13741 throw new Error('orderByChild: cannot pass in empty path. Use orderByValue() instead.');
13742 }
13743 var index = new PathIndex(parsedPath);
13744 var newParams = queryParamsOrderBy(query._queryParams, index);
13745 validateQueryEndpoints(newParams);
13746 return new QueryImpl(query._repo, query._path, newParams,
13747 /*orderByCalled=*/ true);
13748 };
13749 return QueryOrderByChildConstraint;
13750}(QueryConstraint));
13751/**
13752 * Creates a new `QueryConstraint` that orders by the specified child key.
13753 *
13754 * Queries can only order by one key at a time. Calling `orderByChild()`
13755 * multiple times on the same query is an error.
13756 *
13757 * Firebase queries allow you to order your data by any child key on the fly.
13758 * However, if you know in advance what your indexes will be, you can define
13759 * them via the .indexOn rule in your Security Rules for better performance. See
13760 * the{@link https://firebase.google.com/docs/database/security/indexing-data}
13761 * rule for more information.
13762 *
13763 * You can read more about `orderByChild()` in
13764 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13765 *
13766 * @param path - The path to order by.
13767 */
13768function orderByChild(path) {
13769 if (path === '$key') {
13770 throw new Error('orderByChild: "$key" is invalid. Use orderByKey() instead.');
13771 }
13772 else if (path === '$priority') {
13773 throw new Error('orderByChild: "$priority" is invalid. Use orderByPriority() instead.');
13774 }
13775 else if (path === '$value') {
13776 throw new Error('orderByChild: "$value" is invalid. Use orderByValue() instead.');
13777 }
13778 validatePathString('orderByChild', 'path', path, false);
13779 return new QueryOrderByChildConstraint(path);
13780}
13781var QueryOrderByKeyConstraint = /** @class */ (function (_super) {
13782 __extends(QueryOrderByKeyConstraint, _super);
13783 function QueryOrderByKeyConstraint() {
13784 return _super !== null && _super.apply(this, arguments) || this;
13785 }
13786 QueryOrderByKeyConstraint.prototype._apply = function (query) {
13787 validateNoPreviousOrderByCall(query, 'orderByKey');
13788 var newParams = queryParamsOrderBy(query._queryParams, KEY_INDEX);
13789 validateQueryEndpoints(newParams);
13790 return new QueryImpl(query._repo, query._path, newParams,
13791 /*orderByCalled=*/ true);
13792 };
13793 return QueryOrderByKeyConstraint;
13794}(QueryConstraint));
13795/**
13796 * Creates a new `QueryConstraint` that orders by the key.
13797 *
13798 * Sorts the results of a query by their (ascending) key values.
13799 *
13800 * You can read more about `orderByKey()` in
13801 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13802 */
13803function orderByKey() {
13804 return new QueryOrderByKeyConstraint();
13805}
13806var QueryOrderByPriorityConstraint = /** @class */ (function (_super) {
13807 __extends(QueryOrderByPriorityConstraint, _super);
13808 function QueryOrderByPriorityConstraint() {
13809 return _super !== null && _super.apply(this, arguments) || this;
13810 }
13811 QueryOrderByPriorityConstraint.prototype._apply = function (query) {
13812 validateNoPreviousOrderByCall(query, 'orderByPriority');
13813 var newParams = queryParamsOrderBy(query._queryParams, PRIORITY_INDEX);
13814 validateQueryEndpoints(newParams);
13815 return new QueryImpl(query._repo, query._path, newParams,
13816 /*orderByCalled=*/ true);
13817 };
13818 return QueryOrderByPriorityConstraint;
13819}(QueryConstraint));
13820/**
13821 * Creates a new `QueryConstraint` that orders by priority.
13822 *
13823 * Applications need not use priority but can order collections by
13824 * ordinary properties (see
13825 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}
13826 * for alternatives to priority.
13827 */
13828function orderByPriority() {
13829 return new QueryOrderByPriorityConstraint();
13830}
13831var QueryOrderByValueConstraint = /** @class */ (function (_super) {
13832 __extends(QueryOrderByValueConstraint, _super);
13833 function QueryOrderByValueConstraint() {
13834 return _super !== null && _super.apply(this, arguments) || this;
13835 }
13836 QueryOrderByValueConstraint.prototype._apply = function (query) {
13837 validateNoPreviousOrderByCall(query, 'orderByValue');
13838 var newParams = queryParamsOrderBy(query._queryParams, VALUE_INDEX);
13839 validateQueryEndpoints(newParams);
13840 return new QueryImpl(query._repo, query._path, newParams,
13841 /*orderByCalled=*/ true);
13842 };
13843 return QueryOrderByValueConstraint;
13844}(QueryConstraint));
13845/**
13846 * Creates a new `QueryConstraint` that orders by value.
13847 *
13848 * If the children of a query are all scalar values (string, number, or
13849 * boolean), you can order the results by their (ascending) values.
13850 *
13851 * You can read more about `orderByValue()` in
13852 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13853 */
13854function orderByValue() {
13855 return new QueryOrderByValueConstraint();
13856}
13857var QueryEqualToValueConstraint = /** @class */ (function (_super) {
13858 __extends(QueryEqualToValueConstraint, _super);
13859 function QueryEqualToValueConstraint(_value, _key) {
13860 var _this = _super.call(this) || this;
13861 _this._value = _value;
13862 _this._key = _key;
13863 return _this;
13864 }
13865 QueryEqualToValueConstraint.prototype._apply = function (query) {
13866 validateFirebaseDataArg('equalTo', this._value, query._path, false);
13867 if (query._queryParams.hasStart()) {
13868 throw new Error('equalTo: Starting point was already set (by another call to startAt/startAfter or ' +
13869 'equalTo).');
13870 }
13871 if (query._queryParams.hasEnd()) {
13872 throw new Error('equalTo: Ending point was already set (by another call to endAt/endBefore or ' +
13873 'equalTo).');
13874 }
13875 return new QueryEndAtConstraint(this._value, this._key)._apply(new QueryStartAtConstraint(this._value, this._key)._apply(query));
13876 };
13877 return QueryEqualToValueConstraint;
13878}(QueryConstraint));
13879/**
13880 * Creates a `QueryConstraint` that includes children that match the specified
13881 * value.
13882 *
13883 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13884 * allows you to choose arbitrary starting and ending points for your queries.
13885 *
13886 * The optional key argument can be used to further limit the range of the
13887 * query. If it is specified, then children that have exactly the specified
13888 * value must also have exactly the specified key as their key name. This can be
13889 * used to filter result sets with many matches for the same value.
13890 *
13891 * You can read more about `equalTo()` in
13892 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13893 *
13894 * @param value - The value to match for. The argument type depends on which
13895 * `orderBy*()` function was used in this query. Specify a value that matches
13896 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13897 * value must be a string.
13898 * @param key - The child key to start at, among the children with the
13899 * previously specified priority. This argument is only allowed if ordering by
13900 * child, value, or priority.
13901 */
13902function equalTo(value, key) {
13903 validateKey('equalTo', 'key', key, true);
13904 return new QueryEqualToValueConstraint(value, key);
13905}
13906/**
13907 * Creates a new immutable instance of `Query` that is extended to also include
13908 * additional query constraints.
13909 *
13910 * @param query - The Query instance to use as a base for the new constraints.
13911 * @param queryConstraints - The list of `QueryConstraint`s to apply.
13912 * @throws if any of the provided query constraints cannot be combined with the
13913 * existing or new constraints.
13914 */
13915function query(query) {
13916 var e_1, _a;
13917 var queryConstraints = [];
13918 for (var _i = 1; _i < arguments.length; _i++) {
13919 queryConstraints[_i - 1] = arguments[_i];
13920 }
13921 var queryImpl = getModularInstance(query);
13922 try {
13923 for (var queryConstraints_1 = __values(queryConstraints), queryConstraints_1_1 = queryConstraints_1.next(); !queryConstraints_1_1.done; queryConstraints_1_1 = queryConstraints_1.next()) {
13924 var constraint = queryConstraints_1_1.value;
13925 queryImpl = constraint._apply(queryImpl);
13926 }
13927 }
13928 catch (e_1_1) { e_1 = { error: e_1_1 }; }
13929 finally {
13930 try {
13931 if (queryConstraints_1_1 && !queryConstraints_1_1.done && (_a = queryConstraints_1.return)) _a.call(queryConstraints_1);
13932 }
13933 finally { if (e_1) throw e_1.error; }
13934 }
13935 return queryImpl;
13936}
13937/**
13938 * Define reference constructor in various modules
13939 *
13940 * We are doing this here to avoid several circular
13941 * dependency issues
13942 */
13943syncPointSetReferenceConstructor(ReferenceImpl);
13944syncTreeSetReferenceConstructor(ReferenceImpl);
13945
13946/**
13947 * @license
13948 * Copyright 2020 Google LLC
13949 *
13950 * Licensed under the Apache License, Version 2.0 (the "License");
13951 * you may not use this file except in compliance with the License.
13952 * You may obtain a copy of the License at
13953 *
13954 * http://www.apache.org/licenses/LICENSE-2.0
13955 *
13956 * Unless required by applicable law or agreed to in writing, software
13957 * distributed under the License is distributed on an "AS IS" BASIS,
13958 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13959 * See the License for the specific language governing permissions and
13960 * limitations under the License.
13961 */
13962/**
13963 * This variable is also defined in the firebase Node.js Admin SDK. Before
13964 * modifying this definition, consult the definition in:
13965 *
13966 * https://github.com/firebase/firebase-admin-node
13967 *
13968 * and make sure the two are consistent.
13969 */
13970var FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST';
13971/**
13972 * Creates and caches `Repo` instances.
13973 */
13974var repos = {};
13975/**
13976 * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes).
13977 */
13978var useRestClient = false;
13979/**
13980 * Update an existing `Repo` in place to point to a new host/port.
13981 */
13982function repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider) {
13983 repo.repoInfo_ = new RepoInfo(host + ":" + port,
13984 /* secure= */ false, repo.repoInfo_.namespace, repo.repoInfo_.webSocketOnly, repo.repoInfo_.nodeAdmin, repo.repoInfo_.persistenceKey, repo.repoInfo_.includeNamespaceInQueryParams);
13985 if (tokenProvider) {
13986 repo.authTokenProvider_ = tokenProvider;
13987 }
13988}
13989/**
13990 * This function should only ever be called to CREATE a new database instance.
13991 * @internal
13992 */
13993function repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url, nodeAdmin) {
13994 var dbUrl = url || app.options.databaseURL;
13995 if (dbUrl === undefined) {
13996 if (!app.options.projectId) {
13997 fatal("Can't determine Firebase Database URL. Be sure to include " +
13998 ' a Project ID when calling firebase.initializeApp().');
13999 }
14000 log('Using default host for project ', app.options.projectId);
14001 dbUrl = app.options.projectId + "-default-rtdb.firebaseio.com";
14002 }
14003 var parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14004 var repoInfo = parsedUrl.repoInfo;
14005 var isEmulator;
14006 var dbEmulatorHost = undefined;
14007 if (typeof process !== 'undefined') {
14008 dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR];
14009 }
14010 if (dbEmulatorHost) {
14011 isEmulator = true;
14012 dbUrl = "http://" + dbEmulatorHost + "?ns=" + repoInfo.namespace;
14013 parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
14014 repoInfo = parsedUrl.repoInfo;
14015 }
14016 else {
14017 isEmulator = !parsedUrl.repoInfo.secure;
14018 }
14019 var authTokenProvider = nodeAdmin && isEmulator
14020 ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER)
14021 : new FirebaseAuthTokenProvider(app.name, app.options, authProvider);
14022 validateUrl('Invalid Firebase Database URL', parsedUrl);
14023 if (!pathIsEmpty(parsedUrl.path)) {
14024 fatal('Database URL must point to the root of a Firebase Database ' +
14025 '(not including a child path).');
14026 }
14027 var repo = repoManagerCreateRepo(repoInfo, app, authTokenProvider, new AppCheckTokenProvider(app.name, appCheckProvider));
14028 return new Database(repo, app);
14029}
14030/**
14031 * Remove the repo and make sure it is disconnected.
14032 *
14033 */
14034function repoManagerDeleteRepo(repo, appName) {
14035 var appRepos = repos[appName];
14036 // This should never happen...
14037 if (!appRepos || appRepos[repo.key] !== repo) {
14038 fatal("Database " + appName + "(" + repo.repoInfo_ + ") has already been deleted.");
14039 }
14040 repoInterrupt(repo);
14041 delete appRepos[repo.key];
14042}
14043/**
14044 * Ensures a repo doesn't already exist and then creates one using the
14045 * provided app.
14046 *
14047 * @param repoInfo - The metadata about the Repo
14048 * @returns The Repo object for the specified server / repoName.
14049 */
14050function repoManagerCreateRepo(repoInfo, app, authTokenProvider, appCheckProvider) {
14051 var appRepos = repos[app.name];
14052 if (!appRepos) {
14053 appRepos = {};
14054 repos[app.name] = appRepos;
14055 }
14056 var repo = appRepos[repoInfo.toURLString()];
14057 if (repo) {
14058 fatal('Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.');
14059 }
14060 repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider);
14061 appRepos[repoInfo.toURLString()] = repo;
14062 return repo;
14063}
14064/**
14065 * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos.
14066 */
14067function repoManagerForceRestClient(forceRestClient) {
14068 useRestClient = forceRestClient;
14069}
14070/**
14071 * Class representing a Firebase Realtime Database.
14072 */
14073var Database = /** @class */ (function () {
14074 /** @hideconstructor */
14075 function Database(_repoInternal,
14076 /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */
14077 app) {
14078 this._repoInternal = _repoInternal;
14079 this.app = app;
14080 /** Represents a `Database` instance. */
14081 this['type'] = 'database';
14082 /** Track if the instance has been used (root or repo accessed) */
14083 this._instanceStarted = false;
14084 }
14085 Object.defineProperty(Database.prototype, "_repo", {
14086 get: function () {
14087 if (!this._instanceStarted) {
14088 repoStart(this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride']);
14089 this._instanceStarted = true;
14090 }
14091 return this._repoInternal;
14092 },
14093 enumerable: false,
14094 configurable: true
14095 });
14096 Object.defineProperty(Database.prototype, "_root", {
14097 get: function () {
14098 if (!this._rootInternal) {
14099 this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath());
14100 }
14101 return this._rootInternal;
14102 },
14103 enumerable: false,
14104 configurable: true
14105 });
14106 Database.prototype._delete = function () {
14107 if (this._rootInternal !== null) {
14108 repoManagerDeleteRepo(this._repo, this.app.name);
14109 this._repoInternal = null;
14110 this._rootInternal = null;
14111 }
14112 return Promise.resolve();
14113 };
14114 Database.prototype._checkNotDeleted = function (apiName) {
14115 if (this._rootInternal === null) {
14116 fatal('Cannot call ' + apiName + ' on a deleted database.');
14117 }
14118 };
14119 return Database;
14120}());
14121/**
14122 * Returns the instance of the Realtime Database SDK that is associated
14123 * with the provided {@link @firebase/app#FirebaseApp}. Initializes a new instance with
14124 * with default settings if no instance exists or if the existing instance uses
14125 * a custom database URL.
14126 *
14127 * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned Realtime
14128 * Database instance is associated with.
14129 * @param url - The URL of the Realtime Database instance to connect to. If not
14130 * provided, the SDK connects to the default instance of the Firebase App.
14131 * @returns The `Database` instance of the provided app.
14132 */
14133function getDatabase(app, url) {
14134 if (app === void 0) { app = getApp(); }
14135 return _getProvider(app, 'database').getImmediate({
14136 identifier: url
14137 });
14138}
14139/**
14140 * Modify the provided instance to communicate with the Realtime Database
14141 * emulator.
14142 *
14143 * <p>Note: This method must be called before performing any other operation.
14144 *
14145 * @param db - The instance to modify.
14146 * @param host - The emulator host (ex: localhost)
14147 * @param port - The emulator port (ex: 8080)
14148 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
14149 */
14150function connectDatabaseEmulator(db, host, port, options) {
14151 if (options === void 0) { options = {}; }
14152 db = getModularInstance(db);
14153 db._checkNotDeleted('useEmulator');
14154 if (db._instanceStarted) {
14155 fatal('Cannot call useEmulator() after instance has already been initialized.');
14156 }
14157 var repo = db._repoInternal;
14158 var tokenProvider = undefined;
14159 if (repo.repoInfo_.nodeAdmin) {
14160 if (options.mockUserToken) {
14161 fatal('mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".');
14162 }
14163 tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
14164 }
14165 else if (options.mockUserToken) {
14166 var token = typeof options.mockUserToken === 'string'
14167 ? options.mockUserToken
14168 : createMockUserToken(options.mockUserToken, db.app.options.projectId);
14169 tokenProvider = new EmulatorTokenProvider(token);
14170 }
14171 // Modify the repo to apply emulator settings
14172 repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider);
14173}
14174/**
14175 * Disconnects from the server (all Database operations will be completed
14176 * offline).
14177 *
14178 * The client automatically maintains a persistent connection to the Database
14179 * server, which will remain active indefinitely and reconnect when
14180 * disconnected. However, the `goOffline()` and `goOnline()` methods may be used
14181 * to control the client connection in cases where a persistent connection is
14182 * undesirable.
14183 *
14184 * While offline, the client will no longer receive data updates from the
14185 * Database. However, all Database operations performed locally will continue to
14186 * immediately fire events, allowing your application to continue behaving
14187 * normally. Additionally, each operation performed locally will automatically
14188 * be queued and retried upon reconnection to the Database server.
14189 *
14190 * To reconnect to the Database and begin receiving remote events, see
14191 * `goOnline()`.
14192 *
14193 * @param db - The instance to disconnect.
14194 */
14195function goOffline(db) {
14196 db = getModularInstance(db);
14197 db._checkNotDeleted('goOffline');
14198 repoInterrupt(db._repo);
14199}
14200/**
14201 * Reconnects to the server and synchronizes the offline Database state
14202 * with the server state.
14203 *
14204 * This method should be used after disabling the active connection with
14205 * `goOffline()`. Once reconnected, the client will transmit the proper data
14206 * and fire the appropriate events so that your client "catches up"
14207 * automatically.
14208 *
14209 * @param db - The instance to reconnect.
14210 */
14211function goOnline(db) {
14212 db = getModularInstance(db);
14213 db._checkNotDeleted('goOnline');
14214 repoResume(db._repo);
14215}
14216function enableLogging(logger, persistent) {
14217 enableLogging$1(logger, persistent);
14218}
14219
14220/**
14221 * @license
14222 * Copyright 2021 Google LLC
14223 *
14224 * Licensed under the Apache License, Version 2.0 (the "License");
14225 * you may not use this file except in compliance with the License.
14226 * You may obtain a copy of the License at
14227 *
14228 * http://www.apache.org/licenses/LICENSE-2.0
14229 *
14230 * Unless required by applicable law or agreed to in writing, software
14231 * distributed under the License is distributed on an "AS IS" BASIS,
14232 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14233 * See the License for the specific language governing permissions and
14234 * limitations under the License.
14235 */
14236function registerDatabase(variant) {
14237 setSDKVersion(SDK_VERSION$1);
14238 _registerComponent(new Component('database', function (container, _a) {
14239 var url = _a.instanceIdentifier;
14240 var app = container.getProvider('app').getImmediate();
14241 var authProvider = container.getProvider('auth-internal');
14242 var appCheckProvider = container.getProvider('app-check-internal');
14243 return repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url);
14244 }, "PUBLIC" /* PUBLIC */).setMultipleInstances(true));
14245 registerVersion(name, version, variant);
14246}
14247
14248/**
14249 * @license
14250 * Copyright 2020 Google LLC
14251 *
14252 * Licensed under the Apache License, Version 2.0 (the "License");
14253 * you may not use this file except in compliance with the License.
14254 * You may obtain a copy of the License at
14255 *
14256 * http://www.apache.org/licenses/LICENSE-2.0
14257 *
14258 * Unless required by applicable law or agreed to in writing, software
14259 * distributed under the License is distributed on an "AS IS" BASIS,
14260 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14261 * See the License for the specific language governing permissions and
14262 * limitations under the License.
14263 */
14264var SERVER_TIMESTAMP = {
14265 '.sv': 'timestamp'
14266};
14267/**
14268 * Returns a placeholder value for auto-populating the current timestamp (time
14269 * since the Unix epoch, in milliseconds) as determined by the Firebase
14270 * servers.
14271 */
14272function serverTimestamp() {
14273 return SERVER_TIMESTAMP;
14274}
14275/**
14276 * Returns a placeholder value that can be used to atomically increment the
14277 * current database value by the provided delta.
14278 *
14279 * @param delta - the amount to modify the current value atomically.
14280 * @returns A placeholder value for modifying data atomically server-side.
14281 */
14282function increment(delta) {
14283 return {
14284 '.sv': {
14285 'increment': delta
14286 }
14287 };
14288}
14289
14290/**
14291 * @license
14292 * Copyright 2020 Google LLC
14293 *
14294 * Licensed under the Apache License, Version 2.0 (the "License");
14295 * you may not use this file except in compliance with the License.
14296 * You may obtain a copy of the License at
14297 *
14298 * http://www.apache.org/licenses/LICENSE-2.0
14299 *
14300 * Unless required by applicable law or agreed to in writing, software
14301 * distributed under the License is distributed on an "AS IS" BASIS,
14302 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14303 * See the License for the specific language governing permissions and
14304 * limitations under the License.
14305 */
14306/**
14307 * A type for the resolve value of {@link runTransaction}.
14308 */
14309var TransactionResult = /** @class */ (function () {
14310 /** @hideconstructor */
14311 function TransactionResult(
14312 /** Whether the transaction was successfully committed. */
14313 committed,
14314 /** The resulting data snapshot. */
14315 snapshot) {
14316 this.committed = committed;
14317 this.snapshot = snapshot;
14318 }
14319 /** Returns a JSON-serializable representation of this object. */
14320 TransactionResult.prototype.toJSON = function () {
14321 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14322 };
14323 return TransactionResult;
14324}());
14325/**
14326 * Atomically modifies the data at this location.
14327 *
14328 * Atomically modify the data at this location. Unlike a normal `set()`, which
14329 * just overwrites the data regardless of its previous value, `runTransaction()` is
14330 * used to modify the existing value to a new value, ensuring there are no
14331 * conflicts with other clients writing to the same location at the same time.
14332 *
14333 * To accomplish this, you pass `runTransaction()` an update function which is
14334 * used to transform the current value into a new value. If another client
14335 * writes to the location before your new value is successfully written, your
14336 * update function will be called again with the new current value, and the
14337 * write will be retried. This will happen repeatedly until your write succeeds
14338 * without conflict or you abort the transaction by not returning a value from
14339 * your update function.
14340 *
14341 * Note: Modifying data with `set()` will cancel any pending transactions at
14342 * that location, so extreme care should be taken if mixing `set()` and
14343 * `runTransaction()` to update the same data.
14344 *
14345 * Note: When using transactions with Security and Firebase Rules in place, be
14346 * aware that a client needs `.read` access in addition to `.write` access in
14347 * order to perform a transaction. This is because the client-side nature of
14348 * transactions requires the client to read the data in order to transactionally
14349 * update it.
14350 *
14351 * @param ref - The location to atomically modify.
14352 * @param transactionUpdate - A developer-supplied function which will be passed
14353 * the current data stored at this location (as a JavaScript object). The
14354 * function should return the new value it would like written (as a JavaScript
14355 * object). If `undefined` is returned (i.e. you return with no arguments) the
14356 * transaction will be aborted and the data at this location will not be
14357 * modified.
14358 * @param options - An options object to configure transactions.
14359 * @returns A `Promise` that can optionally be used instead of the `onComplete`
14360 * callback to handle success and failure.
14361 */
14362function runTransaction(ref,
14363// eslint-disable-next-line @typescript-eslint/no-explicit-any
14364transactionUpdate, options) {
14365 var _a;
14366 ref = getModularInstance(ref);
14367 validateWritablePath('Reference.transaction', ref._path);
14368 if (ref.key === '.length' || ref.key === '.keys') {
14369 throw ('Reference.transaction failed: ' + ref.key + ' is a read-only object.');
14370 }
14371 var applyLocally = (_a = options === null || options === void 0 ? void 0 : options.applyLocally) !== null && _a !== void 0 ? _a : true;
14372 var deferred = new Deferred();
14373 var promiseComplete = function (error, committed, node) {
14374 var dataSnapshot = null;
14375 if (error) {
14376 deferred.reject(error);
14377 }
14378 else {
14379 dataSnapshot = new DataSnapshot(node, new ReferenceImpl(ref._repo, ref._path), PRIORITY_INDEX);
14380 deferred.resolve(new TransactionResult(committed, dataSnapshot));
14381 }
14382 };
14383 // Add a watch to make sure we get server updates.
14384 var unwatcher = onValue(ref, function () { });
14385 repoStartTransaction(ref._repo, ref._path, transactionUpdate, promiseComplete, unwatcher, applyLocally);
14386 return deferred.promise;
14387}
14388
14389/**
14390 * @license
14391 * Copyright 2017 Google LLC
14392 *
14393 * Licensed under the Apache License, Version 2.0 (the "License");
14394 * you may not use this file except in compliance with the License.
14395 * You may obtain a copy of the License at
14396 *
14397 * http://www.apache.org/licenses/LICENSE-2.0
14398 *
14399 * Unless required by applicable law or agreed to in writing, software
14400 * distributed under the License is distributed on an "AS IS" BASIS,
14401 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14402 * See the License for the specific language governing permissions and
14403 * limitations under the License.
14404 */
14405// eslint-disable-next-line @typescript-eslint/no-explicit-any
14406PersistentConnection.prototype.simpleListen = function (pathString, onComplete) {
14407 this.sendRequest('q', { p: pathString }, onComplete);
14408};
14409// eslint-disable-next-line @typescript-eslint/no-explicit-any
14410PersistentConnection.prototype.echo = function (data, onEcho) {
14411 this.sendRequest('echo', { d: data }, onEcho);
14412};
14413/**
14414 * @internal
14415 */
14416var hijackHash = function (newHash) {
14417 var oldPut = PersistentConnection.prototype.put;
14418 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
14419 if (hash !== undefined) {
14420 hash = newHash();
14421 }
14422 oldPut.call(this, pathString, data, onComplete, hash);
14423 };
14424 return function () {
14425 PersistentConnection.prototype.put = oldPut;
14426 };
14427};
14428/**
14429 * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection.
14430 * @internal
14431 */
14432var forceRestClient = function (forceRestClient) {
14433 repoManagerForceRestClient(forceRestClient);
14434};
14435
14436/**
14437 * Firebase Realtime Database
14438 *
14439 * @packageDocumentation
14440 */
14441registerDatabase();
14442
14443export { 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, 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 };
14444//# sourceMappingURL=index.esm5.js.map