UNPKG

603 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var Websocket = require('faye-websocket');
6var util = require('@firebase/util');
7var tslib = require('tslib');
8var logger$1 = require('@firebase/logger');
9var app = require('@firebase/app');
10var component = require('@firebase/component');
11
12function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
14var Websocket__default = /*#__PURE__*/_interopDefaultLegacy(Websocket);
15
16/**
17 * @license
18 * Copyright 2017 Google LLC
19 *
20 * Licensed under the Apache License, Version 2.0 (the "License");
21 * you may not use this file except in compliance with the License.
22 * You may obtain a copy of the License at
23 *
24 * http://www.apache.org/licenses/LICENSE-2.0
25 *
26 * Unless required by applicable law or agreed to in writing, software
27 * distributed under the License is distributed on an "AS IS" BASIS,
28 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29 * See the License for the specific language governing permissions and
30 * limitations under the License.
31 */
32var PROTOCOL_VERSION = '5';
33var VERSION_PARAM = 'v';
34var TRANSPORT_SESSION_PARAM = 's';
35var REFERER_PARAM = 'r';
36var FORGE_REF = 'f';
37// Matches console.firebase.google.com, firebase-console-*.corp.google.com and
38// firebase.corp.google.com
39var FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/;
40var LAST_SESSION_PARAM = 'ls';
41var APPLICATION_ID_PARAM = 'p';
42var APP_CHECK_TOKEN_PARAM = 'ac';
43var WEBSOCKET = 'websocket';
44var LONG_POLLING = 'long_polling';
45
46/**
47 * @license
48 * Copyright 2017 Google LLC
49 *
50 * Licensed under the Apache License, Version 2.0 (the "License");
51 * you may not use this file except in compliance with the License.
52 * You may obtain a copy of the License at
53 *
54 * http://www.apache.org/licenses/LICENSE-2.0
55 *
56 * Unless required by applicable law or agreed to in writing, software
57 * distributed under the License is distributed on an "AS IS" BASIS,
58 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
59 * See the License for the specific language governing permissions and
60 * limitations under the License.
61 */
62/**
63 * Wraps a DOM Storage object and:
64 * - automatically encode objects as JSON strings before storing them to allow us to store arbitrary types.
65 * - prefixes names with "firebase:" to avoid collisions with app data.
66 *
67 * We automatically (see storage.js) create two such wrappers, one for sessionStorage,
68 * and one for localStorage.
69 *
70 */
71var DOMStorageWrapper = /** @class */ (function () {
72 /**
73 * @param domStorage_ - The underlying storage object (e.g. localStorage or sessionStorage)
74 */
75 function DOMStorageWrapper(domStorage_) {
76 this.domStorage_ = domStorage_;
77 // Use a prefix to avoid collisions with other stuff saved by the app.
78 this.prefix_ = 'firebase:';
79 }
80 /**
81 * @param key - The key to save the value under
82 * @param value - The value being stored, or null to remove the key.
83 */
84 DOMStorageWrapper.prototype.set = function (key, value) {
85 if (value == null) {
86 this.domStorage_.removeItem(this.prefixedName_(key));
87 }
88 else {
89 this.domStorage_.setItem(this.prefixedName_(key), util.stringify(value));
90 }
91 };
92 /**
93 * @returns The value that was stored under this key, or null
94 */
95 DOMStorageWrapper.prototype.get = function (key) {
96 var storedVal = this.domStorage_.getItem(this.prefixedName_(key));
97 if (storedVal == null) {
98 return null;
99 }
100 else {
101 return util.jsonEval(storedVal);
102 }
103 };
104 DOMStorageWrapper.prototype.remove = function (key) {
105 this.domStorage_.removeItem(this.prefixedName_(key));
106 };
107 DOMStorageWrapper.prototype.prefixedName_ = function (name) {
108 return this.prefix_ + name;
109 };
110 DOMStorageWrapper.prototype.toString = function () {
111 return this.domStorage_.toString();
112 };
113 return DOMStorageWrapper;
114}());
115
116/**
117 * @license
118 * Copyright 2017 Google LLC
119 *
120 * Licensed under the Apache License, Version 2.0 (the "License");
121 * you may not use this file except in compliance with the License.
122 * You may obtain a copy of the License at
123 *
124 * http://www.apache.org/licenses/LICENSE-2.0
125 *
126 * Unless required by applicable law or agreed to in writing, software
127 * distributed under the License is distributed on an "AS IS" BASIS,
128 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
129 * See the License for the specific language governing permissions and
130 * limitations under the License.
131 */
132/**
133 * An in-memory storage implementation that matches the API of DOMStorageWrapper
134 * (TODO: create interface for both to implement).
135 */
136var MemoryStorage = /** @class */ (function () {
137 function MemoryStorage() {
138 this.cache_ = {};
139 this.isInMemoryStorage = true;
140 }
141 MemoryStorage.prototype.set = function (key, value) {
142 if (value == null) {
143 delete this.cache_[key];
144 }
145 else {
146 this.cache_[key] = value;
147 }
148 };
149 MemoryStorage.prototype.get = function (key) {
150 if (util.contains(this.cache_, key)) {
151 return this.cache_[key];
152 }
153 return null;
154 };
155 MemoryStorage.prototype.remove = function (key) {
156 delete this.cache_[key];
157 };
158 return MemoryStorage;
159}());
160
161/**
162 * @license
163 * Copyright 2017 Google LLC
164 *
165 * Licensed under the Apache License, Version 2.0 (the "License");
166 * you may not use this file except in compliance with the License.
167 * You may obtain a copy of the License at
168 *
169 * http://www.apache.org/licenses/LICENSE-2.0
170 *
171 * Unless required by applicable law or agreed to in writing, software
172 * distributed under the License is distributed on an "AS IS" BASIS,
173 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
174 * See the License for the specific language governing permissions and
175 * limitations under the License.
176 */
177/**
178 * Helper to create a DOMStorageWrapper or else fall back to MemoryStorage.
179 * TODO: Once MemoryStorage and DOMStorageWrapper have a shared interface this method annotation should change
180 * to reflect this type
181 *
182 * @param domStorageName - Name of the underlying storage object
183 * (e.g. 'localStorage' or 'sessionStorage').
184 * @returns Turning off type information until a common interface is defined.
185 */
186var createStoragefor = function (domStorageName) {
187 try {
188 // NOTE: just accessing "localStorage" or "window['localStorage']" may throw a security exception,
189 // so it must be inside the try/catch.
190 if (typeof window !== 'undefined' &&
191 typeof window[domStorageName] !== 'undefined') {
192 // Need to test cache. Just because it's here doesn't mean it works
193 var domStorage = window[domStorageName];
194 domStorage.setItem('firebase:sentinel', 'cache');
195 domStorage.removeItem('firebase:sentinel');
196 return new DOMStorageWrapper(domStorage);
197 }
198 }
199 catch (e) { }
200 // Failed to create wrapper. Just return in-memory storage.
201 // TODO: log?
202 return new MemoryStorage();
203};
204/** A storage object that lasts across sessions */
205var PersistentStorage = createStoragefor('localStorage');
206/** A storage object that only lasts one session */
207var SessionStorage = createStoragefor('sessionStorage');
208
209/**
210 * @license
211 * Copyright 2017 Google LLC
212 *
213 * Licensed under the Apache License, Version 2.0 (the "License");
214 * you may not use this file except in compliance with the License.
215 * You may obtain a copy of the License at
216 *
217 * http://www.apache.org/licenses/LICENSE-2.0
218 *
219 * Unless required by applicable law or agreed to in writing, software
220 * distributed under the License is distributed on an "AS IS" BASIS,
221 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
222 * See the License for the specific language governing permissions and
223 * limitations under the License.
224 */
225var logClient = new logger$1.Logger('@firebase/database');
226/**
227 * Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
228 */
229var LUIDGenerator = (function () {
230 var id = 1;
231 return function () {
232 return id++;
233 };
234})();
235/**
236 * Sha1 hash of the input string
237 * @param str - The string to hash
238 * @returns {!string} The resulting hash
239 */
240var sha1 = function (str) {
241 var utf8Bytes = util.stringToByteArray(str);
242 var sha1 = new util.Sha1();
243 sha1.update(utf8Bytes);
244 var sha1Bytes = sha1.digest();
245 return util.base64.encodeByteArray(sha1Bytes);
246};
247var buildLogMessage_ = function () {
248 var varArgs = [];
249 for (var _i = 0; _i < arguments.length; _i++) {
250 varArgs[_i] = arguments[_i];
251 }
252 var message = '';
253 for (var i = 0; i < varArgs.length; i++) {
254 var arg = varArgs[i];
255 if (Array.isArray(arg) ||
256 (arg &&
257 typeof arg === 'object' &&
258 // eslint-disable-next-line @typescript-eslint/no-explicit-any
259 typeof arg.length === 'number')) {
260 message += buildLogMessage_.apply(null, arg);
261 }
262 else if (typeof arg === 'object') {
263 message += util.stringify(arg);
264 }
265 else {
266 message += arg;
267 }
268 message += ' ';
269 }
270 return message;
271};
272/**
273 * Use this for all debug messages in Firebase.
274 */
275var logger = null;
276/**
277 * Flag to check for log availability on first log message
278 */
279var firstLog_ = true;
280/**
281 * The implementation of Firebase.enableLogging (defined here to break dependencies)
282 * @param logger_ - A flag to turn on logging, or a custom logger
283 * @param persistent - Whether or not to persist logging settings across refreshes
284 */
285var enableLogging$1 = function (logger_, persistent) {
286 util.assert(!persistent || logger_ === true || logger_ === false, "Can't turn on custom loggers persistently.");
287 if (logger_ === true) {
288 logClient.logLevel = logger$1.LogLevel.VERBOSE;
289 logger = logClient.log.bind(logClient);
290 if (persistent) {
291 SessionStorage.set('logging_enabled', true);
292 }
293 }
294 else if (typeof logger_ === 'function') {
295 logger = logger_;
296 }
297 else {
298 logger = null;
299 SessionStorage.remove('logging_enabled');
300 }
301};
302var log = function () {
303 var varArgs = [];
304 for (var _i = 0; _i < arguments.length; _i++) {
305 varArgs[_i] = arguments[_i];
306 }
307 if (firstLog_ === true) {
308 firstLog_ = false;
309 if (logger === null && SessionStorage.get('logging_enabled') === true) {
310 enableLogging$1(true);
311 }
312 }
313 if (logger) {
314 var message = buildLogMessage_.apply(null, varArgs);
315 logger(message);
316 }
317};
318var logWrapper = function (prefix) {
319 return function () {
320 var varArgs = [];
321 for (var _i = 0; _i < arguments.length; _i++) {
322 varArgs[_i] = arguments[_i];
323 }
324 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs), false));
325 };
326};
327var error = function () {
328 var varArgs = [];
329 for (var _i = 0; _i < arguments.length; _i++) {
330 varArgs[_i] = arguments[_i];
331 }
332 var message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs), false));
333 logClient.error(message);
334};
335var fatal = function () {
336 var varArgs = [];
337 for (var _i = 0; _i < arguments.length; _i++) {
338 varArgs[_i] = arguments[_i];
339 }
340 var message = "FIREBASE FATAL ERROR: ".concat(buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs), false)));
341 logClient.error(message);
342 throw new Error(message);
343};
344var warn = function () {
345 var varArgs = [];
346 for (var _i = 0; _i < arguments.length; _i++) {
347 varArgs[_i] = arguments[_i];
348 }
349 var message = 'FIREBASE WARNING: ' + buildLogMessage_.apply(void 0, tslib.__spreadArray([], tslib.__read(varArgs), false));
350 logClient.warn(message);
351};
352/**
353 * Logs a warning if the containing page uses https. Called when a call to new Firebase
354 * does not use https.
355 */
356var warnIfPageIsSecure = function () {
357 // Be very careful accessing browser globals. Who knows what may or may not exist.
358 if (typeof window !== 'undefined' &&
359 window.location &&
360 window.location.protocol &&
361 window.location.protocol.indexOf('https:') !== -1) {
362 warn('Insecure Firebase access from a secure page. ' +
363 'Please use https in calls to new Firebase().');
364 }
365};
366/**
367 * Returns true if data is NaN, or +/- Infinity.
368 */
369var isInvalidJSONNumber = function (data) {
370 return (typeof data === 'number' &&
371 (data !== data || // NaN
372 data === Number.POSITIVE_INFINITY ||
373 data === Number.NEGATIVE_INFINITY));
374};
375var executeWhenDOMReady = function (fn) {
376 if (util.isNodeSdk() || document.readyState === 'complete') {
377 fn();
378 }
379 else {
380 // Modeled after jQuery. Try DOMContentLoaded and onreadystatechange (which
381 // fire before onload), but fall back to onload.
382 var called_1 = false;
383 var wrappedFn_1 = function () {
384 if (!document.body) {
385 setTimeout(wrappedFn_1, Math.floor(10));
386 return;
387 }
388 if (!called_1) {
389 called_1 = true;
390 fn();
391 }
392 };
393 if (document.addEventListener) {
394 document.addEventListener('DOMContentLoaded', wrappedFn_1, false);
395 // fallback to onload.
396 window.addEventListener('load', wrappedFn_1, false);
397 // eslint-disable-next-line @typescript-eslint/no-explicit-any
398 }
399 else if (document.attachEvent) {
400 // IE.
401 // eslint-disable-next-line @typescript-eslint/no-explicit-any
402 document.attachEvent('onreadystatechange', function () {
403 if (document.readyState === 'complete') {
404 wrappedFn_1();
405 }
406 });
407 // fallback to onload.
408 // eslint-disable-next-line @typescript-eslint/no-explicit-any
409 window.attachEvent('onload', wrappedFn_1);
410 // jQuery has an extra hack for IE that we could employ (based on
411 // http://javascript.nwbox.com/IEContentLoaded/) But it looks really old.
412 // I'm hoping we don't need it.
413 }
414 }
415};
416/**
417 * Minimum key name. Invalid for actual data, used as a marker to sort before any valid names
418 */
419var MIN_NAME = '[MIN_NAME]';
420/**
421 * Maximum key name. Invalid for actual data, used as a marker to sort above any valid names
422 */
423var MAX_NAME = '[MAX_NAME]';
424/**
425 * Compares valid Firebase key names, plus min and max name
426 */
427var nameCompare = function (a, b) {
428 if (a === b) {
429 return 0;
430 }
431 else if (a === MIN_NAME || b === MAX_NAME) {
432 return -1;
433 }
434 else if (b === MIN_NAME || a === MAX_NAME) {
435 return 1;
436 }
437 else {
438 var aAsInt = tryParseInt(a), bAsInt = tryParseInt(b);
439 if (aAsInt !== null) {
440 if (bAsInt !== null) {
441 return aAsInt - bAsInt === 0 ? a.length - b.length : aAsInt - bAsInt;
442 }
443 else {
444 return -1;
445 }
446 }
447 else if (bAsInt !== null) {
448 return 1;
449 }
450 else {
451 return a < b ? -1 : 1;
452 }
453 }
454};
455/**
456 * @returns {!number} comparison result.
457 */
458var stringCompare = function (a, b) {
459 if (a === b) {
460 return 0;
461 }
462 else if (a < b) {
463 return -1;
464 }
465 else {
466 return 1;
467 }
468};
469var requireKey = function (key, obj) {
470 if (obj && key in obj) {
471 return obj[key];
472 }
473 else {
474 throw new Error('Missing required key (' + key + ') in object: ' + util.stringify(obj));
475 }
476};
477var ObjectToUniqueKey = function (obj) {
478 if (typeof obj !== 'object' || obj === null) {
479 return util.stringify(obj);
480 }
481 var keys = [];
482 // eslint-disable-next-line guard-for-in
483 for (var k in obj) {
484 keys.push(k);
485 }
486 // Export as json, but with the keys sorted.
487 keys.sort();
488 var key = '{';
489 for (var i = 0; i < keys.length; i++) {
490 if (i !== 0) {
491 key += ',';
492 }
493 key += util.stringify(keys[i]);
494 key += ':';
495 key += ObjectToUniqueKey(obj[keys[i]]);
496 }
497 key += '}';
498 return key;
499};
500/**
501 * Splits a string into a number of smaller segments of maximum size
502 * @param str - The string
503 * @param segsize - The maximum number of chars in the string.
504 * @returns The string, split into appropriately-sized chunks
505 */
506var splitStringBySize = function (str, segsize) {
507 var len = str.length;
508 if (len <= segsize) {
509 return [str];
510 }
511 var dataSegs = [];
512 for (var c = 0; c < len; c += segsize) {
513 if (c + segsize > len) {
514 dataSegs.push(str.substring(c, len));
515 }
516 else {
517 dataSegs.push(str.substring(c, c + segsize));
518 }
519 }
520 return dataSegs;
521};
522/**
523 * Apply a function to each (key, value) pair in an object or
524 * apply a function to each (index, value) pair in an array
525 * @param obj - The object or array to iterate over
526 * @param fn - The function to apply
527 */
528function each(obj, fn) {
529 for (var key in obj) {
530 if (obj.hasOwnProperty(key)) {
531 fn(key, obj[key]);
532 }
533 }
534}
535/**
536 * Borrowed from http://hg.secondlife.com/llsd/src/tip/js/typedarray.js (MIT License)
537 * I made one modification at the end and removed the NaN / Infinity
538 * handling (since it seemed broken [caused an overflow] and we don't need it). See MJL comments.
539 * @param v - A double
540 *
541 */
542var doubleToIEEE754String = function (v) {
543 util.assert(!isInvalidJSONNumber(v), 'Invalid JSON number'); // MJL
544 var ebits = 11, fbits = 52;
545 var bias = (1 << (ebits - 1)) - 1;
546 var s, e, f, ln, i;
547 // Compute sign, exponent, fraction
548 // Skip NaN / Infinity handling --MJL.
549 if (v === 0) {
550 e = 0;
551 f = 0;
552 s = 1 / v === -Infinity ? 1 : 0;
553 }
554 else {
555 s = v < 0;
556 v = Math.abs(v);
557 if (v >= Math.pow(2, 1 - bias)) {
558 // Normalized
559 ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
560 e = ln + bias;
561 f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
562 }
563 else {
564 // Denormalized
565 e = 0;
566 f = Math.round(v / Math.pow(2, 1 - bias - fbits));
567 }
568 }
569 // Pack sign, exponent, fraction
570 var bits = [];
571 for (i = fbits; i; i -= 1) {
572 bits.push(f % 2 ? 1 : 0);
573 f = Math.floor(f / 2);
574 }
575 for (i = ebits; i; i -= 1) {
576 bits.push(e % 2 ? 1 : 0);
577 e = Math.floor(e / 2);
578 }
579 bits.push(s ? 1 : 0);
580 bits.reverse();
581 var str = bits.join('');
582 // Return the data as a hex string. --MJL
583 var hexByteString = '';
584 for (i = 0; i < 64; i += 8) {
585 var hexByte = parseInt(str.substr(i, 8), 2).toString(16);
586 if (hexByte.length === 1) {
587 hexByte = '0' + hexByte;
588 }
589 hexByteString = hexByteString + hexByte;
590 }
591 return hexByteString.toLowerCase();
592};
593/**
594 * Used to detect if we're in a Chrome content script (which executes in an
595 * isolated environment where long-polling doesn't work).
596 */
597var isChromeExtensionContentScript = function () {
598 return !!(typeof window === 'object' &&
599 window['chrome'] &&
600 window['chrome']['extension'] &&
601 !/^chrome/.test(window.location.href));
602};
603/**
604 * Used to detect if we're in a Windows 8 Store app.
605 */
606var isWindowsStoreApp = function () {
607 // Check for the presence of a couple WinRT globals
608 return typeof Windows === 'object' && typeof Windows.UI === 'object';
609};
610/**
611 * Converts a server error code to a Javascript Error
612 */
613function errorForServerCode(code, query) {
614 var reason = 'Unknown Error';
615 if (code === 'too_big') {
616 reason =
617 'The data requested exceeds the maximum size ' +
618 'that can be accessed with a single request.';
619 }
620 else if (code === 'permission_denied') {
621 reason = "Client doesn't have permission to access the desired data.";
622 }
623 else if (code === 'unavailable') {
624 reason = 'The service is unavailable';
625 }
626 var error = new Error(code + ' at ' + query._path.toString() + ': ' + reason);
627 // eslint-disable-next-line @typescript-eslint/no-explicit-any
628 error.code = code.toUpperCase();
629 return error;
630}
631/**
632 * Used to test for integer-looking strings
633 */
634var INTEGER_REGEXP_ = new RegExp('^-?(0*)\\d{1,10}$');
635/**
636 * For use in keys, the minimum possible 32-bit integer.
637 */
638var INTEGER_32_MIN = -2147483648;
639/**
640 * For use in kyes, the maximum possible 32-bit integer.
641 */
642var INTEGER_32_MAX = 2147483647;
643/**
644 * If the string contains a 32-bit integer, return it. Else return null.
645 */
646var tryParseInt = function (str) {
647 if (INTEGER_REGEXP_.test(str)) {
648 var intVal = Number(str);
649 if (intVal >= INTEGER_32_MIN && intVal <= INTEGER_32_MAX) {
650 return intVal;
651 }
652 }
653 return null;
654};
655/**
656 * Helper to run some code but catch any exceptions and re-throw them later.
657 * Useful for preventing user callbacks from breaking internal code.
658 *
659 * Re-throwing the exception from a setTimeout is a little evil, but it's very
660 * convenient (we don't have to try to figure out when is a safe point to
661 * re-throw it), and the behavior seems reasonable:
662 *
663 * * If you aren't pausing on exceptions, you get an error in the console with
664 * the correct stack trace.
665 * * If you're pausing on all exceptions, the debugger will pause on your
666 * exception and then again when we rethrow it.
667 * * If you're only pausing on uncaught exceptions, the debugger will only pause
668 * on us re-throwing it.
669 *
670 * @param fn - The code to guard.
671 */
672var exceptionGuard = function (fn) {
673 try {
674 fn();
675 }
676 catch (e) {
677 // Re-throw exception when it's safe.
678 setTimeout(function () {
679 // It used to be that "throw e" would result in a good console error with
680 // relevant context, but as of Chrome 39, you just get the firebase.js
681 // file/line number where we re-throw it, which is useless. So we log
682 // e.stack explicitly.
683 var stack = e.stack || '';
684 warn('Exception was thrown by user callback.', stack);
685 throw e;
686 }, Math.floor(0));
687 }
688};
689/**
690 * @returns {boolean} true if we think we're currently being crawled.
691 */
692var beingCrawled = function () {
693 var userAgent = (typeof window === 'object' &&
694 window['navigator'] &&
695 window['navigator']['userAgent']) ||
696 '';
697 // For now we whitelist the most popular crawlers. We should refine this to be the set of crawlers we
698 // believe to support JavaScript/AJAX rendering.
699 // NOTE: Google Webmaster Tools doesn't really belong, but their "This is how a visitor to your website
700 // would have seen the page" is flaky if we don't treat it as a crawler.
701 return (userAgent.search(/googlebot|google webmaster tools|bingbot|yahoo! slurp|baiduspider|yandexbot|duckduckbot/i) >= 0);
702};
703/**
704 * Same as setTimeout() except on Node.JS it will /not/ prevent the process from exiting.
705 *
706 * It is removed with clearTimeout() as normal.
707 *
708 * @param fn - Function to run.
709 * @param time - Milliseconds to wait before running.
710 * @returns The setTimeout() return value.
711 */
712var setTimeoutNonBlocking = function (fn, time) {
713 var timeout = setTimeout(fn, time);
714 // Note: at the time of this comment, unrefTimer is under the unstable set of APIs. Run with --unstable to enable the API.
715 if (typeof timeout === 'number' &&
716 // @ts-ignore Is only defined in Deno environments.
717 typeof Deno !== 'undefined' &&
718 // @ts-ignore Deno and unrefTimer are only defined in Deno environments.
719 Deno['unrefTimer']) {
720 // @ts-ignore Deno and unrefTimer are only defined in Deno environments.
721 Deno.unrefTimer(timeout);
722 // eslint-disable-next-line @typescript-eslint/no-explicit-any
723 }
724 else if (typeof timeout === 'object' && timeout['unref']) {
725 // eslint-disable-next-line @typescript-eslint/no-explicit-any
726 timeout['unref']();
727 }
728 return timeout;
729};
730
731/**
732 * @license
733 * Copyright 2017 Google LLC
734 *
735 * Licensed under the Apache License, Version 2.0 (the "License");
736 * you may not use this file except in compliance with the License.
737 * You may obtain a copy of the License at
738 *
739 * http://www.apache.org/licenses/LICENSE-2.0
740 *
741 * Unless required by applicable law or agreed to in writing, software
742 * distributed under the License is distributed on an "AS IS" BASIS,
743 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
744 * See the License for the specific language governing permissions and
745 * limitations under the License.
746 */
747/**
748 * A class that holds metadata about a Repo object
749 */
750var RepoInfo = /** @class */ (function () {
751 /**
752 * @param host - Hostname portion of the url for the repo
753 * @param secure - Whether or not this repo is accessed over ssl
754 * @param namespace - The namespace represented by the repo
755 * @param webSocketOnly - Whether to prefer websockets over all other transports (used by Nest).
756 * @param nodeAdmin - Whether this instance uses Admin SDK credentials
757 * @param persistenceKey - Override the default session persistence storage key
758 */
759 function RepoInfo(host, secure, namespace, webSocketOnly, nodeAdmin, persistenceKey, includeNamespaceInQueryParams, isUsingEmulator) {
760 if (nodeAdmin === void 0) { nodeAdmin = false; }
761 if (persistenceKey === void 0) { persistenceKey = ''; }
762 if (includeNamespaceInQueryParams === void 0) { includeNamespaceInQueryParams = false; }
763 if (isUsingEmulator === void 0) { isUsingEmulator = false; }
764 this.secure = secure;
765 this.namespace = namespace;
766 this.webSocketOnly = webSocketOnly;
767 this.nodeAdmin = nodeAdmin;
768 this.persistenceKey = persistenceKey;
769 this.includeNamespaceInQueryParams = includeNamespaceInQueryParams;
770 this.isUsingEmulator = isUsingEmulator;
771 this._host = host.toLowerCase();
772 this._domain = this._host.substr(this._host.indexOf('.') + 1);
773 this.internalHost =
774 PersistentStorage.get('host:' + host) || this._host;
775 }
776 RepoInfo.prototype.isCacheableHost = function () {
777 return this.internalHost.substr(0, 2) === 's-';
778 };
779 RepoInfo.prototype.isCustomHost = function () {
780 return (this._domain !== 'firebaseio.com' &&
781 this._domain !== 'firebaseio-demo.com');
782 };
783 Object.defineProperty(RepoInfo.prototype, "host", {
784 get: function () {
785 return this._host;
786 },
787 set: function (newHost) {
788 if (newHost !== this.internalHost) {
789 this.internalHost = newHost;
790 if (this.isCacheableHost()) {
791 PersistentStorage.set('host:' + this._host, this.internalHost);
792 }
793 }
794 },
795 enumerable: false,
796 configurable: true
797 });
798 RepoInfo.prototype.toString = function () {
799 var str = this.toURLString();
800 if (this.persistenceKey) {
801 str += '<' + this.persistenceKey + '>';
802 }
803 return str;
804 };
805 RepoInfo.prototype.toURLString = function () {
806 var protocol = this.secure ? 'https://' : 'http://';
807 var query = this.includeNamespaceInQueryParams
808 ? "?ns=".concat(this.namespace)
809 : '';
810 return "".concat(protocol).concat(this.host, "/").concat(query);
811 };
812 return RepoInfo;
813}());
814function repoInfoNeedsQueryParam(repoInfo) {
815 return (repoInfo.host !== repoInfo.internalHost ||
816 repoInfo.isCustomHost() ||
817 repoInfo.includeNamespaceInQueryParams);
818}
819/**
820 * Returns the websocket URL for this repo
821 * @param repoInfo - RepoInfo object
822 * @param type - of connection
823 * @param params - list
824 * @returns The URL for this repo
825 */
826function repoInfoConnectionURL(repoInfo, type, params) {
827 util.assert(typeof type === 'string', 'typeof type must == string');
828 util.assert(typeof params === 'object', 'typeof params must == object');
829 var connURL;
830 if (type === WEBSOCKET) {
831 connURL =
832 (repoInfo.secure ? 'wss://' : 'ws://') + repoInfo.internalHost + '/.ws?';
833 }
834 else if (type === LONG_POLLING) {
835 connURL =
836 (repoInfo.secure ? 'https://' : 'http://') +
837 repoInfo.internalHost +
838 '/.lp?';
839 }
840 else {
841 throw new Error('Unknown connection type: ' + type);
842 }
843 if (repoInfoNeedsQueryParam(repoInfo)) {
844 params['ns'] = repoInfo.namespace;
845 }
846 var pairs = [];
847 each(params, function (key, value) {
848 pairs.push(key + '=' + value);
849 });
850 return connURL + pairs.join('&');
851}
852
853/**
854 * @license
855 * Copyright 2017 Google LLC
856 *
857 * Licensed under the Apache License, Version 2.0 (the "License");
858 * you may not use this file except in compliance with the License.
859 * You may obtain a copy of the License at
860 *
861 * http://www.apache.org/licenses/LICENSE-2.0
862 *
863 * Unless required by applicable law or agreed to in writing, software
864 * distributed under the License is distributed on an "AS IS" BASIS,
865 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
866 * See the License for the specific language governing permissions and
867 * limitations under the License.
868 */
869/**
870 * Tracks a collection of stats.
871 */
872var StatsCollection = /** @class */ (function () {
873 function StatsCollection() {
874 this.counters_ = {};
875 }
876 StatsCollection.prototype.incrementCounter = function (name, amount) {
877 if (amount === void 0) { amount = 1; }
878 if (!util.contains(this.counters_, name)) {
879 this.counters_[name] = 0;
880 }
881 this.counters_[name] += amount;
882 };
883 StatsCollection.prototype.get = function () {
884 return util.deepCopy(this.counters_);
885 };
886 return StatsCollection;
887}());
888
889/**
890 * @license
891 * Copyright 2017 Google LLC
892 *
893 * Licensed under the Apache License, Version 2.0 (the "License");
894 * you may not use this file except in compliance with the License.
895 * You may obtain a copy of the License at
896 *
897 * http://www.apache.org/licenses/LICENSE-2.0
898 *
899 * Unless required by applicable law or agreed to in writing, software
900 * distributed under the License is distributed on an "AS IS" BASIS,
901 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
902 * See the License for the specific language governing permissions and
903 * limitations under the License.
904 */
905var collections = {};
906var reporters = {};
907function statsManagerGetCollection(repoInfo) {
908 var hashString = repoInfo.toString();
909 if (!collections[hashString]) {
910 collections[hashString] = new StatsCollection();
911 }
912 return collections[hashString];
913}
914function statsManagerGetOrCreateReporter(repoInfo, creatorFunction) {
915 var hashString = repoInfo.toString();
916 if (!reporters[hashString]) {
917 reporters[hashString] = creatorFunction();
918 }
919 return reporters[hashString];
920}
921
922/**
923 * @license
924 * Copyright 2019 Google LLC
925 *
926 * Licensed under the Apache License, Version 2.0 (the "License");
927 * you may not use this file except in compliance with the License.
928 * You may obtain a copy of the License at
929 *
930 * http://www.apache.org/licenses/LICENSE-2.0
931 *
932 * Unless required by applicable law or agreed to in writing, software
933 * distributed under the License is distributed on an "AS IS" BASIS,
934 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
935 * See the License for the specific language governing permissions and
936 * limitations under the License.
937 */
938/** The semver (www.semver.org) version of the SDK. */
939var SDK_VERSION = '';
940/**
941 * SDK_VERSION should be set before any database instance is created
942 * @internal
943 */
944function setSDKVersion(version) {
945 SDK_VERSION = version;
946}
947
948/**
949 * @license
950 * Copyright 2017 Google LLC
951 *
952 * Licensed under the Apache License, Version 2.0 (the "License");
953 * you may not use this file except in compliance with the License.
954 * You may obtain a copy of the License at
955 *
956 * http://www.apache.org/licenses/LICENSE-2.0
957 *
958 * Unless required by applicable law or agreed to in writing, software
959 * distributed under the License is distributed on an "AS IS" BASIS,
960 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
961 * See the License for the specific language governing permissions and
962 * limitations under the License.
963 */
964var WEBSOCKET_MAX_FRAME_SIZE = 16384;
965var WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
966var WebSocketImpl = null;
967if (typeof MozWebSocket !== 'undefined') {
968 WebSocketImpl = MozWebSocket;
969}
970else if (typeof WebSocket !== 'undefined') {
971 WebSocketImpl = WebSocket;
972}
973function setWebSocketImpl(impl) {
974 WebSocketImpl = impl;
975}
976/**
977 * Create a new websocket connection with the given callbacks.
978 */
979var WebSocketConnection = /** @class */ (function () {
980 /**
981 * @param connId identifier for this transport
982 * @param repoInfo The info for the websocket endpoint.
983 * @param applicationId The Firebase App ID for this project.
984 * @param appCheckToken The App Check Token for this client.
985 * @param authToken The Auth Token for this client.
986 * @param transportSessionId Optional transportSessionId if this is connecting
987 * to an existing transport session
988 * @param lastSessionId Optional lastSessionId if there was a previous
989 * connection
990 */
991 function WebSocketConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
992 this.connId = connId;
993 this.applicationId = applicationId;
994 this.appCheckToken = appCheckToken;
995 this.authToken = authToken;
996 this.keepaliveTimer = null;
997 this.frames = null;
998 this.totalFrames = 0;
999 this.bytesSent = 0;
1000 this.bytesReceived = 0;
1001 this.log_ = logWrapper(this.connId);
1002 this.stats_ = statsManagerGetCollection(repoInfo);
1003 this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId);
1004 this.nodeAdmin = repoInfo.nodeAdmin;
1005 }
1006 /**
1007 * @param repoInfo - The info for the websocket endpoint.
1008 * @param transportSessionId - Optional transportSessionId if this is connecting to an existing transport
1009 * session
1010 * @param lastSessionId - Optional lastSessionId if there was a previous connection
1011 * @returns connection url
1012 */
1013 WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId, appCheckToken, applicationId) {
1014 var urlParams = {};
1015 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1016 if (!util.isNodeSdk() &&
1017 typeof location !== 'undefined' &&
1018 location.hostname &&
1019 FORGE_DOMAIN_RE.test(location.hostname)) {
1020 urlParams[REFERER_PARAM] = FORGE_REF;
1021 }
1022 if (transportSessionId) {
1023 urlParams[TRANSPORT_SESSION_PARAM] = transportSessionId;
1024 }
1025 if (lastSessionId) {
1026 urlParams[LAST_SESSION_PARAM] = lastSessionId;
1027 }
1028 if (appCheckToken) {
1029 urlParams[APP_CHECK_TOKEN_PARAM] = appCheckToken;
1030 }
1031 if (applicationId) {
1032 urlParams[APPLICATION_ID_PARAM] = applicationId;
1033 }
1034 return repoInfoConnectionURL(repoInfo, WEBSOCKET, urlParams);
1035 };
1036 /**
1037 * @param onMessage - Callback when messages arrive
1038 * @param onDisconnect - Callback with connection lost.
1039 */
1040 WebSocketConnection.prototype.open = function (onMessage, onDisconnect) {
1041 var _this = this;
1042 this.onDisconnect = onDisconnect;
1043 this.onMessage = onMessage;
1044 this.log_('Websocket connecting to ' + this.connURL);
1045 this.everConnected_ = false;
1046 // Assume failure until proven otherwise.
1047 PersistentStorage.set('previous_websocket_failure', true);
1048 try {
1049 var options = void 0;
1050 if (util.isNodeSdk()) {
1051 var device = this.nodeAdmin ? 'AdminNode' : 'Node';
1052 // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
1053 options = {
1054 headers: {
1055 'User-Agent': "Firebase/".concat(PROTOCOL_VERSION, "/").concat(SDK_VERSION, "/").concat(process.platform, "/").concat(device),
1056 'X-Firebase-GMPID': this.applicationId || ''
1057 }
1058 };
1059 // If using Node with admin creds, AppCheck-related checks are unnecessary.
1060 // Note that we send the credentials here even if they aren't admin credentials, which is
1061 // not a problem.
1062 // Note that this header is just used to bypass appcheck, and the token should still be sent
1063 // through the websocket connection once it is established.
1064 if (this.authToken) {
1065 options.headers['Authorization'] = "Bearer ".concat(this.authToken);
1066 }
1067 if (this.appCheckToken) {
1068 options.headers['X-Firebase-AppCheck'] = this.appCheckToken;
1069 }
1070 // Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
1071 var env = process['env'];
1072 var proxy = this.connURL.indexOf('wss://') === 0
1073 ? env['HTTPS_PROXY'] || env['https_proxy']
1074 : env['HTTP_PROXY'] || env['http_proxy'];
1075 if (proxy) {
1076 options['proxy'] = { origin: proxy };
1077 }
1078 }
1079 this.mySock = new WebSocketImpl(this.connURL, [], options);
1080 }
1081 catch (e) {
1082 this.log_('Error instantiating WebSocket.');
1083 var error = e.message || e.data;
1084 if (error) {
1085 this.log_(error);
1086 }
1087 this.onClosed_();
1088 return;
1089 }
1090 this.mySock.onopen = function () {
1091 _this.log_('Websocket connected.');
1092 _this.everConnected_ = true;
1093 };
1094 this.mySock.onclose = function () {
1095 _this.log_('Websocket connection was disconnected.');
1096 _this.mySock = null;
1097 _this.onClosed_();
1098 };
1099 this.mySock.onmessage = function (m) {
1100 _this.handleIncomingFrame(m);
1101 };
1102 this.mySock.onerror = function (e) {
1103 _this.log_('WebSocket error. Closing connection.');
1104 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1105 var error = e.message || e.data;
1106 if (error) {
1107 _this.log_(error);
1108 }
1109 _this.onClosed_();
1110 };
1111 };
1112 /**
1113 * No-op for websockets, we don't need to do anything once the connection is confirmed as open
1114 */
1115 WebSocketConnection.prototype.start = function () { };
1116 WebSocketConnection.forceDisallow = function () {
1117 WebSocketConnection.forceDisallow_ = true;
1118 };
1119 WebSocketConnection.isAvailable = function () {
1120 var isOldAndroid = false;
1121 if (typeof navigator !== 'undefined' && navigator.userAgent) {
1122 var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/;
1123 var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex);
1124 if (oldAndroidMatch && oldAndroidMatch.length > 1) {
1125 if (parseFloat(oldAndroidMatch[1]) < 4.4) {
1126 isOldAndroid = true;
1127 }
1128 }
1129 }
1130 return (!isOldAndroid &&
1131 WebSocketImpl !== null &&
1132 !WebSocketConnection.forceDisallow_);
1133 };
1134 /**
1135 * Returns true if we previously failed to connect with this transport.
1136 */
1137 WebSocketConnection.previouslyFailed = function () {
1138 // If our persistent storage is actually only in-memory storage,
1139 // we default to assuming that it previously failed to be safe.
1140 return (PersistentStorage.isInMemoryStorage ||
1141 PersistentStorage.get('previous_websocket_failure') === true);
1142 };
1143 WebSocketConnection.prototype.markConnectionHealthy = function () {
1144 PersistentStorage.remove('previous_websocket_failure');
1145 };
1146 WebSocketConnection.prototype.appendFrame_ = function (data) {
1147 this.frames.push(data);
1148 if (this.frames.length === this.totalFrames) {
1149 var fullMess = this.frames.join('');
1150 this.frames = null;
1151 var jsonMess = util.jsonEval(fullMess);
1152 //handle the message
1153 this.onMessage(jsonMess);
1154 }
1155 };
1156 /**
1157 * @param frameCount - The number of frames we are expecting from the server
1158 */
1159 WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) {
1160 this.totalFrames = frameCount;
1161 this.frames = [];
1162 };
1163 /**
1164 * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1
1165 * @returns Any remaining data to be process, or null if there is none
1166 */
1167 WebSocketConnection.prototype.extractFrameCount_ = function (data) {
1168 util.assert(this.frames === null, 'We already have a frame buffer');
1169 // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced
1170 // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508
1171 if (data.length <= 6) {
1172 var frameCount = Number(data);
1173 if (!isNaN(frameCount)) {
1174 this.handleNewFrameCount_(frameCount);
1175 return null;
1176 }
1177 }
1178 this.handleNewFrameCount_(1);
1179 return data;
1180 };
1181 /**
1182 * Process a websocket frame that has arrived from the server.
1183 * @param mess - The frame data
1184 */
1185 WebSocketConnection.prototype.handleIncomingFrame = function (mess) {
1186 if (this.mySock === null) {
1187 return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes.
1188 }
1189 var data = mess['data'];
1190 this.bytesReceived += data.length;
1191 this.stats_.incrementCounter('bytes_received', data.length);
1192 this.resetKeepAlive();
1193 if (this.frames !== null) {
1194 // we're buffering
1195 this.appendFrame_(data);
1196 }
1197 else {
1198 // try to parse out a frame count, otherwise, assume 1 and process it
1199 var remainingData = this.extractFrameCount_(data);
1200 if (remainingData !== null) {
1201 this.appendFrame_(remainingData);
1202 }
1203 }
1204 };
1205 /**
1206 * Send a message to the server
1207 * @param data - The JSON object to transmit
1208 */
1209 WebSocketConnection.prototype.send = function (data) {
1210 this.resetKeepAlive();
1211 var dataStr = util.stringify(data);
1212 this.bytesSent += dataStr.length;
1213 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1214 //We can only fit a certain amount in each websocket frame, so we need to split this request
1215 //up into multiple pieces if it doesn't fit in one request.
1216 var dataSegs = splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE);
1217 //Send the length header
1218 if (dataSegs.length > 1) {
1219 this.sendString_(String(dataSegs.length));
1220 }
1221 //Send the actual data in segments.
1222 for (var i = 0; i < dataSegs.length; i++) {
1223 this.sendString_(dataSegs[i]);
1224 }
1225 };
1226 WebSocketConnection.prototype.shutdown_ = function () {
1227 this.isClosed_ = true;
1228 if (this.keepaliveTimer) {
1229 clearInterval(this.keepaliveTimer);
1230 this.keepaliveTimer = null;
1231 }
1232 if (this.mySock) {
1233 this.mySock.close();
1234 this.mySock = null;
1235 }
1236 };
1237 WebSocketConnection.prototype.onClosed_ = function () {
1238 if (!this.isClosed_) {
1239 this.log_('WebSocket is closing itself');
1240 this.shutdown_();
1241 // since this is an internal close, trigger the close listener
1242 if (this.onDisconnect) {
1243 this.onDisconnect(this.everConnected_);
1244 this.onDisconnect = null;
1245 }
1246 }
1247 };
1248 /**
1249 * External-facing close handler.
1250 * Close the websocket and kill the connection.
1251 */
1252 WebSocketConnection.prototype.close = function () {
1253 if (!this.isClosed_) {
1254 this.log_('WebSocket is being closed');
1255 this.shutdown_();
1256 }
1257 };
1258 /**
1259 * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after
1260 * the last activity.
1261 */
1262 WebSocketConnection.prototype.resetKeepAlive = function () {
1263 var _this = this;
1264 clearInterval(this.keepaliveTimer);
1265 this.keepaliveTimer = setInterval(function () {
1266 //If there has been no websocket activity for a while, send a no-op
1267 if (_this.mySock) {
1268 _this.sendString_('0');
1269 }
1270 _this.resetKeepAlive();
1271 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1272 }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL));
1273 };
1274 /**
1275 * Send a string over the websocket.
1276 *
1277 * @param str - String to send.
1278 */
1279 WebSocketConnection.prototype.sendString_ = function (str) {
1280 // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send()
1281 // calls for some unknown reason. We treat these as an error and disconnect.
1282 // See https://app.asana.com/0/58926111402292/68021340250410
1283 try {
1284 this.mySock.send(str);
1285 }
1286 catch (e) {
1287 this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.');
1288 setTimeout(this.onClosed_.bind(this), 0);
1289 }
1290 };
1291 /**
1292 * Number of response before we consider the connection "healthy."
1293 */
1294 WebSocketConnection.responsesRequiredToBeHealthy = 2;
1295 /**
1296 * Time to wait for the connection te become healthy before giving up.
1297 */
1298 WebSocketConnection.healthyTimeout = 30000;
1299 return WebSocketConnection;
1300}());
1301
1302var name = "@firebase/database";
1303var version = "0.14.4";
1304
1305/**
1306 * @license
1307 * Copyright 2021 Google LLC
1308 *
1309 * Licensed under the Apache License, Version 2.0 (the "License");
1310 * you may not use this file except in compliance with the License.
1311 * You may obtain a copy of the License at
1312 *
1313 * http://www.apache.org/licenses/LICENSE-2.0
1314 *
1315 * Unless required by applicable law or agreed to in writing, software
1316 * distributed under the License is distributed on an "AS IS" BASIS,
1317 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1318 * See the License for the specific language governing permissions and
1319 * limitations under the License.
1320 */
1321/**
1322 * Abstraction around AppCheck's token fetching capabilities.
1323 */
1324var AppCheckTokenProvider = /** @class */ (function () {
1325 function AppCheckTokenProvider(appName_, appCheckProvider) {
1326 var _this = this;
1327 this.appName_ = appName_;
1328 this.appCheckProvider = appCheckProvider;
1329 this.appCheck = appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.getImmediate({ optional: true });
1330 if (!this.appCheck) {
1331 appCheckProvider === null || appCheckProvider === void 0 ? void 0 : appCheckProvider.get().then(function (appCheck) { return (_this.appCheck = appCheck); });
1332 }
1333 }
1334 AppCheckTokenProvider.prototype.getToken = function (forceRefresh) {
1335 var _this = this;
1336 if (!this.appCheck) {
1337 return new Promise(function (resolve, reject) {
1338 // Support delayed initialization of FirebaseAppCheck. This allows our
1339 // customers to initialize the RTDB SDK before initializing Firebase
1340 // AppCheck and ensures that all requests are authenticated if a token
1341 // becomes available before the timoeout below expires.
1342 setTimeout(function () {
1343 if (_this.appCheck) {
1344 _this.getToken(forceRefresh).then(resolve, reject);
1345 }
1346 else {
1347 resolve(null);
1348 }
1349 }, 0);
1350 });
1351 }
1352 return this.appCheck.getToken(forceRefresh);
1353 };
1354 AppCheckTokenProvider.prototype.addTokenChangeListener = function (listener) {
1355 var _a;
1356 (_a = this.appCheckProvider) === null || _a === void 0 ? void 0 : _a.get().then(function (appCheck) { return appCheck.addTokenListener(listener); });
1357 };
1358 AppCheckTokenProvider.prototype.notifyForInvalidToken = function () {
1359 warn("Provided AppCheck credentials for the app named \"".concat(this.appName_, "\" ") +
1360 'are invalid. This usually indicates your app was not initialized correctly.');
1361 };
1362 return AppCheckTokenProvider;
1363}());
1364
1365/**
1366 * @license
1367 * Copyright 2017 Google LLC
1368 *
1369 * Licensed under the Apache License, Version 2.0 (the "License");
1370 * you may not use this file except in compliance with the License.
1371 * You may obtain a copy of the License at
1372 *
1373 * http://www.apache.org/licenses/LICENSE-2.0
1374 *
1375 * Unless required by applicable law or agreed to in writing, software
1376 * distributed under the License is distributed on an "AS IS" BASIS,
1377 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1378 * See the License for the specific language governing permissions and
1379 * limitations under the License.
1380 */
1381/**
1382 * Abstraction around FirebaseApp's token fetching capabilities.
1383 */
1384var FirebaseAuthTokenProvider = /** @class */ (function () {
1385 function FirebaseAuthTokenProvider(appName_, firebaseOptions_, authProvider_) {
1386 var _this = this;
1387 this.appName_ = appName_;
1388 this.firebaseOptions_ = firebaseOptions_;
1389 this.authProvider_ = authProvider_;
1390 this.auth_ = null;
1391 this.auth_ = authProvider_.getImmediate({ optional: true });
1392 if (!this.auth_) {
1393 authProvider_.onInit(function (auth) { return (_this.auth_ = auth); });
1394 }
1395 }
1396 FirebaseAuthTokenProvider.prototype.getToken = function (forceRefresh) {
1397 var _this = this;
1398 if (!this.auth_) {
1399 return new Promise(function (resolve, reject) {
1400 // Support delayed initialization of FirebaseAuth. This allows our
1401 // customers to initialize the RTDB SDK before initializing Firebase
1402 // Auth and ensures that all requests are authenticated if a token
1403 // becomes available before the timoeout below expires.
1404 setTimeout(function () {
1405 if (_this.auth_) {
1406 _this.getToken(forceRefresh).then(resolve, reject);
1407 }
1408 else {
1409 resolve(null);
1410 }
1411 }, 0);
1412 });
1413 }
1414 return this.auth_.getToken(forceRefresh).catch(function (error) {
1415 // TODO: Need to figure out all the cases this is raised and whether
1416 // this makes sense.
1417 if (error && error.code === 'auth/token-not-initialized') {
1418 log('Got auth/token-not-initialized error. Treating as null token.');
1419 return null;
1420 }
1421 else {
1422 return Promise.reject(error);
1423 }
1424 });
1425 };
1426 FirebaseAuthTokenProvider.prototype.addTokenChangeListener = function (listener) {
1427 // TODO: We might want to wrap the listener and call it with no args to
1428 // avoid a leaky abstraction, but that makes removing the listener harder.
1429 if (this.auth_) {
1430 this.auth_.addAuthTokenListener(listener);
1431 }
1432 else {
1433 this.authProvider_
1434 .get()
1435 .then(function (auth) { return auth.addAuthTokenListener(listener); });
1436 }
1437 };
1438 FirebaseAuthTokenProvider.prototype.removeTokenChangeListener = function (listener) {
1439 this.authProvider_
1440 .get()
1441 .then(function (auth) { return auth.removeAuthTokenListener(listener); });
1442 };
1443 FirebaseAuthTokenProvider.prototype.notifyForInvalidToken = function () {
1444 var errorMessage = 'Provided authentication credentials for the app named "' +
1445 this.appName_ +
1446 '" are invalid. This usually indicates your app was not ' +
1447 'initialized correctly. ';
1448 if ('credential' in this.firebaseOptions_) {
1449 errorMessage +=
1450 'Make sure the "credential" property provided to initializeApp() ' +
1451 'is authorized to access the specified "databaseURL" and is from the correct ' +
1452 'project.';
1453 }
1454 else if ('serviceAccount' in this.firebaseOptions_) {
1455 errorMessage +=
1456 'Make sure the "serviceAccount" property provided to initializeApp() ' +
1457 'is authorized to access the specified "databaseURL" and is from the correct ' +
1458 'project.';
1459 }
1460 else {
1461 errorMessage +=
1462 'Make sure the "apiKey" and "databaseURL" properties provided to ' +
1463 'initializeApp() match the values provided for your app at ' +
1464 'https://console.firebase.google.com/.';
1465 }
1466 warn(errorMessage);
1467 };
1468 return FirebaseAuthTokenProvider;
1469}());
1470/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */
1471var EmulatorTokenProvider = /** @class */ (function () {
1472 function EmulatorTokenProvider(accessToken) {
1473 this.accessToken = accessToken;
1474 }
1475 EmulatorTokenProvider.prototype.getToken = function (forceRefresh) {
1476 return Promise.resolve({
1477 accessToken: this.accessToken
1478 });
1479 };
1480 EmulatorTokenProvider.prototype.addTokenChangeListener = function (listener) {
1481 // Invoke the listener immediately to match the behavior in Firebase Auth
1482 // (see packages/auth/src/auth.js#L1807)
1483 listener(this.accessToken);
1484 };
1485 EmulatorTokenProvider.prototype.removeTokenChangeListener = function (listener) { };
1486 EmulatorTokenProvider.prototype.notifyForInvalidToken = function () { };
1487 /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */
1488 EmulatorTokenProvider.OWNER = 'owner';
1489 return EmulatorTokenProvider;
1490}());
1491
1492/**
1493 * @license
1494 * Copyright 2017 Google LLC
1495 *
1496 * Licensed under the Apache License, Version 2.0 (the "License");
1497 * you may not use this file except in compliance with the License.
1498 * You may obtain a copy of the License at
1499 *
1500 * http://www.apache.org/licenses/LICENSE-2.0
1501 *
1502 * Unless required by applicable law or agreed to in writing, software
1503 * distributed under the License is distributed on an "AS IS" BASIS,
1504 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1505 * See the License for the specific language governing permissions and
1506 * limitations under the License.
1507 */
1508/**
1509 * This class ensures the packets from the server arrive in order
1510 * This class takes data from the server and ensures it gets passed into the callbacks in order.
1511 */
1512var PacketReceiver = /** @class */ (function () {
1513 /**
1514 * @param onMessage_
1515 */
1516 function PacketReceiver(onMessage_) {
1517 this.onMessage_ = onMessage_;
1518 this.pendingResponses = [];
1519 this.currentResponseNum = 0;
1520 this.closeAfterResponse = -1;
1521 this.onClose = null;
1522 }
1523 PacketReceiver.prototype.closeAfter = function (responseNum, callback) {
1524 this.closeAfterResponse = responseNum;
1525 this.onClose = callback;
1526 if (this.closeAfterResponse < this.currentResponseNum) {
1527 this.onClose();
1528 this.onClose = null;
1529 }
1530 };
1531 /**
1532 * Each message from the server comes with a response number, and an array of data. The responseNumber
1533 * allows us to ensure that we process them in the right order, since we can't be guaranteed that all
1534 * browsers will respond in the same order as the requests we sent
1535 */
1536 PacketReceiver.prototype.handleResponse = function (requestNum, data) {
1537 var _this = this;
1538 this.pendingResponses[requestNum] = data;
1539 var _loop_1 = function () {
1540 var toProcess = this_1.pendingResponses[this_1.currentResponseNum];
1541 delete this_1.pendingResponses[this_1.currentResponseNum];
1542 var _loop_2 = function (i) {
1543 if (toProcess[i]) {
1544 exceptionGuard(function () {
1545 _this.onMessage_(toProcess[i]);
1546 });
1547 }
1548 };
1549 for (var i = 0; i < toProcess.length; ++i) {
1550 _loop_2(i);
1551 }
1552 if (this_1.currentResponseNum === this_1.closeAfterResponse) {
1553 if (this_1.onClose) {
1554 this_1.onClose();
1555 this_1.onClose = null;
1556 }
1557 return "break";
1558 }
1559 this_1.currentResponseNum++;
1560 };
1561 var this_1 = this;
1562 while (this.pendingResponses[this.currentResponseNum]) {
1563 var state_1 = _loop_1();
1564 if (state_1 === "break")
1565 break;
1566 }
1567 };
1568 return PacketReceiver;
1569}());
1570
1571/**
1572 * @license
1573 * Copyright 2017 Google LLC
1574 *
1575 * Licensed under the Apache License, Version 2.0 (the "License");
1576 * you may not use this file except in compliance with the License.
1577 * You may obtain a copy of the License at
1578 *
1579 * http://www.apache.org/licenses/LICENSE-2.0
1580 *
1581 * Unless required by applicable law or agreed to in writing, software
1582 * distributed under the License is distributed on an "AS IS" BASIS,
1583 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1584 * See the License for the specific language governing permissions and
1585 * limitations under the License.
1586 */
1587// URL query parameters associated with longpolling
1588var FIREBASE_LONGPOLL_START_PARAM = 'start';
1589var FIREBASE_LONGPOLL_CLOSE_COMMAND = 'close';
1590var FIREBASE_LONGPOLL_COMMAND_CB_NAME = 'pLPCommand';
1591var FIREBASE_LONGPOLL_DATA_CB_NAME = 'pRTLPCB';
1592var FIREBASE_LONGPOLL_ID_PARAM = 'id';
1593var FIREBASE_LONGPOLL_PW_PARAM = 'pw';
1594var FIREBASE_LONGPOLL_SERIAL_PARAM = 'ser';
1595var FIREBASE_LONGPOLL_CALLBACK_ID_PARAM = 'cb';
1596var FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM = 'seg';
1597var FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET = 'ts';
1598var FIREBASE_LONGPOLL_DATA_PARAM = 'd';
1599var FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM = 'dframe';
1600//Data size constants.
1601//TODO: Perf: the maximum length actually differs from browser to browser.
1602// We should check what browser we're on and set accordingly.
1603var MAX_URL_DATA_SIZE = 1870;
1604var SEG_HEADER_SIZE = 30; //ie: &seg=8299234&ts=982389123&d=
1605var MAX_PAYLOAD_SIZE = MAX_URL_DATA_SIZE - SEG_HEADER_SIZE;
1606/**
1607 * Keepalive period
1608 * send a fresh request at minimum every 25 seconds. Opera has a maximum request
1609 * length of 30 seconds that we can't exceed.
1610 */
1611var KEEPALIVE_REQUEST_INTERVAL = 25000;
1612/**
1613 * How long to wait before aborting a long-polling connection attempt.
1614 */
1615var LP_CONNECT_TIMEOUT = 30000;
1616/**
1617 * This class manages a single long-polling connection.
1618 */
1619var BrowserPollConnection = /** @class */ (function () {
1620 /**
1621 * @param connId An identifier for this connection, used for logging
1622 * @param repoInfo The info for the endpoint to send data to.
1623 * @param applicationId The Firebase App ID for this project.
1624 * @param appCheckToken The AppCheck token for this client.
1625 * @param authToken The AuthToken to use for this connection.
1626 * @param transportSessionId Optional transportSessionid if we are
1627 * reconnecting for an existing transport session
1628 * @param lastSessionId Optional lastSessionId if the PersistentConnection has
1629 * already created a connection previously
1630 */
1631 function BrowserPollConnection(connId, repoInfo, applicationId, appCheckToken, authToken, transportSessionId, lastSessionId) {
1632 var _this = this;
1633 this.connId = connId;
1634 this.repoInfo = repoInfo;
1635 this.applicationId = applicationId;
1636 this.appCheckToken = appCheckToken;
1637 this.authToken = authToken;
1638 this.transportSessionId = transportSessionId;
1639 this.lastSessionId = lastSessionId;
1640 this.bytesSent = 0;
1641 this.bytesReceived = 0;
1642 this.everConnected_ = false;
1643 this.log_ = logWrapper(connId);
1644 this.stats_ = statsManagerGetCollection(repoInfo);
1645 this.urlFn = function (params) {
1646 // Always add the token if we have one.
1647 if (_this.appCheckToken) {
1648 params[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1649 }
1650 return repoInfoConnectionURL(repoInfo, LONG_POLLING, params);
1651 };
1652 }
1653 /**
1654 * @param onMessage - Callback when messages arrive
1655 * @param onDisconnect - Callback with connection lost.
1656 */
1657 BrowserPollConnection.prototype.open = function (onMessage, onDisconnect) {
1658 var _this = this;
1659 this.curSegmentNum = 0;
1660 this.onDisconnect_ = onDisconnect;
1661 this.myPacketOrderer = new PacketReceiver(onMessage);
1662 this.isClosed_ = false;
1663 this.connectTimeoutTimer_ = setTimeout(function () {
1664 _this.log_('Timed out trying to connect.');
1665 // Make sure we clear the host cache
1666 _this.onClosed_();
1667 _this.connectTimeoutTimer_ = null;
1668 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1669 }, Math.floor(LP_CONNECT_TIMEOUT));
1670 // Ensure we delay the creation of the iframe until the DOM is loaded.
1671 executeWhenDOMReady(function () {
1672 if (_this.isClosed_) {
1673 return;
1674 }
1675 //Set up a callback that gets triggered once a connection is set up.
1676 _this.scriptTagHolder = new FirebaseIFrameScriptHolder(function () {
1677 var args = [];
1678 for (var _i = 0; _i < arguments.length; _i++) {
1679 args[_i] = arguments[_i];
1680 }
1681 var _a = tslib.__read(args, 5), command = _a[0], arg1 = _a[1], arg2 = _a[2]; _a[3]; _a[4];
1682 _this.incrementIncomingBytes_(args);
1683 if (!_this.scriptTagHolder) {
1684 return; // we closed the connection.
1685 }
1686 if (_this.connectTimeoutTimer_) {
1687 clearTimeout(_this.connectTimeoutTimer_);
1688 _this.connectTimeoutTimer_ = null;
1689 }
1690 _this.everConnected_ = true;
1691 if (command === FIREBASE_LONGPOLL_START_PARAM) {
1692 _this.id = arg1;
1693 _this.password = arg2;
1694 }
1695 else if (command === FIREBASE_LONGPOLL_CLOSE_COMMAND) {
1696 // Don't clear the host cache. We got a response from the server, so we know it's reachable
1697 if (arg1) {
1698 // We aren't expecting any more data (other than what the server's already in the process of sending us
1699 // through our already open polls), so don't send any more.
1700 _this.scriptTagHolder.sendNewPolls = false;
1701 // arg1 in this case is the last response number sent by the server. We should try to receive
1702 // all of the responses up to this one before closing
1703 _this.myPacketOrderer.closeAfter(arg1, function () {
1704 _this.onClosed_();
1705 });
1706 }
1707 else {
1708 _this.onClosed_();
1709 }
1710 }
1711 else {
1712 throw new Error('Unrecognized command received: ' + command);
1713 }
1714 }, function () {
1715 var args = [];
1716 for (var _i = 0; _i < arguments.length; _i++) {
1717 args[_i] = arguments[_i];
1718 }
1719 var _a = tslib.__read(args, 2), pN = _a[0], data = _a[1];
1720 _this.incrementIncomingBytes_(args);
1721 _this.myPacketOrderer.handleResponse(pN, data);
1722 }, function () {
1723 _this.onClosed_();
1724 }, _this.urlFn);
1725 //Send the initial request to connect. The serial number is simply to keep the browser from pulling previous results
1726 //from cache.
1727 var urlParams = {};
1728 urlParams[FIREBASE_LONGPOLL_START_PARAM] = 't';
1729 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = Math.floor(Math.random() * 100000000);
1730 if (_this.scriptTagHolder.uniqueCallbackIdentifier) {
1731 urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] =
1732 _this.scriptTagHolder.uniqueCallbackIdentifier;
1733 }
1734 urlParams[VERSION_PARAM] = PROTOCOL_VERSION;
1735 if (_this.transportSessionId) {
1736 urlParams[TRANSPORT_SESSION_PARAM] = _this.transportSessionId;
1737 }
1738 if (_this.lastSessionId) {
1739 urlParams[LAST_SESSION_PARAM] = _this.lastSessionId;
1740 }
1741 if (_this.applicationId) {
1742 urlParams[APPLICATION_ID_PARAM] = _this.applicationId;
1743 }
1744 if (_this.appCheckToken) {
1745 urlParams[APP_CHECK_TOKEN_PARAM] = _this.appCheckToken;
1746 }
1747 if (typeof location !== 'undefined' &&
1748 location.hostname &&
1749 FORGE_DOMAIN_RE.test(location.hostname)) {
1750 urlParams[REFERER_PARAM] = FORGE_REF;
1751 }
1752 var connectURL = _this.urlFn(urlParams);
1753 _this.log_('Connecting via long-poll to ' + connectURL);
1754 _this.scriptTagHolder.addTag(connectURL, function () {
1755 /* do nothing */
1756 });
1757 });
1758 };
1759 /**
1760 * Call this when a handshake has completed successfully and we want to consider the connection established
1761 */
1762 BrowserPollConnection.prototype.start = function () {
1763 this.scriptTagHolder.startLongPoll(this.id, this.password);
1764 this.addDisconnectPingFrame(this.id, this.password);
1765 };
1766 /**
1767 * Forces long polling to be considered as a potential transport
1768 */
1769 BrowserPollConnection.forceAllow = function () {
1770 BrowserPollConnection.forceAllow_ = true;
1771 };
1772 /**
1773 * Forces longpolling to not be considered as a potential transport
1774 */
1775 BrowserPollConnection.forceDisallow = function () {
1776 BrowserPollConnection.forceDisallow_ = true;
1777 };
1778 // Static method, use string literal so it can be accessed in a generic way
1779 BrowserPollConnection.isAvailable = function () {
1780 if (util.isNodeSdk()) {
1781 return false;
1782 }
1783 else if (BrowserPollConnection.forceAllow_) {
1784 return true;
1785 }
1786 else {
1787 // NOTE: In React-Native there's normally no 'document', but if you debug a React-Native app in
1788 // the Chrome debugger, 'document' is defined, but document.createElement is null (2015/06/08).
1789 return (!BrowserPollConnection.forceDisallow_ &&
1790 typeof document !== 'undefined' &&
1791 document.createElement != null &&
1792 !isChromeExtensionContentScript() &&
1793 !isWindowsStoreApp());
1794 }
1795 };
1796 /**
1797 * No-op for polling
1798 */
1799 BrowserPollConnection.prototype.markConnectionHealthy = function () { };
1800 /**
1801 * Stops polling and cleans up the iframe
1802 */
1803 BrowserPollConnection.prototype.shutdown_ = function () {
1804 this.isClosed_ = true;
1805 if (this.scriptTagHolder) {
1806 this.scriptTagHolder.close();
1807 this.scriptTagHolder = null;
1808 }
1809 //remove the disconnect frame, which will trigger an XHR call to the server to tell it we're leaving.
1810 if (this.myDisconnFrame) {
1811 document.body.removeChild(this.myDisconnFrame);
1812 this.myDisconnFrame = null;
1813 }
1814 if (this.connectTimeoutTimer_) {
1815 clearTimeout(this.connectTimeoutTimer_);
1816 this.connectTimeoutTimer_ = null;
1817 }
1818 };
1819 /**
1820 * Triggered when this transport is closed
1821 */
1822 BrowserPollConnection.prototype.onClosed_ = function () {
1823 if (!this.isClosed_) {
1824 this.log_('Longpoll is closing itself');
1825 this.shutdown_();
1826 if (this.onDisconnect_) {
1827 this.onDisconnect_(this.everConnected_);
1828 this.onDisconnect_ = null;
1829 }
1830 }
1831 };
1832 /**
1833 * External-facing close handler. RealTime has requested we shut down. Kill our connection and tell the server
1834 * that we've left.
1835 */
1836 BrowserPollConnection.prototype.close = function () {
1837 if (!this.isClosed_) {
1838 this.log_('Longpoll is being closed.');
1839 this.shutdown_();
1840 }
1841 };
1842 /**
1843 * Send the JSON object down to the server. It will need to be stringified, base64 encoded, and then
1844 * broken into chunks (since URLs have a small maximum length).
1845 * @param data - The JSON data to transmit.
1846 */
1847 BrowserPollConnection.prototype.send = function (data) {
1848 var dataStr = util.stringify(data);
1849 this.bytesSent += dataStr.length;
1850 this.stats_.incrementCounter('bytes_sent', dataStr.length);
1851 //first, lets get the base64-encoded data
1852 var base64data = util.base64Encode(dataStr);
1853 //We can only fit a certain amount in each URL, so we need to split this request
1854 //up into multiple pieces if it doesn't fit in one request.
1855 var dataSegs = splitStringBySize(base64data, MAX_PAYLOAD_SIZE);
1856 //Enqueue each segment for transmission. We assign each chunk a sequential ID and a total number
1857 //of segments so that we can reassemble the packet on the server.
1858 for (var i = 0; i < dataSegs.length; i++) {
1859 this.scriptTagHolder.enqueueSegment(this.curSegmentNum, dataSegs.length, dataSegs[i]);
1860 this.curSegmentNum++;
1861 }
1862 };
1863 /**
1864 * This is how we notify the server that we're leaving.
1865 * We aren't able to send requests with DHTML on a window close event, but we can
1866 * trigger XHR requests in some browsers (everything but Opera basically).
1867 */
1868 BrowserPollConnection.prototype.addDisconnectPingFrame = function (id, pw) {
1869 if (util.isNodeSdk()) {
1870 return;
1871 }
1872 this.myDisconnFrame = document.createElement('iframe');
1873 var urlParams = {};
1874 urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
1875 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = id;
1876 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = pw;
1877 this.myDisconnFrame.src = this.urlFn(urlParams);
1878 this.myDisconnFrame.style.display = 'none';
1879 document.body.appendChild(this.myDisconnFrame);
1880 };
1881 /**
1882 * Used to track the bytes received by this client
1883 */
1884 BrowserPollConnection.prototype.incrementIncomingBytes_ = function (args) {
1885 // TODO: This is an annoying perf hit just to track the number of incoming bytes. Maybe it should be opt-in.
1886 var bytesReceived = util.stringify(args).length;
1887 this.bytesReceived += bytesReceived;
1888 this.stats_.incrementCounter('bytes_received', bytesReceived);
1889 };
1890 return BrowserPollConnection;
1891}());
1892/*********************************************************************************************
1893 * A wrapper around an iframe that is used as a long-polling script holder.
1894 *********************************************************************************************/
1895var FirebaseIFrameScriptHolder = /** @class */ (function () {
1896 /**
1897 * @param commandCB - The callback to be called when control commands are recevied from the server.
1898 * @param onMessageCB - The callback to be triggered when responses arrive from the server.
1899 * @param onDisconnect - The callback to be triggered when this tag holder is closed
1900 * @param urlFn - A function that provides the URL of the endpoint to send data to.
1901 */
1902 function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnect, urlFn) {
1903 this.onDisconnect = onDisconnect;
1904 this.urlFn = urlFn;
1905 //We maintain a count of all of the outstanding requests, because if we have too many active at once it can cause
1906 //problems in some browsers.
1907 this.outstandingRequests = new Set();
1908 //A queue of the pending segments waiting for transmission to the server.
1909 this.pendingSegs = [];
1910 //A serial number. We use this for two things:
1911 // 1) A way to ensure the browser doesn't cache responses to polls
1912 // 2) A way to make the server aware when long-polls arrive in a different order than we started them. The
1913 // server needs to release both polls in this case or it will cause problems in Opera since Opera can only execute
1914 // JSONP code in the order it was added to the iframe.
1915 this.currentSerial = Math.floor(Math.random() * 100000000);
1916 // This gets set to false when we're "closing down" the connection (e.g. we're switching transports but there's still
1917 // incoming data from the server that we're waiting for).
1918 this.sendNewPolls = true;
1919 if (!util.isNodeSdk()) {
1920 //Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
1921 //iframes where we put the long-polling script tags. We have two callbacks:
1922 // 1) Command Callback - Triggered for control issues, like starting a connection.
1923 // 2) Message Callback - Triggered when new data arrives.
1924 this.uniqueCallbackIdentifier = LUIDGenerator();
1925 window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
1926 window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] =
1927 onMessageCB;
1928 //Create an iframe for us to add script tags to.
1929 this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_();
1930 // Set the iframe's contents.
1931 var script = '';
1932 // if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
1933 // for ie9, but ie8 needs to do it again in the document itself.
1934 if (this.myIFrame.src &&
1935 this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
1936 var currentDomain = document.domain;
1937 script = '<script>document.domain="' + currentDomain + '";</script>';
1938 }
1939 var iframeContents = '<html><body>' + script + '</body></html>';
1940 try {
1941 this.myIFrame.doc.open();
1942 this.myIFrame.doc.write(iframeContents);
1943 this.myIFrame.doc.close();
1944 }
1945 catch (e) {
1946 log('frame writing exception');
1947 if (e.stack) {
1948 log(e.stack);
1949 }
1950 log(e);
1951 }
1952 }
1953 else {
1954 this.commandCB = commandCB;
1955 this.onMessageCB = onMessageCB;
1956 }
1957 }
1958 /**
1959 * Each browser has its own funny way to handle iframes. Here we mush them all together into one object that I can
1960 * actually use.
1961 */
1962 FirebaseIFrameScriptHolder.createIFrame_ = function () {
1963 var iframe = document.createElement('iframe');
1964 iframe.style.display = 'none';
1965 // This is necessary in order to initialize the document inside the iframe
1966 if (document.body) {
1967 document.body.appendChild(iframe);
1968 try {
1969 // If document.domain has been modified in IE, this will throw an error, and we need to set the
1970 // domain of the iframe's document manually. We can do this via a javascript: url as the src attribute
1971 // Also note that we must do this *after* the iframe has been appended to the page. Otherwise it doesn't work.
1972 var a = iframe.contentWindow.document;
1973 if (!a) {
1974 // Apologies for the log-spam, I need to do something to keep closure from optimizing out the assignment above.
1975 log('No IE domain setting required');
1976 }
1977 }
1978 catch (e) {
1979 var domain = document.domain;
1980 iframe.src =
1981 "javascript:void((function(){document.open();document.domain='" +
1982 domain +
1983 "';document.close();})())";
1984 }
1985 }
1986 else {
1987 // LongPollConnection attempts to delay initialization until the document is ready, so hopefully this
1988 // never gets hit.
1989 throw 'Document body has not initialized. Wait to initialize Firebase until after the document is ready.';
1990 }
1991 // Get the document of the iframe in a browser-specific way.
1992 if (iframe.contentDocument) {
1993 iframe.doc = iframe.contentDocument; // Firefox, Opera, Safari
1994 }
1995 else if (iframe.contentWindow) {
1996 iframe.doc = iframe.contentWindow.document; // Internet Explorer
1997 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1998 }
1999 else if (iframe.document) {
2000 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2001 iframe.doc = iframe.document; //others?
2002 }
2003 return iframe;
2004 };
2005 /**
2006 * Cancel all outstanding queries and remove the frame.
2007 */
2008 FirebaseIFrameScriptHolder.prototype.close = function () {
2009 var _this = this;
2010 //Mark this iframe as dead, so no new requests are sent.
2011 this.alive = false;
2012 if (this.myIFrame) {
2013 //We have to actually remove all of the html inside this iframe before removing it from the
2014 //window, or IE will continue loading and executing the script tags we've already added, which
2015 //can lead to some errors being thrown. Setting textContent seems to be the safest way to do this.
2016 this.myIFrame.doc.body.textContent = '';
2017 setTimeout(function () {
2018 if (_this.myIFrame !== null) {
2019 document.body.removeChild(_this.myIFrame);
2020 _this.myIFrame = null;
2021 }
2022 }, Math.floor(0));
2023 }
2024 // Protect from being called recursively.
2025 var onDisconnect = this.onDisconnect;
2026 if (onDisconnect) {
2027 this.onDisconnect = null;
2028 onDisconnect();
2029 }
2030 };
2031 /**
2032 * Actually start the long-polling session by adding the first script tag(s) to the iframe.
2033 * @param id - The ID of this connection
2034 * @param pw - The password for this connection
2035 */
2036 FirebaseIFrameScriptHolder.prototype.startLongPoll = function (id, pw) {
2037 this.myID = id;
2038 this.myPW = pw;
2039 this.alive = true;
2040 //send the initial request. If there are requests queued, make sure that we transmit as many as we are currently able to.
2041 while (this.newRequest_()) { }
2042 };
2043 /**
2044 * This is called any time someone might want a script tag to be added. It adds a script tag when there aren't
2045 * too many outstanding requests and we are still alive.
2046 *
2047 * If there are outstanding packet segments to send, it sends one. If there aren't, it sends a long-poll anyways if
2048 * needed.
2049 */
2050 FirebaseIFrameScriptHolder.prototype.newRequest_ = function () {
2051 // We keep one outstanding request open all the time to receive data, but if we need to send data
2052 // (pendingSegs.length > 0) then we create a new request to send the data. The server will automatically
2053 // close the old request.
2054 if (this.alive &&
2055 this.sendNewPolls &&
2056 this.outstandingRequests.size < (this.pendingSegs.length > 0 ? 2 : 1)) {
2057 //construct our url
2058 this.currentSerial++;
2059 var urlParams = {};
2060 urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
2061 urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
2062 urlParams[FIREBASE_LONGPOLL_SERIAL_PARAM] = this.currentSerial;
2063 var theURL = this.urlFn(urlParams);
2064 //Now add as much data as we can.
2065 var curDataString = '';
2066 var i = 0;
2067 while (this.pendingSegs.length > 0) {
2068 //first, lets see if the next segment will fit.
2069 var nextSeg = this.pendingSegs[0];
2070 if (nextSeg.d.length +
2071 SEG_HEADER_SIZE +
2072 curDataString.length <=
2073 MAX_URL_DATA_SIZE) {
2074 //great, the segment will fit. Lets append it.
2075 var theSeg = this.pendingSegs.shift();
2076 curDataString =
2077 curDataString +
2078 '&' +
2079 FIREBASE_LONGPOLL_SEGMENT_NUM_PARAM +
2080 i +
2081 '=' +
2082 theSeg.seg +
2083 '&' +
2084 FIREBASE_LONGPOLL_SEGMENTS_IN_PACKET +
2085 i +
2086 '=' +
2087 theSeg.ts +
2088 '&' +
2089 FIREBASE_LONGPOLL_DATA_PARAM +
2090 i +
2091 '=' +
2092 theSeg.d;
2093 i++;
2094 }
2095 else {
2096 break;
2097 }
2098 }
2099 theURL = theURL + curDataString;
2100 this.addLongPollTag_(theURL, this.currentSerial);
2101 return true;
2102 }
2103 else {
2104 return false;
2105 }
2106 };
2107 /**
2108 * Queue a packet for transmission to the server.
2109 * @param segnum - A sequential id for this packet segment used for reassembly
2110 * @param totalsegs - The total number of segments in this packet
2111 * @param data - The data for this segment.
2112 */
2113 FirebaseIFrameScriptHolder.prototype.enqueueSegment = function (segnum, totalsegs, data) {
2114 //add this to the queue of segments to send.
2115 this.pendingSegs.push({ seg: segnum, ts: totalsegs, d: data });
2116 //send the data immediately if there isn't already data being transmitted, unless
2117 //startLongPoll hasn't been called yet.
2118 if (this.alive) {
2119 this.newRequest_();
2120 }
2121 };
2122 /**
2123 * Add a script tag for a regular long-poll request.
2124 * @param url - The URL of the script tag.
2125 * @param serial - The serial number of the request.
2126 */
2127 FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function (url, serial) {
2128 var _this = this;
2129 //remember that we sent this request.
2130 this.outstandingRequests.add(serial);
2131 var doNewRequest = function () {
2132 _this.outstandingRequests.delete(serial);
2133 _this.newRequest_();
2134 };
2135 // If this request doesn't return on its own accord (by the server sending us some data), we'll
2136 // create a new one after the KEEPALIVE interval to make sure we always keep a fresh request open.
2137 var keepaliveTimeout = setTimeout(doNewRequest, Math.floor(KEEPALIVE_REQUEST_INTERVAL));
2138 var readyStateCB = function () {
2139 // Request completed. Cancel the keepalive.
2140 clearTimeout(keepaliveTimeout);
2141 // Trigger a new request so we can continue receiving data.
2142 doNewRequest();
2143 };
2144 this.addTag(url, readyStateCB);
2145 };
2146 /**
2147 * Add an arbitrary script tag to the iframe.
2148 * @param url - The URL for the script tag source.
2149 * @param loadCB - A callback to be triggered once the script has loaded.
2150 */
2151 FirebaseIFrameScriptHolder.prototype.addTag = function (url, loadCB) {
2152 var _this = this;
2153 if (util.isNodeSdk()) {
2154 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2155 this.doNodeLongPoll(url, loadCB);
2156 }
2157 else {
2158 setTimeout(function () {
2159 try {
2160 // if we're already closed, don't add this poll
2161 if (!_this.sendNewPolls) {
2162 return;
2163 }
2164 var newScript_1 = _this.myIFrame.doc.createElement('script');
2165 newScript_1.type = 'text/javascript';
2166 newScript_1.async = true;
2167 newScript_1.src = url;
2168 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2169 newScript_1.onload = newScript_1.onreadystatechange =
2170 function () {
2171 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2172 var rstate = newScript_1.readyState;
2173 if (!rstate || rstate === 'loaded' || rstate === 'complete') {
2174 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2175 newScript_1.onload = newScript_1.onreadystatechange = null;
2176 if (newScript_1.parentNode) {
2177 newScript_1.parentNode.removeChild(newScript_1);
2178 }
2179 loadCB();
2180 }
2181 };
2182 newScript_1.onerror = function () {
2183 log('Long-poll script failed to load: ' + url);
2184 _this.sendNewPolls = false;
2185 _this.close();
2186 };
2187 _this.myIFrame.doc.body.appendChild(newScript_1);
2188 }
2189 catch (e) {
2190 // TODO: we should make this error visible somehow
2191 }
2192 }, Math.floor(1));
2193 }
2194 };
2195 return FirebaseIFrameScriptHolder;
2196}());
2197
2198/**
2199 * @license
2200 * Copyright 2017 Google LLC
2201 *
2202 * Licensed under the Apache License, Version 2.0 (the "License");
2203 * you may not use this file except in compliance with the License.
2204 * You may obtain a copy of the License at
2205 *
2206 * http://www.apache.org/licenses/LICENSE-2.0
2207 *
2208 * Unless required by applicable law or agreed to in writing, software
2209 * distributed under the License is distributed on an "AS IS" BASIS,
2210 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2211 * See the License for the specific language governing permissions and
2212 * limitations under the License.
2213 */
2214/**
2215 * Currently simplistic, this class manages what transport a Connection should use at various stages of its
2216 * lifecycle.
2217 *
2218 * It starts with longpolling in a browser, and httppolling on node. It then upgrades to websockets if
2219 * they are available.
2220 */
2221var TransportManager = /** @class */ (function () {
2222 /**
2223 * @param repoInfo - Metadata around the namespace we're connecting to
2224 */
2225 function TransportManager(repoInfo) {
2226 this.initTransports_(repoInfo);
2227 }
2228 Object.defineProperty(TransportManager, "ALL_TRANSPORTS", {
2229 get: function () {
2230 return [BrowserPollConnection, WebSocketConnection];
2231 },
2232 enumerable: false,
2233 configurable: true
2234 });
2235 Object.defineProperty(TransportManager, "IS_TRANSPORT_INITIALIZED", {
2236 /**
2237 * Returns whether transport has been selected to ensure WebSocketConnection or BrowserPollConnection are not called after
2238 * TransportManager has already set up transports_
2239 */
2240 get: function () {
2241 return this.globalTransportInitialized_;
2242 },
2243 enumerable: false,
2244 configurable: true
2245 });
2246 TransportManager.prototype.initTransports_ = function (repoInfo) {
2247 var e_1, _a;
2248 var isWebSocketsAvailable = WebSocketConnection && WebSocketConnection['isAvailable']();
2249 var isSkipPollConnection = isWebSocketsAvailable && !WebSocketConnection.previouslyFailed();
2250 if (repoInfo.webSocketOnly) {
2251 if (!isWebSocketsAvailable) {
2252 warn("wss:// URL used, but browser isn't known to support websockets. Trying anyway.");
2253 }
2254 isSkipPollConnection = true;
2255 }
2256 if (isSkipPollConnection) {
2257 this.transports_ = [WebSocketConnection];
2258 }
2259 else {
2260 var transports = (this.transports_ = []);
2261 try {
2262 for (var _b = tslib.__values(TransportManager.ALL_TRANSPORTS), _c = _b.next(); !_c.done; _c = _b.next()) {
2263 var transport = _c.value;
2264 if (transport && transport['isAvailable']()) {
2265 transports.push(transport);
2266 }
2267 }
2268 }
2269 catch (e_1_1) { e_1 = { error: e_1_1 }; }
2270 finally {
2271 try {
2272 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
2273 }
2274 finally { if (e_1) throw e_1.error; }
2275 }
2276 TransportManager.globalTransportInitialized_ = true;
2277 }
2278 };
2279 /**
2280 * @returns The constructor for the initial transport to use
2281 */
2282 TransportManager.prototype.initialTransport = function () {
2283 if (this.transports_.length > 0) {
2284 return this.transports_[0];
2285 }
2286 else {
2287 throw new Error('No transports available');
2288 }
2289 };
2290 /**
2291 * @returns The constructor for the next transport, or null
2292 */
2293 TransportManager.prototype.upgradeTransport = function () {
2294 if (this.transports_.length > 1) {
2295 return this.transports_[1];
2296 }
2297 else {
2298 return null;
2299 }
2300 };
2301 // Keeps track of whether the TransportManager has already chosen a transport to use
2302 TransportManager.globalTransportInitialized_ = false;
2303 return TransportManager;
2304}());
2305
2306/**
2307 * @license
2308 * Copyright 2017 Google LLC
2309 *
2310 * Licensed under the Apache License, Version 2.0 (the "License");
2311 * you may not use this file except in compliance with the License.
2312 * You may obtain a copy of the License at
2313 *
2314 * http://www.apache.org/licenses/LICENSE-2.0
2315 *
2316 * Unless required by applicable law or agreed to in writing, software
2317 * distributed under the License is distributed on an "AS IS" BASIS,
2318 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2319 * See the License for the specific language governing permissions and
2320 * limitations under the License.
2321 */
2322// Abort upgrade attempt if it takes longer than 60s.
2323var UPGRADE_TIMEOUT = 60000;
2324// For some transports (WebSockets), we need to "validate" the transport by exchanging a few requests and responses.
2325// If we haven't sent enough requests within 5s, we'll start sending noop ping requests.
2326var DELAY_BEFORE_SENDING_EXTRA_REQUESTS = 5000;
2327// 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)
2328// then we may not be able to exchange our ping/pong requests within the healthy timeout. So if we reach the timeout
2329// but we've sent/received enough bytes, we don't cancel the connection.
2330var BYTES_SENT_HEALTHY_OVERRIDE = 10 * 1024;
2331var BYTES_RECEIVED_HEALTHY_OVERRIDE = 100 * 1024;
2332var MESSAGE_TYPE = 't';
2333var MESSAGE_DATA = 'd';
2334var CONTROL_SHUTDOWN = 's';
2335var CONTROL_RESET = 'r';
2336var CONTROL_ERROR = 'e';
2337var CONTROL_PONG = 'o';
2338var SWITCH_ACK = 'a';
2339var END_TRANSMISSION = 'n';
2340var PING = 'p';
2341var SERVER_HELLO = 'h';
2342/**
2343 * Creates a new real-time connection to the server using whichever method works
2344 * best in the current browser.
2345 */
2346var Connection = /** @class */ (function () {
2347 /**
2348 * @param id - an id for this connection
2349 * @param repoInfo_ - the info for the endpoint to connect to
2350 * @param applicationId_ - the Firebase App ID for this project
2351 * @param appCheckToken_ - The App Check Token for this device.
2352 * @param authToken_ - The auth token for this session.
2353 * @param onMessage_ - the callback to be triggered when a server-push message arrives
2354 * @param onReady_ - the callback to be triggered when this connection is ready to send messages.
2355 * @param onDisconnect_ - the callback to be triggered when a connection was lost
2356 * @param onKill_ - the callback to be triggered when this connection has permanently shut down.
2357 * @param lastSessionId - last session id in persistent connection. is used to clean up old session in real-time server
2358 */
2359 function Connection(id, repoInfo_, applicationId_, appCheckToken_, authToken_, onMessage_, onReady_, onDisconnect_, onKill_, lastSessionId) {
2360 this.id = id;
2361 this.repoInfo_ = repoInfo_;
2362 this.applicationId_ = applicationId_;
2363 this.appCheckToken_ = appCheckToken_;
2364 this.authToken_ = authToken_;
2365 this.onMessage_ = onMessage_;
2366 this.onReady_ = onReady_;
2367 this.onDisconnect_ = onDisconnect_;
2368 this.onKill_ = onKill_;
2369 this.lastSessionId = lastSessionId;
2370 this.connectionCount = 0;
2371 this.pendingDataMessages = [];
2372 this.state_ = 0 /* RealtimeState.CONNECTING */;
2373 this.log_ = logWrapper('c:' + this.id + ':');
2374 this.transportManager_ = new TransportManager(repoInfo_);
2375 this.log_('Connection created');
2376 this.start_();
2377 }
2378 /**
2379 * Starts a connection attempt
2380 */
2381 Connection.prototype.start_ = function () {
2382 var _this = this;
2383 var conn = this.transportManager_.initialTransport();
2384 this.conn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, null, this.lastSessionId);
2385 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2386 // can consider the transport healthy.
2387 this.primaryResponsesRequired_ = conn['responsesRequiredToBeHealthy'] || 0;
2388 var onMessageReceived = this.connReceiver_(this.conn_);
2389 var onConnectionLost = this.disconnReceiver_(this.conn_);
2390 this.tx_ = this.conn_;
2391 this.rx_ = this.conn_;
2392 this.secondaryConn_ = null;
2393 this.isHealthy_ = false;
2394 /*
2395 * Firefox doesn't like when code from one iframe tries to create another iframe by way of the parent frame.
2396 * This can occur in the case of a redirect, i.e. we guessed wrong on what server to connect to and received a reset.
2397 * Somehow, setTimeout seems to make this ok. That doesn't make sense from a security perspective, since you should
2398 * still have the context of your originating frame.
2399 */
2400 setTimeout(function () {
2401 // this.conn_ gets set to null in some of the tests. Check to make sure it still exists before using it
2402 _this.conn_ && _this.conn_.open(onMessageReceived, onConnectionLost);
2403 }, Math.floor(0));
2404 var healthyTimeoutMS = conn['healthyTimeout'] || 0;
2405 if (healthyTimeoutMS > 0) {
2406 this.healthyTimeout_ = setTimeoutNonBlocking(function () {
2407 _this.healthyTimeout_ = null;
2408 if (!_this.isHealthy_) {
2409 if (_this.conn_ &&
2410 _this.conn_.bytesReceived > BYTES_RECEIVED_HEALTHY_OVERRIDE) {
2411 _this.log_('Connection exceeded healthy timeout but has received ' +
2412 _this.conn_.bytesReceived +
2413 ' bytes. Marking connection healthy.');
2414 _this.isHealthy_ = true;
2415 _this.conn_.markConnectionHealthy();
2416 }
2417 else if (_this.conn_ &&
2418 _this.conn_.bytesSent > BYTES_SENT_HEALTHY_OVERRIDE) {
2419 _this.log_('Connection exceeded healthy timeout but has sent ' +
2420 _this.conn_.bytesSent +
2421 ' bytes. Leaving connection alive.');
2422 // NOTE: We don't want to mark it healthy, since we have no guarantee that the bytes have made it to
2423 // the server.
2424 }
2425 else {
2426 _this.log_('Closing unhealthy connection after timeout.');
2427 _this.close();
2428 }
2429 }
2430 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2431 }, Math.floor(healthyTimeoutMS));
2432 }
2433 };
2434 Connection.prototype.nextTransportId_ = function () {
2435 return 'c:' + this.id + ':' + this.connectionCount++;
2436 };
2437 Connection.prototype.disconnReceiver_ = function (conn) {
2438 var _this = this;
2439 return function (everConnected) {
2440 if (conn === _this.conn_) {
2441 _this.onConnectionLost_(everConnected);
2442 }
2443 else if (conn === _this.secondaryConn_) {
2444 _this.log_('Secondary connection lost.');
2445 _this.onSecondaryConnectionLost_();
2446 }
2447 else {
2448 _this.log_('closing an old connection');
2449 }
2450 };
2451 };
2452 Connection.prototype.connReceiver_ = function (conn) {
2453 var _this = this;
2454 return function (message) {
2455 if (_this.state_ !== 2 /* RealtimeState.DISCONNECTED */) {
2456 if (conn === _this.rx_) {
2457 _this.onPrimaryMessageReceived_(message);
2458 }
2459 else if (conn === _this.secondaryConn_) {
2460 _this.onSecondaryMessageReceived_(message);
2461 }
2462 else {
2463 _this.log_('message on old connection');
2464 }
2465 }
2466 };
2467 };
2468 /**
2469 * @param dataMsg - An arbitrary data message to be sent to the server
2470 */
2471 Connection.prototype.sendRequest = function (dataMsg) {
2472 // wrap in a data message envelope and send it on
2473 var msg = { t: 'd', d: dataMsg };
2474 this.sendData_(msg);
2475 };
2476 Connection.prototype.tryCleanupConnection = function () {
2477 if (this.tx_ === this.secondaryConn_ && this.rx_ === this.secondaryConn_) {
2478 this.log_('cleaning up and promoting a connection: ' + this.secondaryConn_.connId);
2479 this.conn_ = this.secondaryConn_;
2480 this.secondaryConn_ = null;
2481 // the server will shutdown the old connection
2482 }
2483 };
2484 Connection.prototype.onSecondaryControl_ = function (controlData) {
2485 if (MESSAGE_TYPE in controlData) {
2486 var cmd = controlData[MESSAGE_TYPE];
2487 if (cmd === SWITCH_ACK) {
2488 this.upgradeIfSecondaryHealthy_();
2489 }
2490 else if (cmd === CONTROL_RESET) {
2491 // Most likely the session wasn't valid. Abandon the switch attempt
2492 this.log_('Got a reset on secondary, closing it');
2493 this.secondaryConn_.close();
2494 // If we were already using this connection for something, than we need to fully close
2495 if (this.tx_ === this.secondaryConn_ ||
2496 this.rx_ === this.secondaryConn_) {
2497 this.close();
2498 }
2499 }
2500 else if (cmd === CONTROL_PONG) {
2501 this.log_('got pong on secondary.');
2502 this.secondaryResponsesRequired_--;
2503 this.upgradeIfSecondaryHealthy_();
2504 }
2505 }
2506 };
2507 Connection.prototype.onSecondaryMessageReceived_ = function (parsedData) {
2508 var layer = requireKey('t', parsedData);
2509 var data = requireKey('d', parsedData);
2510 if (layer === 'c') {
2511 this.onSecondaryControl_(data);
2512 }
2513 else if (layer === 'd') {
2514 // got a data message, but we're still second connection. Need to buffer it up
2515 this.pendingDataMessages.push(data);
2516 }
2517 else {
2518 throw new Error('Unknown protocol layer: ' + layer);
2519 }
2520 };
2521 Connection.prototype.upgradeIfSecondaryHealthy_ = function () {
2522 if (this.secondaryResponsesRequired_ <= 0) {
2523 this.log_('Secondary connection is healthy.');
2524 this.isHealthy_ = true;
2525 this.secondaryConn_.markConnectionHealthy();
2526 this.proceedWithUpgrade_();
2527 }
2528 else {
2529 // Send a ping to make sure the connection is healthy.
2530 this.log_('sending ping on secondary.');
2531 this.secondaryConn_.send({ t: 'c', d: { t: PING, d: {} } });
2532 }
2533 };
2534 Connection.prototype.proceedWithUpgrade_ = function () {
2535 // tell this connection to consider itself open
2536 this.secondaryConn_.start();
2537 // send ack
2538 this.log_('sending client ack on secondary');
2539 this.secondaryConn_.send({ t: 'c', d: { t: SWITCH_ACK, d: {} } });
2540 // send end packet on primary transport, switch to sending on this one
2541 // can receive on this one, buffer responses until end received on primary transport
2542 this.log_('Ending transmission on primary');
2543 this.conn_.send({ t: 'c', d: { t: END_TRANSMISSION, d: {} } });
2544 this.tx_ = this.secondaryConn_;
2545 this.tryCleanupConnection();
2546 };
2547 Connection.prototype.onPrimaryMessageReceived_ = function (parsedData) {
2548 // Must refer to parsedData properties in quotes, so closure doesn't touch them.
2549 var layer = requireKey('t', parsedData);
2550 var data = requireKey('d', parsedData);
2551 if (layer === 'c') {
2552 this.onControl_(data);
2553 }
2554 else if (layer === 'd') {
2555 this.onDataMessage_(data);
2556 }
2557 };
2558 Connection.prototype.onDataMessage_ = function (message) {
2559 this.onPrimaryResponse_();
2560 // We don't do anything with data messages, just kick them up a level
2561 this.onMessage_(message);
2562 };
2563 Connection.prototype.onPrimaryResponse_ = function () {
2564 if (!this.isHealthy_) {
2565 this.primaryResponsesRequired_--;
2566 if (this.primaryResponsesRequired_ <= 0) {
2567 this.log_('Primary connection is healthy.');
2568 this.isHealthy_ = true;
2569 this.conn_.markConnectionHealthy();
2570 }
2571 }
2572 };
2573 Connection.prototype.onControl_ = function (controlData) {
2574 var cmd = requireKey(MESSAGE_TYPE, controlData);
2575 if (MESSAGE_DATA in controlData) {
2576 var payload = controlData[MESSAGE_DATA];
2577 if (cmd === SERVER_HELLO) {
2578 var handshakePayload = tslib.__assign({}, payload);
2579 if (this.repoInfo_.isUsingEmulator) {
2580 // Upon connecting, the emulator will pass the hostname that it's aware of, but we prefer the user's set hostname via `connectDatabaseEmulator` over what the emulator passes.
2581 handshakePayload.h = this.repoInfo_.host;
2582 }
2583 this.onHandshake_(handshakePayload);
2584 }
2585 else if (cmd === END_TRANSMISSION) {
2586 this.log_('recvd end transmission on primary');
2587 this.rx_ = this.secondaryConn_;
2588 for (var i = 0; i < this.pendingDataMessages.length; ++i) {
2589 this.onDataMessage_(this.pendingDataMessages[i]);
2590 }
2591 this.pendingDataMessages = [];
2592 this.tryCleanupConnection();
2593 }
2594 else if (cmd === CONTROL_SHUTDOWN) {
2595 // This was previously the 'onKill' callback passed to the lower-level connection
2596 // payload in this case is the reason for the shutdown. Generally a human-readable error
2597 this.onConnectionShutdown_(payload);
2598 }
2599 else if (cmd === CONTROL_RESET) {
2600 // payload in this case is the host we should contact
2601 this.onReset_(payload);
2602 }
2603 else if (cmd === CONTROL_ERROR) {
2604 error('Server Error: ' + payload);
2605 }
2606 else if (cmd === CONTROL_PONG) {
2607 this.log_('got pong on primary.');
2608 this.onPrimaryResponse_();
2609 this.sendPingOnPrimaryIfNecessary_();
2610 }
2611 else {
2612 error('Unknown control packet command: ' + cmd);
2613 }
2614 }
2615 };
2616 /**
2617 * @param handshake - The handshake data returned from the server
2618 */
2619 Connection.prototype.onHandshake_ = function (handshake) {
2620 var timestamp = handshake.ts;
2621 var version = handshake.v;
2622 var host = handshake.h;
2623 this.sessionId = handshake.s;
2624 this.repoInfo_.host = host;
2625 // if we've already closed the connection, then don't bother trying to progress further
2626 if (this.state_ === 0 /* RealtimeState.CONNECTING */) {
2627 this.conn_.start();
2628 this.onConnectionEstablished_(this.conn_, timestamp);
2629 if (PROTOCOL_VERSION !== version) {
2630 warn('Protocol version mismatch detected');
2631 }
2632 // TODO: do we want to upgrade? when? maybe a delay?
2633 this.tryStartUpgrade_();
2634 }
2635 };
2636 Connection.prototype.tryStartUpgrade_ = function () {
2637 var conn = this.transportManager_.upgradeTransport();
2638 if (conn) {
2639 this.startUpgrade_(conn);
2640 }
2641 };
2642 Connection.prototype.startUpgrade_ = function (conn) {
2643 var _this = this;
2644 this.secondaryConn_ = new conn(this.nextTransportId_(), this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, this.sessionId);
2645 // For certain transports (WebSockets), we need to send and receive several messages back and forth before we
2646 // can consider the transport healthy.
2647 this.secondaryResponsesRequired_ =
2648 conn['responsesRequiredToBeHealthy'] || 0;
2649 var onMessage = this.connReceiver_(this.secondaryConn_);
2650 var onDisconnect = this.disconnReceiver_(this.secondaryConn_);
2651 this.secondaryConn_.open(onMessage, onDisconnect);
2652 // If we haven't successfully upgraded after UPGRADE_TIMEOUT, give up and kill the secondary.
2653 setTimeoutNonBlocking(function () {
2654 if (_this.secondaryConn_) {
2655 _this.log_('Timed out trying to upgrade.');
2656 _this.secondaryConn_.close();
2657 }
2658 }, Math.floor(UPGRADE_TIMEOUT));
2659 };
2660 Connection.prototype.onReset_ = function (host) {
2661 this.log_('Reset packet received. New host: ' + host);
2662 this.repoInfo_.host = host;
2663 // TODO: if we're already "connected", we need to trigger a disconnect at the next layer up.
2664 // We don't currently support resets after the connection has already been established
2665 if (this.state_ === 1 /* RealtimeState.CONNECTED */) {
2666 this.close();
2667 }
2668 else {
2669 // Close whatever connections we have open and start again.
2670 this.closeConnections_();
2671 this.start_();
2672 }
2673 };
2674 Connection.prototype.onConnectionEstablished_ = function (conn, timestamp) {
2675 var _this = this;
2676 this.log_('Realtime connection established.');
2677 this.conn_ = conn;
2678 this.state_ = 1 /* RealtimeState.CONNECTED */;
2679 if (this.onReady_) {
2680 this.onReady_(timestamp, this.sessionId);
2681 this.onReady_ = null;
2682 }
2683 // If after 5 seconds we haven't sent enough requests to the server to get the connection healthy,
2684 // send some pings.
2685 if (this.primaryResponsesRequired_ === 0) {
2686 this.log_('Primary connection is healthy.');
2687 this.isHealthy_ = true;
2688 }
2689 else {
2690 setTimeoutNonBlocking(function () {
2691 _this.sendPingOnPrimaryIfNecessary_();
2692 }, Math.floor(DELAY_BEFORE_SENDING_EXTRA_REQUESTS));
2693 }
2694 };
2695 Connection.prototype.sendPingOnPrimaryIfNecessary_ = function () {
2696 // If the connection isn't considered healthy yet, we'll send a noop ping packet request.
2697 if (!this.isHealthy_ && this.state_ === 1 /* RealtimeState.CONNECTED */) {
2698 this.log_('sending ping on primary.');
2699 this.sendData_({ t: 'c', d: { t: PING, d: {} } });
2700 }
2701 };
2702 Connection.prototype.onSecondaryConnectionLost_ = function () {
2703 var conn = this.secondaryConn_;
2704 this.secondaryConn_ = null;
2705 if (this.tx_ === conn || this.rx_ === conn) {
2706 // we are relying on this connection already in some capacity. Therefore, a failure is real
2707 this.close();
2708 }
2709 };
2710 /**
2711 * @param everConnected - Whether or not the connection ever reached a server. Used to determine if
2712 * we should flush the host cache
2713 */
2714 Connection.prototype.onConnectionLost_ = function (everConnected) {
2715 this.conn_ = null;
2716 // NOTE: IF you're seeing a Firefox error for this line, I think it might be because it's getting
2717 // called on window close and RealtimeState.CONNECTING is no longer defined. Just a guess.
2718 if (!everConnected && this.state_ === 0 /* RealtimeState.CONNECTING */) {
2719 this.log_('Realtime connection failed.');
2720 // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
2721 if (this.repoInfo_.isCacheableHost()) {
2722 PersistentStorage.remove('host:' + this.repoInfo_.host);
2723 // reset the internal host to what we would show the user, i.e. <ns>.firebaseio.com
2724 this.repoInfo_.internalHost = this.repoInfo_.host;
2725 }
2726 }
2727 else if (this.state_ === 1 /* RealtimeState.CONNECTED */) {
2728 this.log_('Realtime connection lost.');
2729 }
2730 this.close();
2731 };
2732 Connection.prototype.onConnectionShutdown_ = function (reason) {
2733 this.log_('Connection shutdown command received. Shutting down...');
2734 if (this.onKill_) {
2735 this.onKill_(reason);
2736 this.onKill_ = null;
2737 }
2738 // We intentionally don't want to fire onDisconnect (kill is a different case),
2739 // so clear the callback.
2740 this.onDisconnect_ = null;
2741 this.close();
2742 };
2743 Connection.prototype.sendData_ = function (data) {
2744 if (this.state_ !== 1 /* RealtimeState.CONNECTED */) {
2745 throw 'Connection is not connected';
2746 }
2747 else {
2748 this.tx_.send(data);
2749 }
2750 };
2751 /**
2752 * Cleans up this connection, calling the appropriate callbacks
2753 */
2754 Connection.prototype.close = function () {
2755 if (this.state_ !== 2 /* RealtimeState.DISCONNECTED */) {
2756 this.log_('Closing realtime connection.');
2757 this.state_ = 2 /* RealtimeState.DISCONNECTED */;
2758 this.closeConnections_();
2759 if (this.onDisconnect_) {
2760 this.onDisconnect_();
2761 this.onDisconnect_ = null;
2762 }
2763 }
2764 };
2765 Connection.prototype.closeConnections_ = function () {
2766 this.log_('Shutting down all connections');
2767 if (this.conn_) {
2768 this.conn_.close();
2769 this.conn_ = null;
2770 }
2771 if (this.secondaryConn_) {
2772 this.secondaryConn_.close();
2773 this.secondaryConn_ = null;
2774 }
2775 if (this.healthyTimeout_) {
2776 clearTimeout(this.healthyTimeout_);
2777 this.healthyTimeout_ = null;
2778 }
2779 };
2780 return Connection;
2781}());
2782
2783/**
2784 * @license
2785 * Copyright 2017 Google LLC
2786 *
2787 * Licensed under the Apache License, Version 2.0 (the "License");
2788 * you may not use this file except in compliance with the License.
2789 * You may obtain a copy of the License at
2790 *
2791 * http://www.apache.org/licenses/LICENSE-2.0
2792 *
2793 * Unless required by applicable law or agreed to in writing, software
2794 * distributed under the License is distributed on an "AS IS" BASIS,
2795 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2796 * See the License for the specific language governing permissions and
2797 * limitations under the License.
2798 */
2799/**
2800 * Interface defining the set of actions that can be performed against the Firebase server
2801 * (basically corresponds to our wire protocol).
2802 *
2803 * @interface
2804 */
2805var ServerActions = /** @class */ (function () {
2806 function ServerActions() {
2807 }
2808 ServerActions.prototype.put = function (pathString, data, onComplete, hash) { };
2809 ServerActions.prototype.merge = function (pathString, data, onComplete, hash) { };
2810 /**
2811 * Refreshes the auth token for the current connection.
2812 * @param token - The authentication token
2813 */
2814 ServerActions.prototype.refreshAuthToken = function (token) { };
2815 /**
2816 * Refreshes the app check token for the current connection.
2817 * @param token The app check token
2818 */
2819 ServerActions.prototype.refreshAppCheckToken = function (token) { };
2820 ServerActions.prototype.onDisconnectPut = function (pathString, data, onComplete) { };
2821 ServerActions.prototype.onDisconnectMerge = function (pathString, data, onComplete) { };
2822 ServerActions.prototype.onDisconnectCancel = function (pathString, onComplete) { };
2823 ServerActions.prototype.reportStats = function (stats) { };
2824 return ServerActions;
2825}());
2826
2827/**
2828 * @license
2829 * Copyright 2017 Google LLC
2830 *
2831 * Licensed under the Apache License, Version 2.0 (the "License");
2832 * you may not use this file except in compliance with the License.
2833 * You may obtain a copy of the License at
2834 *
2835 * http://www.apache.org/licenses/LICENSE-2.0
2836 *
2837 * Unless required by applicable law or agreed to in writing, software
2838 * distributed under the License is distributed on an "AS IS" BASIS,
2839 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2840 * See the License for the specific language governing permissions and
2841 * limitations under the License.
2842 */
2843/**
2844 * Base class to be used if you want to emit events. Call the constructor with
2845 * the set of allowed event names.
2846 */
2847var EventEmitter = /** @class */ (function () {
2848 function EventEmitter(allowedEvents_) {
2849 this.allowedEvents_ = allowedEvents_;
2850 this.listeners_ = {};
2851 util.assert(Array.isArray(allowedEvents_) && allowedEvents_.length > 0, 'Requires a non-empty array');
2852 }
2853 /**
2854 * To be called by derived classes to trigger events.
2855 */
2856 EventEmitter.prototype.trigger = function (eventType) {
2857 var varArgs = [];
2858 for (var _i = 1; _i < arguments.length; _i++) {
2859 varArgs[_i - 1] = arguments[_i];
2860 }
2861 if (Array.isArray(this.listeners_[eventType])) {
2862 // Clone the list, since callbacks could add/remove listeners.
2863 var listeners = tslib.__spreadArray([], tslib.__read(this.listeners_[eventType]), false);
2864 for (var i = 0; i < listeners.length; i++) {
2865 listeners[i].callback.apply(listeners[i].context, varArgs);
2866 }
2867 }
2868 };
2869 EventEmitter.prototype.on = function (eventType, callback, context) {
2870 this.validateEventType_(eventType);
2871 this.listeners_[eventType] = this.listeners_[eventType] || [];
2872 this.listeners_[eventType].push({ callback: callback, context: context });
2873 var eventData = this.getInitialEvent(eventType);
2874 if (eventData) {
2875 callback.apply(context, eventData);
2876 }
2877 };
2878 EventEmitter.prototype.off = function (eventType, callback, context) {
2879 this.validateEventType_(eventType);
2880 var listeners = this.listeners_[eventType] || [];
2881 for (var i = 0; i < listeners.length; i++) {
2882 if (listeners[i].callback === callback &&
2883 (!context || context === listeners[i].context)) {
2884 listeners.splice(i, 1);
2885 return;
2886 }
2887 }
2888 };
2889 EventEmitter.prototype.validateEventType_ = function (eventType) {
2890 util.assert(this.allowedEvents_.find(function (et) {
2891 return et === eventType;
2892 }), 'Unknown event: ' + eventType);
2893 };
2894 return EventEmitter;
2895}());
2896
2897/**
2898 * @license
2899 * Copyright 2017 Google LLC
2900 *
2901 * Licensed under the Apache License, Version 2.0 (the "License");
2902 * you may not use this file except in compliance with the License.
2903 * You may obtain a copy of the License at
2904 *
2905 * http://www.apache.org/licenses/LICENSE-2.0
2906 *
2907 * Unless required by applicable law or agreed to in writing, software
2908 * distributed under the License is distributed on an "AS IS" BASIS,
2909 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2910 * See the License for the specific language governing permissions and
2911 * limitations under the License.
2912 */
2913/**
2914 * Monitors online state (as reported by window.online/offline events).
2915 *
2916 * The expectation is that this could have many false positives (thinks we are online
2917 * when we're not), but no false negatives. So we can safely use it to determine when
2918 * we definitely cannot reach the internet.
2919 */
2920var OnlineMonitor = /** @class */ (function (_super) {
2921 tslib.__extends(OnlineMonitor, _super);
2922 function OnlineMonitor() {
2923 var _this = _super.call(this, ['online']) || this;
2924 _this.online_ = true;
2925 // We've had repeated complaints that Cordova apps can get stuck "offline", e.g.
2926 // https://forum.ionicframework.com/t/firebase-connection-is-lost-and-never-come-back/43810
2927 // It would seem that the 'online' event does not always fire consistently. So we disable it
2928 // for Cordova.
2929 if (typeof window !== 'undefined' &&
2930 typeof window.addEventListener !== 'undefined' &&
2931 !util.isMobileCordova()) {
2932 window.addEventListener('online', function () {
2933 if (!_this.online_) {
2934 _this.online_ = true;
2935 _this.trigger('online', true);
2936 }
2937 }, false);
2938 window.addEventListener('offline', function () {
2939 if (_this.online_) {
2940 _this.online_ = false;
2941 _this.trigger('online', false);
2942 }
2943 }, false);
2944 }
2945 return _this;
2946 }
2947 OnlineMonitor.getInstance = function () {
2948 return new OnlineMonitor();
2949 };
2950 OnlineMonitor.prototype.getInitialEvent = function (eventType) {
2951 util.assert(eventType === 'online', 'Unknown event type: ' + eventType);
2952 return [this.online_];
2953 };
2954 OnlineMonitor.prototype.currentlyOnline = function () {
2955 return this.online_;
2956 };
2957 return OnlineMonitor;
2958}(EventEmitter));
2959
2960/**
2961 * @license
2962 * Copyright 2017 Google LLC
2963 *
2964 * Licensed under the Apache License, Version 2.0 (the "License");
2965 * you may not use this file except in compliance with the License.
2966 * You may obtain a copy of the License at
2967 *
2968 * http://www.apache.org/licenses/LICENSE-2.0
2969 *
2970 * Unless required by applicable law or agreed to in writing, software
2971 * distributed under the License is distributed on an "AS IS" BASIS,
2972 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2973 * See the License for the specific language governing permissions and
2974 * limitations under the License.
2975 */
2976/** Maximum key depth. */
2977var MAX_PATH_DEPTH = 32;
2978/** Maximum number of (UTF8) bytes in a Firebase path. */
2979var MAX_PATH_LENGTH_BYTES = 768;
2980/**
2981 * An immutable object representing a parsed path. It's immutable so that you
2982 * can pass them around to other functions without worrying about them changing
2983 * it.
2984 */
2985var Path = /** @class */ (function () {
2986 /**
2987 * @param pathOrString - Path string to parse, or another path, or the raw
2988 * tokens array
2989 */
2990 function Path(pathOrString, pieceNum) {
2991 if (pieceNum === void 0) {
2992 this.pieces_ = pathOrString.split('/');
2993 // Remove empty pieces.
2994 var copyTo = 0;
2995 for (var i = 0; i < this.pieces_.length; i++) {
2996 if (this.pieces_[i].length > 0) {
2997 this.pieces_[copyTo] = this.pieces_[i];
2998 copyTo++;
2999 }
3000 }
3001 this.pieces_.length = copyTo;
3002 this.pieceNum_ = 0;
3003 }
3004 else {
3005 this.pieces_ = pathOrString;
3006 this.pieceNum_ = pieceNum;
3007 }
3008 }
3009 Path.prototype.toString = function () {
3010 var pathString = '';
3011 for (var i = this.pieceNum_; i < this.pieces_.length; i++) {
3012 if (this.pieces_[i] !== '') {
3013 pathString += '/' + this.pieces_[i];
3014 }
3015 }
3016 return pathString || '/';
3017 };
3018 return Path;
3019}());
3020function newEmptyPath() {
3021 return new Path('');
3022}
3023function pathGetFront(path) {
3024 if (path.pieceNum_ >= path.pieces_.length) {
3025 return null;
3026 }
3027 return path.pieces_[path.pieceNum_];
3028}
3029/**
3030 * @returns The number of segments in this path
3031 */
3032function pathGetLength(path) {
3033 return path.pieces_.length - path.pieceNum_;
3034}
3035function pathPopFront(path) {
3036 var pieceNum = path.pieceNum_;
3037 if (pieceNum < path.pieces_.length) {
3038 pieceNum++;
3039 }
3040 return new Path(path.pieces_, pieceNum);
3041}
3042function pathGetBack(path) {
3043 if (path.pieceNum_ < path.pieces_.length) {
3044 return path.pieces_[path.pieces_.length - 1];
3045 }
3046 return null;
3047}
3048function pathToUrlEncodedString(path) {
3049 var pathString = '';
3050 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3051 if (path.pieces_[i] !== '') {
3052 pathString += '/' + encodeURIComponent(String(path.pieces_[i]));
3053 }
3054 }
3055 return pathString || '/';
3056}
3057/**
3058 * Shallow copy of the parts of the path.
3059 *
3060 */
3061function pathSlice(path, begin) {
3062 if (begin === void 0) { begin = 0; }
3063 return path.pieces_.slice(path.pieceNum_ + begin);
3064}
3065function pathParent(path) {
3066 if (path.pieceNum_ >= path.pieces_.length) {
3067 return null;
3068 }
3069 var pieces = [];
3070 for (var i = path.pieceNum_; i < path.pieces_.length - 1; i++) {
3071 pieces.push(path.pieces_[i]);
3072 }
3073 return new Path(pieces, 0);
3074}
3075function pathChild(path, childPathObj) {
3076 var pieces = [];
3077 for (var i = path.pieceNum_; i < path.pieces_.length; i++) {
3078 pieces.push(path.pieces_[i]);
3079 }
3080 if (childPathObj instanceof Path) {
3081 for (var i = childPathObj.pieceNum_; i < childPathObj.pieces_.length; i++) {
3082 pieces.push(childPathObj.pieces_[i]);
3083 }
3084 }
3085 else {
3086 var childPieces = childPathObj.split('/');
3087 for (var i = 0; i < childPieces.length; i++) {
3088 if (childPieces[i].length > 0) {
3089 pieces.push(childPieces[i]);
3090 }
3091 }
3092 }
3093 return new Path(pieces, 0);
3094}
3095/**
3096 * @returns True if there are no segments in this path
3097 */
3098function pathIsEmpty(path) {
3099 return path.pieceNum_ >= path.pieces_.length;
3100}
3101/**
3102 * @returns The path from outerPath to innerPath
3103 */
3104function newRelativePath(outerPath, innerPath) {
3105 var outer = pathGetFront(outerPath), inner = pathGetFront(innerPath);
3106 if (outer === null) {
3107 return innerPath;
3108 }
3109 else if (outer === inner) {
3110 return newRelativePath(pathPopFront(outerPath), pathPopFront(innerPath));
3111 }
3112 else {
3113 throw new Error('INTERNAL ERROR: innerPath (' +
3114 innerPath +
3115 ') is not within ' +
3116 'outerPath (' +
3117 outerPath +
3118 ')');
3119 }
3120}
3121/**
3122 * @returns -1, 0, 1 if left is less, equal, or greater than the right.
3123 */
3124function pathCompare(left, right) {
3125 var leftKeys = pathSlice(left, 0);
3126 var rightKeys = pathSlice(right, 0);
3127 for (var i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
3128 var cmp = nameCompare(leftKeys[i], rightKeys[i]);
3129 if (cmp !== 0) {
3130 return cmp;
3131 }
3132 }
3133 if (leftKeys.length === rightKeys.length) {
3134 return 0;
3135 }
3136 return leftKeys.length < rightKeys.length ? -1 : 1;
3137}
3138/**
3139 * @returns true if paths are the same.
3140 */
3141function pathEquals(path, other) {
3142 if (pathGetLength(path) !== pathGetLength(other)) {
3143 return false;
3144 }
3145 for (var i = path.pieceNum_, j = other.pieceNum_; i <= path.pieces_.length; i++, j++) {
3146 if (path.pieces_[i] !== other.pieces_[j]) {
3147 return false;
3148 }
3149 }
3150 return true;
3151}
3152/**
3153 * @returns True if this path is a parent of (or the same as) other
3154 */
3155function pathContains(path, other) {
3156 var i = path.pieceNum_;
3157 var j = other.pieceNum_;
3158 if (pathGetLength(path) > pathGetLength(other)) {
3159 return false;
3160 }
3161 while (i < path.pieces_.length) {
3162 if (path.pieces_[i] !== other.pieces_[j]) {
3163 return false;
3164 }
3165 ++i;
3166 ++j;
3167 }
3168 return true;
3169}
3170/**
3171 * Dynamic (mutable) path used to count path lengths.
3172 *
3173 * This class is used to efficiently check paths for valid
3174 * length (in UTF8 bytes) and depth (used in path validation).
3175 *
3176 * Throws Error exception if path is ever invalid.
3177 *
3178 * The definition of a path always begins with '/'.
3179 */
3180var ValidationPath = /** @class */ (function () {
3181 /**
3182 * @param path - Initial Path.
3183 * @param errorPrefix_ - Prefix for any error messages.
3184 */
3185 function ValidationPath(path, errorPrefix_) {
3186 this.errorPrefix_ = errorPrefix_;
3187 this.parts_ = pathSlice(path, 0);
3188 /** Initialize to number of '/' chars needed in path. */
3189 this.byteLength_ = Math.max(1, this.parts_.length);
3190 for (var i = 0; i < this.parts_.length; i++) {
3191 this.byteLength_ += util.stringLength(this.parts_[i]);
3192 }
3193 validationPathCheckValid(this);
3194 }
3195 return ValidationPath;
3196}());
3197function validationPathPush(validationPath, child) {
3198 // Count the needed '/'
3199 if (validationPath.parts_.length > 0) {
3200 validationPath.byteLength_ += 1;
3201 }
3202 validationPath.parts_.push(child);
3203 validationPath.byteLength_ += util.stringLength(child);
3204 validationPathCheckValid(validationPath);
3205}
3206function validationPathPop(validationPath) {
3207 var last = validationPath.parts_.pop();
3208 validationPath.byteLength_ -= util.stringLength(last);
3209 // Un-count the previous '/'
3210 if (validationPath.parts_.length > 0) {
3211 validationPath.byteLength_ -= 1;
3212 }
3213}
3214function validationPathCheckValid(validationPath) {
3215 if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
3216 throw new Error(validationPath.errorPrefix_ +
3217 'has a key path longer than ' +
3218 MAX_PATH_LENGTH_BYTES +
3219 ' bytes (' +
3220 validationPath.byteLength_ +
3221 ').');
3222 }
3223 if (validationPath.parts_.length > MAX_PATH_DEPTH) {
3224 throw new Error(validationPath.errorPrefix_ +
3225 'path specified exceeds the maximum depth that can be written (' +
3226 MAX_PATH_DEPTH +
3227 ') or object contains a cycle ' +
3228 validationPathToErrorString(validationPath));
3229 }
3230}
3231/**
3232 * String for use in error messages - uses '.' notation for path.
3233 */
3234function validationPathToErrorString(validationPath) {
3235 if (validationPath.parts_.length === 0) {
3236 return '';
3237 }
3238 return "in property '" + validationPath.parts_.join('.') + "'";
3239}
3240
3241/**
3242 * @license
3243 * Copyright 2017 Google LLC
3244 *
3245 * Licensed under the Apache License, Version 2.0 (the "License");
3246 * you may not use this file except in compliance with the License.
3247 * You may obtain a copy of the License at
3248 *
3249 * http://www.apache.org/licenses/LICENSE-2.0
3250 *
3251 * Unless required by applicable law or agreed to in writing, software
3252 * distributed under the License is distributed on an "AS IS" BASIS,
3253 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3254 * See the License for the specific language governing permissions and
3255 * limitations under the License.
3256 */
3257var VisibilityMonitor = /** @class */ (function (_super) {
3258 tslib.__extends(VisibilityMonitor, _super);
3259 function VisibilityMonitor() {
3260 var _this = _super.call(this, ['visible']) || this;
3261 var hidden;
3262 var visibilityChange;
3263 if (typeof document !== 'undefined' &&
3264 typeof document.addEventListener !== 'undefined') {
3265 if (typeof document['hidden'] !== 'undefined') {
3266 // Opera 12.10 and Firefox 18 and later support
3267 visibilityChange = 'visibilitychange';
3268 hidden = 'hidden';
3269 }
3270 else if (typeof document['mozHidden'] !== 'undefined') {
3271 visibilityChange = 'mozvisibilitychange';
3272 hidden = 'mozHidden';
3273 }
3274 else if (typeof document['msHidden'] !== 'undefined') {
3275 visibilityChange = 'msvisibilitychange';
3276 hidden = 'msHidden';
3277 }
3278 else if (typeof document['webkitHidden'] !== 'undefined') {
3279 visibilityChange = 'webkitvisibilitychange';
3280 hidden = 'webkitHidden';
3281 }
3282 }
3283 // Initially, we always assume we are visible. This ensures that in browsers
3284 // without page visibility support or in cases where we are never visible
3285 // (e.g. chrome extension), we act as if we are visible, i.e. don't delay
3286 // reconnects
3287 _this.visible_ = true;
3288 if (visibilityChange) {
3289 document.addEventListener(visibilityChange, function () {
3290 var visible = !document[hidden];
3291 if (visible !== _this.visible_) {
3292 _this.visible_ = visible;
3293 _this.trigger('visible', visible);
3294 }
3295 }, false);
3296 }
3297 return _this;
3298 }
3299 VisibilityMonitor.getInstance = function () {
3300 return new VisibilityMonitor();
3301 };
3302 VisibilityMonitor.prototype.getInitialEvent = function (eventType) {
3303 util.assert(eventType === 'visible', 'Unknown event type: ' + eventType);
3304 return [this.visible_];
3305 };
3306 return VisibilityMonitor;
3307}(EventEmitter));
3308
3309/**
3310 * @license
3311 * Copyright 2017 Google LLC
3312 *
3313 * Licensed under the Apache License, Version 2.0 (the "License");
3314 * you may not use this file except in compliance with the License.
3315 * You may obtain a copy of the License at
3316 *
3317 * http://www.apache.org/licenses/LICENSE-2.0
3318 *
3319 * Unless required by applicable law or agreed to in writing, software
3320 * distributed under the License is distributed on an "AS IS" BASIS,
3321 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3322 * See the License for the specific language governing permissions and
3323 * limitations under the License.
3324 */
3325var RECONNECT_MIN_DELAY = 1000;
3326var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
3327var RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
3328var RECONNECT_DELAY_MULTIPLIER = 1.3;
3329var RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
3330var SERVER_KILL_INTERRUPT_REASON = 'server_kill';
3331// If auth fails repeatedly, we'll assume something is wrong and log a warning / back off.
3332var INVALID_TOKEN_THRESHOLD = 3;
3333/**
3334 * Firebase connection. Abstracts wire protocol and handles reconnecting.
3335 *
3336 * NOTE: All JSON objects sent to the realtime connection must have property names enclosed
3337 * in quotes to make sure the closure compiler does not minify them.
3338 */
3339var PersistentConnection = /** @class */ (function (_super) {
3340 tslib.__extends(PersistentConnection, _super);
3341 /**
3342 * @param repoInfo_ - Data about the namespace we are connecting to
3343 * @param applicationId_ - The Firebase App ID for this project
3344 * @param onDataUpdate_ - A callback for new data from the server
3345 */
3346 function PersistentConnection(repoInfo_, applicationId_, onDataUpdate_, onConnectStatus_, onServerInfoUpdate_, authTokenProvider_, appCheckTokenProvider_, authOverride_) {
3347 var _this = _super.call(this) || this;
3348 _this.repoInfo_ = repoInfo_;
3349 _this.applicationId_ = applicationId_;
3350 _this.onDataUpdate_ = onDataUpdate_;
3351 _this.onConnectStatus_ = onConnectStatus_;
3352 _this.onServerInfoUpdate_ = onServerInfoUpdate_;
3353 _this.authTokenProvider_ = authTokenProvider_;
3354 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
3355 _this.authOverride_ = authOverride_;
3356 // Used for diagnostic logging.
3357 _this.id = PersistentConnection.nextPersistentConnectionId_++;
3358 _this.log_ = logWrapper('p:' + _this.id + ':');
3359 _this.interruptReasons_ = {};
3360 _this.listens = new Map();
3361 _this.outstandingPuts_ = [];
3362 _this.outstandingGets_ = [];
3363 _this.outstandingPutCount_ = 0;
3364 _this.outstandingGetCount_ = 0;
3365 _this.onDisconnectRequestQueue_ = [];
3366 _this.connected_ = false;
3367 _this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3368 _this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_DEFAULT;
3369 _this.securityDebugCallback_ = null;
3370 _this.lastSessionId = null;
3371 _this.establishConnectionTimer_ = null;
3372 _this.visible_ = false;
3373 // Before we get connected, we keep a queue of pending messages to send.
3374 _this.requestCBHash_ = {};
3375 _this.requestNumber_ = 0;
3376 _this.realtime_ = null;
3377 _this.authToken_ = null;
3378 _this.appCheckToken_ = null;
3379 _this.forceTokenRefresh_ = false;
3380 _this.invalidAuthTokenCount_ = 0;
3381 _this.invalidAppCheckTokenCount_ = 0;
3382 _this.firstConnection_ = true;
3383 _this.lastConnectionAttemptTime_ = null;
3384 _this.lastConnectionEstablishedTime_ = null;
3385 if (authOverride_ && !util.isNodeSdk()) {
3386 throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
3387 }
3388 VisibilityMonitor.getInstance().on('visible', _this.onVisible_, _this);
3389 if (repoInfo_.host.indexOf('fblocal') === -1) {
3390 OnlineMonitor.getInstance().on('online', _this.onOnline_, _this);
3391 }
3392 return _this;
3393 }
3394 PersistentConnection.prototype.sendRequest = function (action, body, onResponse) {
3395 var curReqNum = ++this.requestNumber_;
3396 var msg = { r: curReqNum, a: action, b: body };
3397 this.log_(util.stringify(msg));
3398 util.assert(this.connected_, "sendRequest call when we're not connected not allowed.");
3399 this.realtime_.sendRequest(msg);
3400 if (onResponse) {
3401 this.requestCBHash_[curReqNum] = onResponse;
3402 }
3403 };
3404 PersistentConnection.prototype.get = function (query) {
3405 this.initConnection_();
3406 var deferred = new util.Deferred();
3407 var request = {
3408 p: query._path.toString(),
3409 q: query._queryObject
3410 };
3411 var outstandingGet = {
3412 action: 'g',
3413 request: request,
3414 onComplete: function (message) {
3415 var payload = message['d'];
3416 if (message['s'] === 'ok') {
3417 deferred.resolve(payload);
3418 }
3419 else {
3420 deferred.reject(payload);
3421 }
3422 }
3423 };
3424 this.outstandingGets_.push(outstandingGet);
3425 this.outstandingGetCount_++;
3426 var index = this.outstandingGets_.length - 1;
3427 if (this.connected_) {
3428 this.sendGet_(index);
3429 }
3430 return deferred.promise;
3431 };
3432 PersistentConnection.prototype.listen = function (query, currentHashFn, tag, onComplete) {
3433 this.initConnection_();
3434 var queryId = query._queryIdentifier;
3435 var pathString = query._path.toString();
3436 this.log_('Listen called for ' + pathString + ' ' + queryId);
3437 if (!this.listens.has(pathString)) {
3438 this.listens.set(pathString, new Map());
3439 }
3440 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'listen() called for non-default but complete query');
3441 util.assert(!this.listens.get(pathString).has(queryId), "listen() called twice for same path/queryId.");
3442 var listenSpec = {
3443 onComplete: onComplete,
3444 hashFn: currentHashFn,
3445 query: query,
3446 tag: tag
3447 };
3448 this.listens.get(pathString).set(queryId, listenSpec);
3449 if (this.connected_) {
3450 this.sendListen_(listenSpec);
3451 }
3452 };
3453 PersistentConnection.prototype.sendGet_ = function (index) {
3454 var _this = this;
3455 var get = this.outstandingGets_[index];
3456 this.sendRequest('g', get.request, function (message) {
3457 delete _this.outstandingGets_[index];
3458 _this.outstandingGetCount_--;
3459 if (_this.outstandingGetCount_ === 0) {
3460 _this.outstandingGets_ = [];
3461 }
3462 if (get.onComplete) {
3463 get.onComplete(message);
3464 }
3465 });
3466 };
3467 PersistentConnection.prototype.sendListen_ = function (listenSpec) {
3468 var _this = this;
3469 var query = listenSpec.query;
3470 var pathString = query._path.toString();
3471 var queryId = query._queryIdentifier;
3472 this.log_('Listen on ' + pathString + ' for ' + queryId);
3473 var req = { /*path*/ p: pathString };
3474 var action = 'q';
3475 // Only bother to send query if it's non-default.
3476 if (listenSpec.tag) {
3477 req['q'] = query._queryObject;
3478 req['t'] = listenSpec.tag;
3479 }
3480 req[ /*hash*/'h'] = listenSpec.hashFn();
3481 this.sendRequest(action, req, function (message) {
3482 var payload = message[ /*data*/'d'];
3483 var status = message[ /*status*/'s'];
3484 // print warnings in any case...
3485 PersistentConnection.warnOnListenWarnings_(payload, query);
3486 var currentListenSpec = _this.listens.get(pathString) &&
3487 _this.listens.get(pathString).get(queryId);
3488 // only trigger actions if the listen hasn't been removed and readded
3489 if (currentListenSpec === listenSpec) {
3490 _this.log_('listen response', message);
3491 if (status !== 'ok') {
3492 _this.removeListen_(pathString, queryId);
3493 }
3494 if (listenSpec.onComplete) {
3495 listenSpec.onComplete(status, payload);
3496 }
3497 }
3498 });
3499 };
3500 PersistentConnection.warnOnListenWarnings_ = function (payload, query) {
3501 if (payload && typeof payload === 'object' && util.contains(payload, 'w')) {
3502 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3503 var warnings = util.safeGet(payload, 'w');
3504 if (Array.isArray(warnings) && ~warnings.indexOf('no_index')) {
3505 var indexSpec = '".indexOn": "' + query._queryParams.getIndex().toString() + '"';
3506 var indexPath = query._path.toString();
3507 warn("Using an unspecified index. Your data will be downloaded and " +
3508 "filtered on the client. Consider adding ".concat(indexSpec, " at ") +
3509 "".concat(indexPath, " to your security rules for better performance."));
3510 }
3511 }
3512 };
3513 PersistentConnection.prototype.refreshAuthToken = function (token) {
3514 this.authToken_ = token;
3515 this.log_('Auth token refreshed');
3516 if (this.authToken_) {
3517 this.tryAuth();
3518 }
3519 else {
3520 //If we're connected we want to let the server know to unauthenticate us. If we're not connected, simply delete
3521 //the credential so we dont become authenticated next time we connect.
3522 if (this.connected_) {
3523 this.sendRequest('unauth', {}, function () { });
3524 }
3525 }
3526 this.reduceReconnectDelayIfAdminCredential_(token);
3527 };
3528 PersistentConnection.prototype.reduceReconnectDelayIfAdminCredential_ = function (credential) {
3529 // NOTE: This isn't intended to be bulletproof (a malicious developer can always just modify the client).
3530 // Additionally, we don't bother resetting the max delay back to the default if auth fails / expires.
3531 var isFirebaseSecret = credential && credential.length === 40;
3532 if (isFirebaseSecret || util.isAdmin(credential)) {
3533 this.log_('Admin auth credential detected. Reducing max reconnect time.');
3534 this.maxReconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
3535 }
3536 };
3537 PersistentConnection.prototype.refreshAppCheckToken = function (token) {
3538 this.appCheckToken_ = token;
3539 this.log_('App check token refreshed');
3540 if (this.appCheckToken_) {
3541 this.tryAppCheck();
3542 }
3543 else {
3544 //If we're connected we want to let the server know to unauthenticate us.
3545 //If we're not connected, simply delete the credential so we dont become
3546 // authenticated next time we connect.
3547 if (this.connected_) {
3548 this.sendRequest('unappeck', {}, function () { });
3549 }
3550 }
3551 };
3552 /**
3553 * Attempts to authenticate with the given credentials. If the authentication attempt fails, it's triggered like
3554 * a auth revoked (the connection is closed).
3555 */
3556 PersistentConnection.prototype.tryAuth = function () {
3557 var _this = this;
3558 if (this.connected_ && this.authToken_) {
3559 var token_1 = this.authToken_;
3560 var authMethod = util.isValidFormat(token_1) ? 'auth' : 'gauth';
3561 var requestData = { cred: token_1 };
3562 if (this.authOverride_ === null) {
3563 requestData['noauth'] = true;
3564 }
3565 else if (typeof this.authOverride_ === 'object') {
3566 requestData['authvar'] = this.authOverride_;
3567 }
3568 this.sendRequest(authMethod, requestData, function (res) {
3569 var status = res[ /*status*/'s'];
3570 var data = res[ /*data*/'d'] || 'error';
3571 if (_this.authToken_ === token_1) {
3572 if (status === 'ok') {
3573 _this.invalidAuthTokenCount_ = 0;
3574 }
3575 else {
3576 // Triggers reconnect and force refresh for auth token
3577 _this.onAuthRevoked_(status, data);
3578 }
3579 }
3580 });
3581 }
3582 };
3583 /**
3584 * Attempts to authenticate with the given token. If the authentication
3585 * attempt fails, it's triggered like the token was revoked (the connection is
3586 * closed).
3587 */
3588 PersistentConnection.prototype.tryAppCheck = function () {
3589 var _this = this;
3590 if (this.connected_ && this.appCheckToken_) {
3591 this.sendRequest('appcheck', { 'token': this.appCheckToken_ }, function (res) {
3592 var status = res[ /*status*/'s'];
3593 var data = res[ /*data*/'d'] || 'error';
3594 if (status === 'ok') {
3595 _this.invalidAppCheckTokenCount_ = 0;
3596 }
3597 else {
3598 _this.onAppCheckRevoked_(status, data);
3599 }
3600 });
3601 }
3602 };
3603 /**
3604 * @inheritDoc
3605 */
3606 PersistentConnection.prototype.unlisten = function (query, tag) {
3607 var pathString = query._path.toString();
3608 var queryId = query._queryIdentifier;
3609 this.log_('Unlisten called for ' + pathString + ' ' + queryId);
3610 util.assert(query._queryParams.isDefault() || !query._queryParams.loadsAllData(), 'unlisten() called for non-default but complete query');
3611 var listen = this.removeListen_(pathString, queryId);
3612 if (listen && this.connected_) {
3613 this.sendUnlisten_(pathString, queryId, query._queryObject, tag);
3614 }
3615 };
3616 PersistentConnection.prototype.sendUnlisten_ = function (pathString, queryId, queryObj, tag) {
3617 this.log_('Unlisten on ' + pathString + ' for ' + queryId);
3618 var req = { /*path*/ p: pathString };
3619 var action = 'n';
3620 // Only bother sending queryId if it's non-default.
3621 if (tag) {
3622 req['q'] = queryObj;
3623 req['t'] = tag;
3624 }
3625 this.sendRequest(action, req);
3626 };
3627 PersistentConnection.prototype.onDisconnectPut = function (pathString, data, onComplete) {
3628 this.initConnection_();
3629 if (this.connected_) {
3630 this.sendOnDisconnect_('o', pathString, data, onComplete);
3631 }
3632 else {
3633 this.onDisconnectRequestQueue_.push({
3634 pathString: pathString,
3635 action: 'o',
3636 data: data,
3637 onComplete: onComplete
3638 });
3639 }
3640 };
3641 PersistentConnection.prototype.onDisconnectMerge = function (pathString, data, onComplete) {
3642 this.initConnection_();
3643 if (this.connected_) {
3644 this.sendOnDisconnect_('om', pathString, data, onComplete);
3645 }
3646 else {
3647 this.onDisconnectRequestQueue_.push({
3648 pathString: pathString,
3649 action: 'om',
3650 data: data,
3651 onComplete: onComplete
3652 });
3653 }
3654 };
3655 PersistentConnection.prototype.onDisconnectCancel = function (pathString, onComplete) {
3656 this.initConnection_();
3657 if (this.connected_) {
3658 this.sendOnDisconnect_('oc', pathString, null, onComplete);
3659 }
3660 else {
3661 this.onDisconnectRequestQueue_.push({
3662 pathString: pathString,
3663 action: 'oc',
3664 data: null,
3665 onComplete: onComplete
3666 });
3667 }
3668 };
3669 PersistentConnection.prototype.sendOnDisconnect_ = function (action, pathString, data, onComplete) {
3670 var request = { /*path*/ p: pathString, /*data*/ d: data };
3671 this.log_('onDisconnect ' + action, request);
3672 this.sendRequest(action, request, function (response) {
3673 if (onComplete) {
3674 setTimeout(function () {
3675 onComplete(response[ /*status*/'s'], response[ /* data */'d']);
3676 }, Math.floor(0));
3677 }
3678 });
3679 };
3680 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
3681 this.putInternal('p', pathString, data, onComplete, hash);
3682 };
3683 PersistentConnection.prototype.merge = function (pathString, data, onComplete, hash) {
3684 this.putInternal('m', pathString, data, onComplete, hash);
3685 };
3686 PersistentConnection.prototype.putInternal = function (action, pathString, data, onComplete, hash) {
3687 this.initConnection_();
3688 var request = {
3689 /*path*/ p: pathString,
3690 /*data*/ d: data
3691 };
3692 if (hash !== undefined) {
3693 request[ /*hash*/'h'] = hash;
3694 }
3695 // TODO: Only keep track of the most recent put for a given path?
3696 this.outstandingPuts_.push({
3697 action: action,
3698 request: request,
3699 onComplete: onComplete
3700 });
3701 this.outstandingPutCount_++;
3702 var index = this.outstandingPuts_.length - 1;
3703 if (this.connected_) {
3704 this.sendPut_(index);
3705 }
3706 else {
3707 this.log_('Buffering put: ' + pathString);
3708 }
3709 };
3710 PersistentConnection.prototype.sendPut_ = function (index) {
3711 var _this = this;
3712 var action = this.outstandingPuts_[index].action;
3713 var request = this.outstandingPuts_[index].request;
3714 var onComplete = this.outstandingPuts_[index].onComplete;
3715 this.outstandingPuts_[index].queued = this.connected_;
3716 this.sendRequest(action, request, function (message) {
3717 _this.log_(action + ' response', message);
3718 delete _this.outstandingPuts_[index];
3719 _this.outstandingPutCount_--;
3720 // Clean up array occasionally.
3721 if (_this.outstandingPutCount_ === 0) {
3722 _this.outstandingPuts_ = [];
3723 }
3724 if (onComplete) {
3725 onComplete(message[ /*status*/'s'], message[ /* data */'d']);
3726 }
3727 });
3728 };
3729 PersistentConnection.prototype.reportStats = function (stats) {
3730 var _this = this;
3731 // If we're not connected, we just drop the stats.
3732 if (this.connected_) {
3733 var request = { /*counters*/ c: stats };
3734 this.log_('reportStats', request);
3735 this.sendRequest(/*stats*/ 's', request, function (result) {
3736 var status = result[ /*status*/'s'];
3737 if (status !== 'ok') {
3738 var errorReason = result[ /* data */'d'];
3739 _this.log_('reportStats', 'Error sending stats: ' + errorReason);
3740 }
3741 });
3742 }
3743 };
3744 PersistentConnection.prototype.onDataMessage_ = function (message) {
3745 if ('r' in message) {
3746 // this is a response
3747 this.log_('from server: ' + util.stringify(message));
3748 var reqNum = message['r'];
3749 var onResponse = this.requestCBHash_[reqNum];
3750 if (onResponse) {
3751 delete this.requestCBHash_[reqNum];
3752 onResponse(message[ /*body*/'b']);
3753 }
3754 }
3755 else if ('error' in message) {
3756 throw 'A server-side error has occurred: ' + message['error'];
3757 }
3758 else if ('a' in message) {
3759 // a and b are action and body, respectively
3760 this.onDataPush_(message['a'], message['b']);
3761 }
3762 };
3763 PersistentConnection.prototype.onDataPush_ = function (action, body) {
3764 this.log_('handleServerMessage', action, body);
3765 if (action === 'd') {
3766 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3767 /*isMerge*/ false, body['t']);
3768 }
3769 else if (action === 'm') {
3770 this.onDataUpdate_(body[ /*path*/'p'], body[ /*data*/'d'],
3771 /*isMerge=*/ true, body['t']);
3772 }
3773 else if (action === 'c') {
3774 this.onListenRevoked_(body[ /*path*/'p'], body[ /*query*/'q']);
3775 }
3776 else if (action === 'ac') {
3777 this.onAuthRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3778 }
3779 else if (action === 'apc') {
3780 this.onAppCheckRevoked_(body[ /*status code*/'s'], body[ /* explanation */'d']);
3781 }
3782 else if (action === 'sd') {
3783 this.onSecurityDebugPacket_(body);
3784 }
3785 else {
3786 error('Unrecognized action received from server: ' +
3787 util.stringify(action) +
3788 '\nAre you using the latest client?');
3789 }
3790 };
3791 PersistentConnection.prototype.onReady_ = function (timestamp, sessionId) {
3792 this.log_('connection ready');
3793 this.connected_ = true;
3794 this.lastConnectionEstablishedTime_ = new Date().getTime();
3795 this.handleTimestamp_(timestamp);
3796 this.lastSessionId = sessionId;
3797 if (this.firstConnection_) {
3798 this.sendConnectStats_();
3799 }
3800 this.restoreState_();
3801 this.firstConnection_ = false;
3802 this.onConnectStatus_(true);
3803 };
3804 PersistentConnection.prototype.scheduleConnect_ = function (timeout) {
3805 var _this = this;
3806 util.assert(!this.realtime_, "Scheduling a connect when we're already connected/ing?");
3807 if (this.establishConnectionTimer_) {
3808 clearTimeout(this.establishConnectionTimer_);
3809 }
3810 // NOTE: Even when timeout is 0, it's important to do a setTimeout to work around an infuriating "Security Error" in
3811 // Firefox when trying to write to our long-polling iframe in some scenarios (e.g. Forge or our unit tests).
3812 this.establishConnectionTimer_ = setTimeout(function () {
3813 _this.establishConnectionTimer_ = null;
3814 _this.establishConnection_();
3815 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3816 }, Math.floor(timeout));
3817 };
3818 PersistentConnection.prototype.initConnection_ = function () {
3819 if (!this.realtime_ && this.firstConnection_) {
3820 this.scheduleConnect_(0);
3821 }
3822 };
3823 PersistentConnection.prototype.onVisible_ = function (visible) {
3824 // NOTE: Tabbing away and back to a window will defeat our reconnect backoff, but I think that's fine.
3825 if (visible &&
3826 !this.visible_ &&
3827 this.reconnectDelay_ === this.maxReconnectDelay_) {
3828 this.log_('Window became visible. Reducing delay.');
3829 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3830 if (!this.realtime_) {
3831 this.scheduleConnect_(0);
3832 }
3833 }
3834 this.visible_ = visible;
3835 };
3836 PersistentConnection.prototype.onOnline_ = function (online) {
3837 if (online) {
3838 this.log_('Browser went online.');
3839 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3840 if (!this.realtime_) {
3841 this.scheduleConnect_(0);
3842 }
3843 }
3844 else {
3845 this.log_('Browser went offline. Killing connection.');
3846 if (this.realtime_) {
3847 this.realtime_.close();
3848 }
3849 }
3850 };
3851 PersistentConnection.prototype.onRealtimeDisconnect_ = function () {
3852 this.log_('data client disconnected');
3853 this.connected_ = false;
3854 this.realtime_ = null;
3855 // Since we don't know if our sent transactions succeeded or not, we need to cancel them.
3856 this.cancelSentTransactions_();
3857 // Clear out the pending requests.
3858 this.requestCBHash_ = {};
3859 if (this.shouldReconnect_()) {
3860 if (!this.visible_) {
3861 this.log_("Window isn't visible. Delaying reconnect.");
3862 this.reconnectDelay_ = this.maxReconnectDelay_;
3863 this.lastConnectionAttemptTime_ = new Date().getTime();
3864 }
3865 else if (this.lastConnectionEstablishedTime_) {
3866 // If we've been connected long enough, reset reconnect delay to minimum.
3867 var timeSinceLastConnectSucceeded = new Date().getTime() - this.lastConnectionEstablishedTime_;
3868 if (timeSinceLastConnectSucceeded > RECONNECT_DELAY_RESET_TIMEOUT) {
3869 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3870 }
3871 this.lastConnectionEstablishedTime_ = null;
3872 }
3873 var timeSinceLastConnectAttempt = new Date().getTime() - this.lastConnectionAttemptTime_;
3874 var reconnectDelay = Math.max(0, this.reconnectDelay_ - timeSinceLastConnectAttempt);
3875 reconnectDelay = Math.random() * reconnectDelay;
3876 this.log_('Trying to reconnect in ' + reconnectDelay + 'ms');
3877 this.scheduleConnect_(reconnectDelay);
3878 // Adjust reconnect delay for next time.
3879 this.reconnectDelay_ = Math.min(this.maxReconnectDelay_, this.reconnectDelay_ * RECONNECT_DELAY_MULTIPLIER);
3880 }
3881 this.onConnectStatus_(false);
3882 };
3883 PersistentConnection.prototype.establishConnection_ = function () {
3884 return tslib.__awaiter(this, void 0, void 0, function () {
3885 var onDataMessage, onReady, onDisconnect_1, connId, lastSessionId, canceled_1, connection_1, closeFn, sendRequestFn, forceRefresh, _a, authToken, appCheckToken, error_1;
3886 var _this = this;
3887 return tslib.__generator(this, function (_b) {
3888 switch (_b.label) {
3889 case 0:
3890 if (!this.shouldReconnect_()) return [3 /*break*/, 4];
3891 this.log_('Making a connection attempt');
3892 this.lastConnectionAttemptTime_ = new Date().getTime();
3893 this.lastConnectionEstablishedTime_ = null;
3894 onDataMessage = this.onDataMessage_.bind(this);
3895 onReady = this.onReady_.bind(this);
3896 onDisconnect_1 = this.onRealtimeDisconnect_.bind(this);
3897 connId = this.id + ':' + PersistentConnection.nextConnectionId_++;
3898 lastSessionId = this.lastSessionId;
3899 canceled_1 = false;
3900 connection_1 = null;
3901 closeFn = function () {
3902 if (connection_1) {
3903 connection_1.close();
3904 }
3905 else {
3906 canceled_1 = true;
3907 onDisconnect_1();
3908 }
3909 };
3910 sendRequestFn = function (msg) {
3911 util.assert(connection_1, "sendRequest call when we're not connected not allowed.");
3912 connection_1.sendRequest(msg);
3913 };
3914 this.realtime_ = {
3915 close: closeFn,
3916 sendRequest: sendRequestFn
3917 };
3918 forceRefresh = this.forceTokenRefresh_;
3919 this.forceTokenRefresh_ = false;
3920 _b.label = 1;
3921 case 1:
3922 _b.trys.push([1, 3, , 4]);
3923 return [4 /*yield*/, Promise.all([
3924 this.authTokenProvider_.getToken(forceRefresh),
3925 this.appCheckTokenProvider_.getToken(forceRefresh)
3926 ])];
3927 case 2:
3928 _a = tslib.__read.apply(void 0, [_b.sent(), 2]), authToken = _a[0], appCheckToken = _a[1];
3929 if (!canceled_1) {
3930 log('getToken() completed. Creating connection.');
3931 this.authToken_ = authToken && authToken.accessToken;
3932 this.appCheckToken_ = appCheckToken && appCheckToken.token;
3933 connection_1 = new Connection(connId, this.repoInfo_, this.applicationId_, this.appCheckToken_, this.authToken_, onDataMessage, onReady, onDisconnect_1,
3934 /* onKill= */ function (reason) {
3935 warn(reason + ' (' + _this.repoInfo_.toString() + ')');
3936 _this.interrupt(SERVER_KILL_INTERRUPT_REASON);
3937 }, lastSessionId);
3938 }
3939 else {
3940 log('getToken() completed but was canceled');
3941 }
3942 return [3 /*break*/, 4];
3943 case 3:
3944 error_1 = _b.sent();
3945 this.log_('Failed to get token: ' + error_1);
3946 if (!canceled_1) {
3947 if (this.repoInfo_.nodeAdmin) {
3948 // This may be a critical error for the Admin Node.js SDK, so log a warning.
3949 // But getToken() may also just have temporarily failed, so we still want to
3950 // continue retrying.
3951 warn(error_1);
3952 }
3953 closeFn();
3954 }
3955 return [3 /*break*/, 4];
3956 case 4: return [2 /*return*/];
3957 }
3958 });
3959 });
3960 };
3961 PersistentConnection.prototype.interrupt = function (reason) {
3962 log('Interrupting connection for reason: ' + reason);
3963 this.interruptReasons_[reason] = true;
3964 if (this.realtime_) {
3965 this.realtime_.close();
3966 }
3967 else {
3968 if (this.establishConnectionTimer_) {
3969 clearTimeout(this.establishConnectionTimer_);
3970 this.establishConnectionTimer_ = null;
3971 }
3972 if (this.connected_) {
3973 this.onRealtimeDisconnect_();
3974 }
3975 }
3976 };
3977 PersistentConnection.prototype.resume = function (reason) {
3978 log('Resuming connection for reason: ' + reason);
3979 delete this.interruptReasons_[reason];
3980 if (util.isEmpty(this.interruptReasons_)) {
3981 this.reconnectDelay_ = RECONNECT_MIN_DELAY;
3982 if (!this.realtime_) {
3983 this.scheduleConnect_(0);
3984 }
3985 }
3986 };
3987 PersistentConnection.prototype.handleTimestamp_ = function (timestamp) {
3988 var delta = timestamp - new Date().getTime();
3989 this.onServerInfoUpdate_({ serverTimeOffset: delta });
3990 };
3991 PersistentConnection.prototype.cancelSentTransactions_ = function () {
3992 for (var i = 0; i < this.outstandingPuts_.length; i++) {
3993 var put = this.outstandingPuts_[i];
3994 if (put && /*hash*/ 'h' in put.request && put.queued) {
3995 if (put.onComplete) {
3996 put.onComplete('disconnect');
3997 }
3998 delete this.outstandingPuts_[i];
3999 this.outstandingPutCount_--;
4000 }
4001 }
4002 // Clean up array occasionally.
4003 if (this.outstandingPutCount_ === 0) {
4004 this.outstandingPuts_ = [];
4005 }
4006 };
4007 PersistentConnection.prototype.onListenRevoked_ = function (pathString, query) {
4008 // Remove the listen and manufacture a "permission_denied" error for the failed listen.
4009 var queryId;
4010 if (!query) {
4011 queryId = 'default';
4012 }
4013 else {
4014 queryId = query.map(function (q) { return ObjectToUniqueKey(q); }).join('$');
4015 }
4016 var listen = this.removeListen_(pathString, queryId);
4017 if (listen && listen.onComplete) {
4018 listen.onComplete('permission_denied');
4019 }
4020 };
4021 PersistentConnection.prototype.removeListen_ = function (pathString, queryId) {
4022 var normalizedPathString = new Path(pathString).toString(); // normalize path.
4023 var listen;
4024 if (this.listens.has(normalizedPathString)) {
4025 var map = this.listens.get(normalizedPathString);
4026 listen = map.get(queryId);
4027 map.delete(queryId);
4028 if (map.size === 0) {
4029 this.listens.delete(normalizedPathString);
4030 }
4031 }
4032 else {
4033 // all listens for this path has already been removed
4034 listen = undefined;
4035 }
4036 return listen;
4037 };
4038 PersistentConnection.prototype.onAuthRevoked_ = function (statusCode, explanation) {
4039 log('Auth token revoked: ' + statusCode + '/' + explanation);
4040 this.authToken_ = null;
4041 this.forceTokenRefresh_ = true;
4042 this.realtime_.close();
4043 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4044 // We'll wait a couple times before logging the warning / increasing the
4045 // retry period since oauth tokens will report as "invalid" if they're
4046 // just expired. Plus there may be transient issues that resolve themselves.
4047 this.invalidAuthTokenCount_++;
4048 if (this.invalidAuthTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4049 // Set a long reconnect delay because recovery is unlikely
4050 this.reconnectDelay_ = RECONNECT_MAX_DELAY_FOR_ADMINS;
4051 // Notify the auth token provider that the token is invalid, which will log
4052 // a warning
4053 this.authTokenProvider_.notifyForInvalidToken();
4054 }
4055 }
4056 };
4057 PersistentConnection.prototype.onAppCheckRevoked_ = function (statusCode, explanation) {
4058 log('App check token revoked: ' + statusCode + '/' + explanation);
4059 this.appCheckToken_ = null;
4060 this.forceTokenRefresh_ = true;
4061 // Note: We don't close the connection as the developer may not have
4062 // enforcement enabled. The backend closes connections with enforcements.
4063 if (statusCode === 'invalid_token' || statusCode === 'permission_denied') {
4064 // We'll wait a couple times before logging the warning / increasing the
4065 // retry period since oauth tokens will report as "invalid" if they're
4066 // just expired. Plus there may be transient issues that resolve themselves.
4067 this.invalidAppCheckTokenCount_++;
4068 if (this.invalidAppCheckTokenCount_ >= INVALID_TOKEN_THRESHOLD) {
4069 this.appCheckTokenProvider_.notifyForInvalidToken();
4070 }
4071 }
4072 };
4073 PersistentConnection.prototype.onSecurityDebugPacket_ = function (body) {
4074 if (this.securityDebugCallback_) {
4075 this.securityDebugCallback_(body);
4076 }
4077 else {
4078 if ('msg' in body) {
4079 console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
4080 }
4081 }
4082 };
4083 PersistentConnection.prototype.restoreState_ = function () {
4084 var e_1, _a, e_2, _b;
4085 //Re-authenticate ourselves if we have a credential stored.
4086 this.tryAuth();
4087 this.tryAppCheck();
4088 try {
4089 // Puts depend on having received the corresponding data update from the server before they complete, so we must
4090 // make sure to send listens before puts.
4091 for (var _c = tslib.__values(this.listens.values()), _d = _c.next(); !_d.done; _d = _c.next()) {
4092 var queries = _d.value;
4093 try {
4094 for (var _e = (e_2 = void 0, tslib.__values(queries.values())), _f = _e.next(); !_f.done; _f = _e.next()) {
4095 var listenSpec = _f.value;
4096 this.sendListen_(listenSpec);
4097 }
4098 }
4099 catch (e_2_1) { e_2 = { error: e_2_1 }; }
4100 finally {
4101 try {
4102 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
4103 }
4104 finally { if (e_2) throw e_2.error; }
4105 }
4106 }
4107 }
4108 catch (e_1_1) { e_1 = { error: e_1_1 }; }
4109 finally {
4110 try {
4111 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
4112 }
4113 finally { if (e_1) throw e_1.error; }
4114 }
4115 for (var i = 0; i < this.outstandingPuts_.length; i++) {
4116 if (this.outstandingPuts_[i]) {
4117 this.sendPut_(i);
4118 }
4119 }
4120 while (this.onDisconnectRequestQueue_.length) {
4121 var request = this.onDisconnectRequestQueue_.shift();
4122 this.sendOnDisconnect_(request.action, request.pathString, request.data, request.onComplete);
4123 }
4124 for (var i = 0; i < this.outstandingGets_.length; i++) {
4125 if (this.outstandingGets_[i]) {
4126 this.sendGet_(i);
4127 }
4128 }
4129 };
4130 /**
4131 * Sends client stats for first connection
4132 */
4133 PersistentConnection.prototype.sendConnectStats_ = function () {
4134 var stats = {};
4135 var clientName = 'js';
4136 if (util.isNodeSdk()) {
4137 if (this.repoInfo_.nodeAdmin) {
4138 clientName = 'admin_node';
4139 }
4140 else {
4141 clientName = 'node';
4142 }
4143 }
4144 stats['sdk.' + clientName + '.' + SDK_VERSION.replace(/\./g, '-')] = 1;
4145 if (util.isMobileCordova()) {
4146 stats['framework.cordova'] = 1;
4147 }
4148 else if (util.isReactNative()) {
4149 stats['framework.reactnative'] = 1;
4150 }
4151 this.reportStats(stats);
4152 };
4153 PersistentConnection.prototype.shouldReconnect_ = function () {
4154 var online = OnlineMonitor.getInstance().currentlyOnline();
4155 return util.isEmpty(this.interruptReasons_) && online;
4156 };
4157 PersistentConnection.nextPersistentConnectionId_ = 0;
4158 /**
4159 * Counter for number of connections created. Mainly used for tagging in the logs
4160 */
4161 PersistentConnection.nextConnectionId_ = 0;
4162 return PersistentConnection;
4163}(ServerActions));
4164
4165/**
4166 * @license
4167 * Copyright 2017 Google LLC
4168 *
4169 * Licensed under the Apache License, Version 2.0 (the "License");
4170 * you may not use this file except in compliance with the License.
4171 * You may obtain a copy of the License at
4172 *
4173 * http://www.apache.org/licenses/LICENSE-2.0
4174 *
4175 * Unless required by applicable law or agreed to in writing, software
4176 * distributed under the License is distributed on an "AS IS" BASIS,
4177 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4178 * See the License for the specific language governing permissions and
4179 * limitations under the License.
4180 */
4181var NamedNode = /** @class */ (function () {
4182 function NamedNode(name, node) {
4183 this.name = name;
4184 this.node = node;
4185 }
4186 NamedNode.Wrap = function (name, node) {
4187 return new NamedNode(name, node);
4188 };
4189 return NamedNode;
4190}());
4191
4192/**
4193 * @license
4194 * Copyright 2017 Google LLC
4195 *
4196 * Licensed under the Apache License, Version 2.0 (the "License");
4197 * you may not use this file except in compliance with the License.
4198 * You may obtain a copy of the License at
4199 *
4200 * http://www.apache.org/licenses/LICENSE-2.0
4201 *
4202 * Unless required by applicable law or agreed to in writing, software
4203 * distributed under the License is distributed on an "AS IS" BASIS,
4204 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4205 * See the License for the specific language governing permissions and
4206 * limitations under the License.
4207 */
4208var Index = /** @class */ (function () {
4209 function Index() {
4210 }
4211 /**
4212 * @returns A standalone comparison function for
4213 * this index
4214 */
4215 Index.prototype.getCompare = function () {
4216 return this.compare.bind(this);
4217 };
4218 /**
4219 * Given a before and after value for a node, determine if the indexed value has changed. Even if they are different,
4220 * it's possible that the changes are isolated to parts of the snapshot that are not indexed.
4221 *
4222 *
4223 * @returns True if the portion of the snapshot being indexed changed between oldNode and newNode
4224 */
4225 Index.prototype.indexedValueChanged = function (oldNode, newNode) {
4226 var oldWrapped = new NamedNode(MIN_NAME, oldNode);
4227 var newWrapped = new NamedNode(MIN_NAME, newNode);
4228 return this.compare(oldWrapped, newWrapped) !== 0;
4229 };
4230 /**
4231 * @returns a node wrapper that will sort equal to or less than
4232 * any other node wrapper, using this index
4233 */
4234 Index.prototype.minPost = function () {
4235 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4236 return NamedNode.MIN;
4237 };
4238 return Index;
4239}());
4240
4241/**
4242 * @license
4243 * Copyright 2017 Google LLC
4244 *
4245 * Licensed under the Apache License, Version 2.0 (the "License");
4246 * you may not use this file except in compliance with the License.
4247 * You may obtain a copy of the License at
4248 *
4249 * http://www.apache.org/licenses/LICENSE-2.0
4250 *
4251 * Unless required by applicable law or agreed to in writing, software
4252 * distributed under the License is distributed on an "AS IS" BASIS,
4253 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4254 * See the License for the specific language governing permissions and
4255 * limitations under the License.
4256 */
4257var __EMPTY_NODE;
4258var KeyIndex = /** @class */ (function (_super) {
4259 tslib.__extends(KeyIndex, _super);
4260 function KeyIndex() {
4261 return _super !== null && _super.apply(this, arguments) || this;
4262 }
4263 Object.defineProperty(KeyIndex, "__EMPTY_NODE", {
4264 get: function () {
4265 return __EMPTY_NODE;
4266 },
4267 set: function (val) {
4268 __EMPTY_NODE = val;
4269 },
4270 enumerable: false,
4271 configurable: true
4272 });
4273 KeyIndex.prototype.compare = function (a, b) {
4274 return nameCompare(a.name, b.name);
4275 };
4276 KeyIndex.prototype.isDefinedOn = function (node) {
4277 // We could probably return true here (since every node has a key), but it's never called
4278 // so just leaving unimplemented for now.
4279 throw util.assertionError('KeyIndex.isDefinedOn not expected to be called.');
4280 };
4281 KeyIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
4282 return false; // The key for a node never changes.
4283 };
4284 KeyIndex.prototype.minPost = function () {
4285 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4286 return NamedNode.MIN;
4287 };
4288 KeyIndex.prototype.maxPost = function () {
4289 // TODO: This should really be created once and cached in a static property, but
4290 // NamedNode isn't defined yet, so I can't use it in a static. Bleh.
4291 return new NamedNode(MAX_NAME, __EMPTY_NODE);
4292 };
4293 KeyIndex.prototype.makePost = function (indexValue, name) {
4294 util.assert(typeof indexValue === 'string', 'KeyIndex indexValue must always be a string.');
4295 // We just use empty node, but it'll never be compared, since our comparator only looks at name.
4296 return new NamedNode(indexValue, __EMPTY_NODE);
4297 };
4298 /**
4299 * @returns String representation for inclusion in a query spec
4300 */
4301 KeyIndex.prototype.toString = function () {
4302 return '.key';
4303 };
4304 return KeyIndex;
4305}(Index));
4306var KEY_INDEX = new KeyIndex();
4307
4308/**
4309 * @license
4310 * Copyright 2017 Google LLC
4311 *
4312 * Licensed under the Apache License, Version 2.0 (the "License");
4313 * you may not use this file except in compliance with the License.
4314 * You may obtain a copy of the License at
4315 *
4316 * http://www.apache.org/licenses/LICENSE-2.0
4317 *
4318 * Unless required by applicable law or agreed to in writing, software
4319 * distributed under the License is distributed on an "AS IS" BASIS,
4320 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4321 * See the License for the specific language governing permissions and
4322 * limitations under the License.
4323 */
4324/**
4325 * An iterator over an LLRBNode.
4326 */
4327var SortedMapIterator = /** @class */ (function () {
4328 /**
4329 * @param node - Node to iterate.
4330 * @param isReverse_ - Whether or not to iterate in reverse
4331 */
4332 function SortedMapIterator(node, startKey, comparator, isReverse_, resultGenerator_) {
4333 if (resultGenerator_ === void 0) { resultGenerator_ = null; }
4334 this.isReverse_ = isReverse_;
4335 this.resultGenerator_ = resultGenerator_;
4336 this.nodeStack_ = [];
4337 var cmp = 1;
4338 while (!node.isEmpty()) {
4339 node = node;
4340 cmp = startKey ? comparator(node.key, startKey) : 1;
4341 // flip the comparison if we're going in reverse
4342 if (isReverse_) {
4343 cmp *= -1;
4344 }
4345 if (cmp < 0) {
4346 // This node is less than our start key. ignore it
4347 if (this.isReverse_) {
4348 node = node.left;
4349 }
4350 else {
4351 node = node.right;
4352 }
4353 }
4354 else if (cmp === 0) {
4355 // This node is exactly equal to our start key. Push it on the stack, but stop iterating;
4356 this.nodeStack_.push(node);
4357 break;
4358 }
4359 else {
4360 // This node is greater than our start key, add it to the stack and move to the next one
4361 this.nodeStack_.push(node);
4362 if (this.isReverse_) {
4363 node = node.right;
4364 }
4365 else {
4366 node = node.left;
4367 }
4368 }
4369 }
4370 }
4371 SortedMapIterator.prototype.getNext = function () {
4372 if (this.nodeStack_.length === 0) {
4373 return null;
4374 }
4375 var node = this.nodeStack_.pop();
4376 var result;
4377 if (this.resultGenerator_) {
4378 result = this.resultGenerator_(node.key, node.value);
4379 }
4380 else {
4381 result = { key: node.key, value: node.value };
4382 }
4383 if (this.isReverse_) {
4384 node = node.left;
4385 while (!node.isEmpty()) {
4386 this.nodeStack_.push(node);
4387 node = node.right;
4388 }
4389 }
4390 else {
4391 node = node.right;
4392 while (!node.isEmpty()) {
4393 this.nodeStack_.push(node);
4394 node = node.left;
4395 }
4396 }
4397 return result;
4398 };
4399 SortedMapIterator.prototype.hasNext = function () {
4400 return this.nodeStack_.length > 0;
4401 };
4402 SortedMapIterator.prototype.peek = function () {
4403 if (this.nodeStack_.length === 0) {
4404 return null;
4405 }
4406 var node = this.nodeStack_[this.nodeStack_.length - 1];
4407 if (this.resultGenerator_) {
4408 return this.resultGenerator_(node.key, node.value);
4409 }
4410 else {
4411 return { key: node.key, value: node.value };
4412 }
4413 };
4414 return SortedMapIterator;
4415}());
4416/**
4417 * Represents a node in a Left-leaning Red-Black tree.
4418 */
4419var LLRBNode = /** @class */ (function () {
4420 /**
4421 * @param key - Key associated with this node.
4422 * @param value - Value associated with this node.
4423 * @param color - Whether this node is red.
4424 * @param left - Left child.
4425 * @param right - Right child.
4426 */
4427 function LLRBNode(key, value, color, left, right) {
4428 this.key = key;
4429 this.value = value;
4430 this.color = color != null ? color : LLRBNode.RED;
4431 this.left =
4432 left != null ? left : SortedMap.EMPTY_NODE;
4433 this.right =
4434 right != null ? right : SortedMap.EMPTY_NODE;
4435 }
4436 /**
4437 * Returns a copy of the current node, optionally replacing pieces of it.
4438 *
4439 * @param key - New key for the node, or null.
4440 * @param value - New value for the node, or null.
4441 * @param color - New color for the node, or null.
4442 * @param left - New left child for the node, or null.
4443 * @param right - New right child for the node, or null.
4444 * @returns The node copy.
4445 */
4446 LLRBNode.prototype.copy = function (key, value, color, left, right) {
4447 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);
4448 };
4449 /**
4450 * @returns The total number of nodes in the tree.
4451 */
4452 LLRBNode.prototype.count = function () {
4453 return this.left.count() + 1 + this.right.count();
4454 };
4455 /**
4456 * @returns True if the tree is empty.
4457 */
4458 LLRBNode.prototype.isEmpty = function () {
4459 return false;
4460 };
4461 /**
4462 * Traverses the tree in key order and calls the specified action function
4463 * for each node.
4464 *
4465 * @param action - Callback function to be called for each
4466 * node. If it returns true, traversal is aborted.
4467 * @returns The first truthy value returned by action, or the last falsey
4468 * value returned by action
4469 */
4470 LLRBNode.prototype.inorderTraversal = function (action) {
4471 return (this.left.inorderTraversal(action) ||
4472 !!action(this.key, this.value) ||
4473 this.right.inorderTraversal(action));
4474 };
4475 /**
4476 * Traverses the tree in reverse key order and calls the specified action function
4477 * for each node.
4478 *
4479 * @param action - Callback function to be called for each
4480 * node. If it returns true, traversal is aborted.
4481 * @returns True if traversal was aborted.
4482 */
4483 LLRBNode.prototype.reverseTraversal = function (action) {
4484 return (this.right.reverseTraversal(action) ||
4485 action(this.key, this.value) ||
4486 this.left.reverseTraversal(action));
4487 };
4488 /**
4489 * @returns The minimum node in the tree.
4490 */
4491 LLRBNode.prototype.min_ = function () {
4492 if (this.left.isEmpty()) {
4493 return this;
4494 }
4495 else {
4496 return this.left.min_();
4497 }
4498 };
4499 /**
4500 * @returns The maximum key in the tree.
4501 */
4502 LLRBNode.prototype.minKey = function () {
4503 return this.min_().key;
4504 };
4505 /**
4506 * @returns The maximum key in the tree.
4507 */
4508 LLRBNode.prototype.maxKey = function () {
4509 if (this.right.isEmpty()) {
4510 return this.key;
4511 }
4512 else {
4513 return this.right.maxKey();
4514 }
4515 };
4516 /**
4517 * @param key - Key to insert.
4518 * @param value - Value to insert.
4519 * @param comparator - Comparator.
4520 * @returns New tree, with the key/value added.
4521 */
4522 LLRBNode.prototype.insert = function (key, value, comparator) {
4523 var n = this;
4524 var cmp = comparator(key, n.key);
4525 if (cmp < 0) {
4526 n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
4527 }
4528 else if (cmp === 0) {
4529 n = n.copy(null, value, null, null, null);
4530 }
4531 else {
4532 n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
4533 }
4534 return n.fixUp_();
4535 };
4536 /**
4537 * @returns New tree, with the minimum key removed.
4538 */
4539 LLRBNode.prototype.removeMin_ = function () {
4540 if (this.left.isEmpty()) {
4541 return SortedMap.EMPTY_NODE;
4542 }
4543 var n = this;
4544 if (!n.left.isRed_() && !n.left.left.isRed_()) {
4545 n = n.moveRedLeft_();
4546 }
4547 n = n.copy(null, null, null, n.left.removeMin_(), null);
4548 return n.fixUp_();
4549 };
4550 /**
4551 * @param key - The key of the item to remove.
4552 * @param comparator - Comparator.
4553 * @returns New tree, with the specified item removed.
4554 */
4555 LLRBNode.prototype.remove = function (key, comparator) {
4556 var n, smallest;
4557 n = this;
4558 if (comparator(key, n.key) < 0) {
4559 if (!n.left.isEmpty() && !n.left.isRed_() && !n.left.left.isRed_()) {
4560 n = n.moveRedLeft_();
4561 }
4562 n = n.copy(null, null, null, n.left.remove(key, comparator), null);
4563 }
4564 else {
4565 if (n.left.isRed_()) {
4566 n = n.rotateRight_();
4567 }
4568 if (!n.right.isEmpty() && !n.right.isRed_() && !n.right.left.isRed_()) {
4569 n = n.moveRedRight_();
4570 }
4571 if (comparator(key, n.key) === 0) {
4572 if (n.right.isEmpty()) {
4573 return SortedMap.EMPTY_NODE;
4574 }
4575 else {
4576 smallest = n.right.min_();
4577 n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin_());
4578 }
4579 }
4580 n = n.copy(null, null, null, null, n.right.remove(key, comparator));
4581 }
4582 return n.fixUp_();
4583 };
4584 /**
4585 * @returns Whether this is a RED node.
4586 */
4587 LLRBNode.prototype.isRed_ = function () {
4588 return this.color;
4589 };
4590 /**
4591 * @returns New tree after performing any needed rotations.
4592 */
4593 LLRBNode.prototype.fixUp_ = function () {
4594 var n = this;
4595 if (n.right.isRed_() && !n.left.isRed_()) {
4596 n = n.rotateLeft_();
4597 }
4598 if (n.left.isRed_() && n.left.left.isRed_()) {
4599 n = n.rotateRight_();
4600 }
4601 if (n.left.isRed_() && n.right.isRed_()) {
4602 n = n.colorFlip_();
4603 }
4604 return n;
4605 };
4606 /**
4607 * @returns New tree, after moveRedLeft.
4608 */
4609 LLRBNode.prototype.moveRedLeft_ = function () {
4610 var n = this.colorFlip_();
4611 if (n.right.left.isRed_()) {
4612 n = n.copy(null, null, null, null, n.right.rotateRight_());
4613 n = n.rotateLeft_();
4614 n = n.colorFlip_();
4615 }
4616 return n;
4617 };
4618 /**
4619 * @returns New tree, after moveRedRight.
4620 */
4621 LLRBNode.prototype.moveRedRight_ = function () {
4622 var n = this.colorFlip_();
4623 if (n.left.left.isRed_()) {
4624 n = n.rotateRight_();
4625 n = n.colorFlip_();
4626 }
4627 return n;
4628 };
4629 /**
4630 * @returns New tree, after rotateLeft.
4631 */
4632 LLRBNode.prototype.rotateLeft_ = function () {
4633 var nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
4634 return this.right.copy(null, null, this.color, nl, null);
4635 };
4636 /**
4637 * @returns New tree, after rotateRight.
4638 */
4639 LLRBNode.prototype.rotateRight_ = function () {
4640 var nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
4641 return this.left.copy(null, null, this.color, null, nr);
4642 };
4643 /**
4644 * @returns Newt ree, after colorFlip.
4645 */
4646 LLRBNode.prototype.colorFlip_ = function () {
4647 var left = this.left.copy(null, null, !this.left.color, null, null);
4648 var right = this.right.copy(null, null, !this.right.color, null, null);
4649 return this.copy(null, null, !this.color, left, right);
4650 };
4651 /**
4652 * For testing.
4653 *
4654 * @returns True if all is well.
4655 */
4656 LLRBNode.prototype.checkMaxDepth_ = function () {
4657 var blackDepth = this.check_();
4658 return Math.pow(2.0, blackDepth) <= this.count() + 1;
4659 };
4660 LLRBNode.prototype.check_ = function () {
4661 if (this.isRed_() && this.left.isRed_()) {
4662 throw new Error('Red node has red child(' + this.key + ',' + this.value + ')');
4663 }
4664 if (this.right.isRed_()) {
4665 throw new Error('Right child of (' + this.key + ',' + this.value + ') is red');
4666 }
4667 var blackDepth = this.left.check_();
4668 if (blackDepth !== this.right.check_()) {
4669 throw new Error('Black depths differ');
4670 }
4671 else {
4672 return blackDepth + (this.isRed_() ? 0 : 1);
4673 }
4674 };
4675 LLRBNode.RED = true;
4676 LLRBNode.BLACK = false;
4677 return LLRBNode;
4678}());
4679/**
4680 * Represents an empty node (a leaf node in the Red-Black Tree).
4681 */
4682var LLRBEmptyNode = /** @class */ (function () {
4683 function LLRBEmptyNode() {
4684 }
4685 /**
4686 * Returns a copy of the current node.
4687 *
4688 * @returns The node copy.
4689 */
4690 LLRBEmptyNode.prototype.copy = function (key, value, color, left, right) {
4691 return this;
4692 };
4693 /**
4694 * Returns a copy of the tree, with the specified key/value added.
4695 *
4696 * @param key - Key to be added.
4697 * @param value - Value to be added.
4698 * @param comparator - Comparator.
4699 * @returns New tree, with item added.
4700 */
4701 LLRBEmptyNode.prototype.insert = function (key, value, comparator) {
4702 return new LLRBNode(key, value, null);
4703 };
4704 /**
4705 * Returns a copy of the tree, with the specified key removed.
4706 *
4707 * @param key - The key to remove.
4708 * @param comparator - Comparator.
4709 * @returns New tree, with item removed.
4710 */
4711 LLRBEmptyNode.prototype.remove = function (key, comparator) {
4712 return this;
4713 };
4714 /**
4715 * @returns The total number of nodes in the tree.
4716 */
4717 LLRBEmptyNode.prototype.count = function () {
4718 return 0;
4719 };
4720 /**
4721 * @returns True if the tree is empty.
4722 */
4723 LLRBEmptyNode.prototype.isEmpty = function () {
4724 return true;
4725 };
4726 /**
4727 * Traverses the tree in key order and calls the specified action function
4728 * for each node.
4729 *
4730 * @param action - Callback function to be called for each
4731 * node. If it returns true, traversal is aborted.
4732 * @returns True if traversal was aborted.
4733 */
4734 LLRBEmptyNode.prototype.inorderTraversal = function (action) {
4735 return false;
4736 };
4737 /**
4738 * Traverses the tree in reverse key order and calls the specified action function
4739 * for each node.
4740 *
4741 * @param action - Callback function to be called for each
4742 * node. If it returns true, traversal is aborted.
4743 * @returns True if traversal was aborted.
4744 */
4745 LLRBEmptyNode.prototype.reverseTraversal = function (action) {
4746 return false;
4747 };
4748 LLRBEmptyNode.prototype.minKey = function () {
4749 return null;
4750 };
4751 LLRBEmptyNode.prototype.maxKey = function () {
4752 return null;
4753 };
4754 LLRBEmptyNode.prototype.check_ = function () {
4755 return 0;
4756 };
4757 /**
4758 * @returns Whether this node is red.
4759 */
4760 LLRBEmptyNode.prototype.isRed_ = function () {
4761 return false;
4762 };
4763 return LLRBEmptyNode;
4764}());
4765/**
4766 * An immutable sorted map implementation, based on a Left-leaning Red-Black
4767 * tree.
4768 */
4769var SortedMap = /** @class */ (function () {
4770 /**
4771 * @param comparator_ - Key comparator.
4772 * @param root_ - Optional root node for the map.
4773 */
4774 function SortedMap(comparator_, root_) {
4775 if (root_ === void 0) { root_ = SortedMap.EMPTY_NODE; }
4776 this.comparator_ = comparator_;
4777 this.root_ = root_;
4778 }
4779 /**
4780 * Returns a copy of the map, with the specified key/value added or replaced.
4781 * (TODO: We should perhaps rename this method to 'put')
4782 *
4783 * @param key - Key to be added.
4784 * @param value - Value to be added.
4785 * @returns New map, with item added.
4786 */
4787 SortedMap.prototype.insert = function (key, value) {
4788 return new SortedMap(this.comparator_, this.root_
4789 .insert(key, value, this.comparator_)
4790 .copy(null, null, LLRBNode.BLACK, null, null));
4791 };
4792 /**
4793 * Returns a copy of the map, with the specified key removed.
4794 *
4795 * @param key - The key to remove.
4796 * @returns New map, with item removed.
4797 */
4798 SortedMap.prototype.remove = function (key) {
4799 return new SortedMap(this.comparator_, this.root_
4800 .remove(key, this.comparator_)
4801 .copy(null, null, LLRBNode.BLACK, null, null));
4802 };
4803 /**
4804 * Returns the value of the node with the given key, or null.
4805 *
4806 * @param key - The key to look up.
4807 * @returns The value of the node with the given key, or null if the
4808 * key doesn't exist.
4809 */
4810 SortedMap.prototype.get = function (key) {
4811 var cmp;
4812 var node = this.root_;
4813 while (!node.isEmpty()) {
4814 cmp = this.comparator_(key, node.key);
4815 if (cmp === 0) {
4816 return node.value;
4817 }
4818 else if (cmp < 0) {
4819 node = node.left;
4820 }
4821 else if (cmp > 0) {
4822 node = node.right;
4823 }
4824 }
4825 return null;
4826 };
4827 /**
4828 * Returns the key of the item *before* the specified key, or null if key is the first item.
4829 * @param key - The key to find the predecessor of
4830 * @returns The predecessor key.
4831 */
4832 SortedMap.prototype.getPredecessorKey = function (key) {
4833 var cmp, node = this.root_, rightParent = null;
4834 while (!node.isEmpty()) {
4835 cmp = this.comparator_(key, node.key);
4836 if (cmp === 0) {
4837 if (!node.left.isEmpty()) {
4838 node = node.left;
4839 while (!node.right.isEmpty()) {
4840 node = node.right;
4841 }
4842 return node.key;
4843 }
4844 else if (rightParent) {
4845 return rightParent.key;
4846 }
4847 else {
4848 return null; // first item.
4849 }
4850 }
4851 else if (cmp < 0) {
4852 node = node.left;
4853 }
4854 else if (cmp > 0) {
4855 rightParent = node;
4856 node = node.right;
4857 }
4858 }
4859 throw new Error('Attempted to find predecessor key for a nonexistent key. What gives?');
4860 };
4861 /**
4862 * @returns True if the map is empty.
4863 */
4864 SortedMap.prototype.isEmpty = function () {
4865 return this.root_.isEmpty();
4866 };
4867 /**
4868 * @returns The total number of nodes in the map.
4869 */
4870 SortedMap.prototype.count = function () {
4871 return this.root_.count();
4872 };
4873 /**
4874 * @returns The minimum key in the map.
4875 */
4876 SortedMap.prototype.minKey = function () {
4877 return this.root_.minKey();
4878 };
4879 /**
4880 * @returns The maximum key in the map.
4881 */
4882 SortedMap.prototype.maxKey = function () {
4883 return this.root_.maxKey();
4884 };
4885 /**
4886 * Traverses the map in key order and calls the specified action function
4887 * for each key/value pair.
4888 *
4889 * @param action - Callback function to be called
4890 * for each key/value pair. If action returns true, traversal is aborted.
4891 * @returns The first truthy value returned by action, or the last falsey
4892 * value returned by action
4893 */
4894 SortedMap.prototype.inorderTraversal = function (action) {
4895 return this.root_.inorderTraversal(action);
4896 };
4897 /**
4898 * Traverses the map in reverse key order and calls the specified action function
4899 * for each key/value pair.
4900 *
4901 * @param action - Callback function to be called
4902 * for each key/value pair. If action returns true, traversal is aborted.
4903 * @returns True if the traversal was aborted.
4904 */
4905 SortedMap.prototype.reverseTraversal = function (action) {
4906 return this.root_.reverseTraversal(action);
4907 };
4908 /**
4909 * Returns an iterator over the SortedMap.
4910 * @returns The iterator.
4911 */
4912 SortedMap.prototype.getIterator = function (resultGenerator) {
4913 return new SortedMapIterator(this.root_, null, this.comparator_, false, resultGenerator);
4914 };
4915 SortedMap.prototype.getIteratorFrom = function (key, resultGenerator) {
4916 return new SortedMapIterator(this.root_, key, this.comparator_, false, resultGenerator);
4917 };
4918 SortedMap.prototype.getReverseIteratorFrom = function (key, resultGenerator) {
4919 return new SortedMapIterator(this.root_, key, this.comparator_, true, resultGenerator);
4920 };
4921 SortedMap.prototype.getReverseIterator = function (resultGenerator) {
4922 return new SortedMapIterator(this.root_, null, this.comparator_, true, resultGenerator);
4923 };
4924 /**
4925 * Always use the same empty node, to reduce memory.
4926 */
4927 SortedMap.EMPTY_NODE = new LLRBEmptyNode();
4928 return SortedMap;
4929}());
4930
4931/**
4932 * @license
4933 * Copyright 2017 Google LLC
4934 *
4935 * Licensed under the Apache License, Version 2.0 (the "License");
4936 * you may not use this file except in compliance with the License.
4937 * You may obtain a copy of the License at
4938 *
4939 * http://www.apache.org/licenses/LICENSE-2.0
4940 *
4941 * Unless required by applicable law or agreed to in writing, software
4942 * distributed under the License is distributed on an "AS IS" BASIS,
4943 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4944 * See the License for the specific language governing permissions and
4945 * limitations under the License.
4946 */
4947function NAME_ONLY_COMPARATOR(left, right) {
4948 return nameCompare(left.name, right.name);
4949}
4950function NAME_COMPARATOR(left, right) {
4951 return nameCompare(left, right);
4952}
4953
4954/**
4955 * @license
4956 * Copyright 2017 Google LLC
4957 *
4958 * Licensed under the Apache License, Version 2.0 (the "License");
4959 * you may not use this file except in compliance with the License.
4960 * You may obtain a copy of the License at
4961 *
4962 * http://www.apache.org/licenses/LICENSE-2.0
4963 *
4964 * Unless required by applicable law or agreed to in writing, software
4965 * distributed under the License is distributed on an "AS IS" BASIS,
4966 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4967 * See the License for the specific language governing permissions and
4968 * limitations under the License.
4969 */
4970var MAX_NODE$2;
4971function setMaxNode$1(val) {
4972 MAX_NODE$2 = val;
4973}
4974var priorityHashText = function (priority) {
4975 if (typeof priority === 'number') {
4976 return 'number:' + doubleToIEEE754String(priority);
4977 }
4978 else {
4979 return 'string:' + priority;
4980 }
4981};
4982/**
4983 * Validates that a priority snapshot Node is valid.
4984 */
4985var validatePriorityNode = function (priorityNode) {
4986 if (priorityNode.isLeafNode()) {
4987 var val = priorityNode.val();
4988 util.assert(typeof val === 'string' ||
4989 typeof val === 'number' ||
4990 (typeof val === 'object' && util.contains(val, '.sv')), 'Priority must be a string or number.');
4991 }
4992 else {
4993 util.assert(priorityNode === MAX_NODE$2 || priorityNode.isEmpty(), 'priority of unexpected type.');
4994 }
4995 // Don't call getPriority() on MAX_NODE to avoid hitting assertion.
4996 util.assert(priorityNode === MAX_NODE$2 || priorityNode.getPriority().isEmpty(), "Priority nodes can't have a priority of their own.");
4997};
4998
4999/**
5000 * @license
5001 * Copyright 2017 Google LLC
5002 *
5003 * Licensed under the Apache License, Version 2.0 (the "License");
5004 * you may not use this file except in compliance with the License.
5005 * You may obtain a copy of the License at
5006 *
5007 * http://www.apache.org/licenses/LICENSE-2.0
5008 *
5009 * Unless required by applicable law or agreed to in writing, software
5010 * distributed under the License is distributed on an "AS IS" BASIS,
5011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5012 * See the License for the specific language governing permissions and
5013 * limitations under the License.
5014 */
5015var __childrenNodeConstructor;
5016/**
5017 * LeafNode is a class for storing leaf nodes in a DataSnapshot. It
5018 * implements Node and stores the value of the node (a string,
5019 * number, or boolean) accessible via getValue().
5020 */
5021var LeafNode = /** @class */ (function () {
5022 /**
5023 * @param value_ - The value to store in this leaf node. The object type is
5024 * possible in the event of a deferred value
5025 * @param priorityNode_ - The priority of this node.
5026 */
5027 function LeafNode(value_, priorityNode_) {
5028 if (priorityNode_ === void 0) { priorityNode_ = LeafNode.__childrenNodeConstructor.EMPTY_NODE; }
5029 this.value_ = value_;
5030 this.priorityNode_ = priorityNode_;
5031 this.lazyHash_ = null;
5032 util.assert(this.value_ !== undefined && this.value_ !== null, "LeafNode shouldn't be created with null/undefined value.");
5033 validatePriorityNode(this.priorityNode_);
5034 }
5035 Object.defineProperty(LeafNode, "__childrenNodeConstructor", {
5036 get: function () {
5037 return __childrenNodeConstructor;
5038 },
5039 set: function (val) {
5040 __childrenNodeConstructor = val;
5041 },
5042 enumerable: false,
5043 configurable: true
5044 });
5045 /** @inheritDoc */
5046 LeafNode.prototype.isLeafNode = function () {
5047 return true;
5048 };
5049 /** @inheritDoc */
5050 LeafNode.prototype.getPriority = function () {
5051 return this.priorityNode_;
5052 };
5053 /** @inheritDoc */
5054 LeafNode.prototype.updatePriority = function (newPriorityNode) {
5055 return new LeafNode(this.value_, newPriorityNode);
5056 };
5057 /** @inheritDoc */
5058 LeafNode.prototype.getImmediateChild = function (childName) {
5059 // Hack to treat priority as a regular child
5060 if (childName === '.priority') {
5061 return this.priorityNode_;
5062 }
5063 else {
5064 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5065 }
5066 };
5067 /** @inheritDoc */
5068 LeafNode.prototype.getChild = function (path) {
5069 if (pathIsEmpty(path)) {
5070 return this;
5071 }
5072 else if (pathGetFront(path) === '.priority') {
5073 return this.priorityNode_;
5074 }
5075 else {
5076 return LeafNode.__childrenNodeConstructor.EMPTY_NODE;
5077 }
5078 };
5079 LeafNode.prototype.hasChild = function () {
5080 return false;
5081 };
5082 /** @inheritDoc */
5083 LeafNode.prototype.getPredecessorChildName = function (childName, childNode) {
5084 return null;
5085 };
5086 /** @inheritDoc */
5087 LeafNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5088 if (childName === '.priority') {
5089 return this.updatePriority(newChildNode);
5090 }
5091 else if (newChildNode.isEmpty() && childName !== '.priority') {
5092 return this;
5093 }
5094 else {
5095 return LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateImmediateChild(childName, newChildNode).updatePriority(this.priorityNode_);
5096 }
5097 };
5098 /** @inheritDoc */
5099 LeafNode.prototype.updateChild = function (path, newChildNode) {
5100 var front = pathGetFront(path);
5101 if (front === null) {
5102 return newChildNode;
5103 }
5104 else if (newChildNode.isEmpty() && front !== '.priority') {
5105 return this;
5106 }
5107 else {
5108 util.assert(front !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5109 return this.updateImmediateChild(front, LeafNode.__childrenNodeConstructor.EMPTY_NODE.updateChild(pathPopFront(path), newChildNode));
5110 }
5111 };
5112 /** @inheritDoc */
5113 LeafNode.prototype.isEmpty = function () {
5114 return false;
5115 };
5116 /** @inheritDoc */
5117 LeafNode.prototype.numChildren = function () {
5118 return 0;
5119 };
5120 /** @inheritDoc */
5121 LeafNode.prototype.forEachChild = function (index, action) {
5122 return false;
5123 };
5124 LeafNode.prototype.val = function (exportFormat) {
5125 if (exportFormat && !this.getPriority().isEmpty()) {
5126 return {
5127 '.value': this.getValue(),
5128 '.priority': this.getPriority().val()
5129 };
5130 }
5131 else {
5132 return this.getValue();
5133 }
5134 };
5135 /** @inheritDoc */
5136 LeafNode.prototype.hash = function () {
5137 if (this.lazyHash_ === null) {
5138 var toHash = '';
5139 if (!this.priorityNode_.isEmpty()) {
5140 toHash +=
5141 'priority:' +
5142 priorityHashText(this.priorityNode_.val()) +
5143 ':';
5144 }
5145 var type = typeof this.value_;
5146 toHash += type + ':';
5147 if (type === 'number') {
5148 toHash += doubleToIEEE754String(this.value_);
5149 }
5150 else {
5151 toHash += this.value_;
5152 }
5153 this.lazyHash_ = sha1(toHash);
5154 }
5155 return this.lazyHash_;
5156 };
5157 /**
5158 * Returns the value of the leaf node.
5159 * @returns The value of the node.
5160 */
5161 LeafNode.prototype.getValue = function () {
5162 return this.value_;
5163 };
5164 LeafNode.prototype.compareTo = function (other) {
5165 if (other === LeafNode.__childrenNodeConstructor.EMPTY_NODE) {
5166 return 1;
5167 }
5168 else if (other instanceof LeafNode.__childrenNodeConstructor) {
5169 return -1;
5170 }
5171 else {
5172 util.assert(other.isLeafNode(), 'Unknown node type');
5173 return this.compareToLeafNode_(other);
5174 }
5175 };
5176 /**
5177 * Comparison specifically for two leaf nodes
5178 */
5179 LeafNode.prototype.compareToLeafNode_ = function (otherLeaf) {
5180 var otherLeafType = typeof otherLeaf.value_;
5181 var thisLeafType = typeof this.value_;
5182 var otherIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(otherLeafType);
5183 var thisIndex = LeafNode.VALUE_TYPE_ORDER.indexOf(thisLeafType);
5184 util.assert(otherIndex >= 0, 'Unknown leaf type: ' + otherLeafType);
5185 util.assert(thisIndex >= 0, 'Unknown leaf type: ' + thisLeafType);
5186 if (otherIndex === thisIndex) {
5187 // Same type, compare values
5188 if (thisLeafType === 'object') {
5189 // Deferred value nodes are all equal, but we should also never get to this point...
5190 return 0;
5191 }
5192 else {
5193 // Note that this works because true > false, all others are number or string comparisons
5194 if (this.value_ < otherLeaf.value_) {
5195 return -1;
5196 }
5197 else if (this.value_ === otherLeaf.value_) {
5198 return 0;
5199 }
5200 else {
5201 return 1;
5202 }
5203 }
5204 }
5205 else {
5206 return thisIndex - otherIndex;
5207 }
5208 };
5209 LeafNode.prototype.withIndex = function () {
5210 return this;
5211 };
5212 LeafNode.prototype.isIndexed = function () {
5213 return true;
5214 };
5215 LeafNode.prototype.equals = function (other) {
5216 if (other === this) {
5217 return true;
5218 }
5219 else if (other.isLeafNode()) {
5220 var otherLeaf = other;
5221 return (this.value_ === otherLeaf.value_ &&
5222 this.priorityNode_.equals(otherLeaf.priorityNode_));
5223 }
5224 else {
5225 return false;
5226 }
5227 };
5228 /**
5229 * The sort order for comparing leaf nodes of different types. If two leaf nodes have
5230 * the same type, the comparison falls back to their value
5231 */
5232 LeafNode.VALUE_TYPE_ORDER = ['object', 'boolean', 'number', 'string'];
5233 return LeafNode;
5234}());
5235
5236/**
5237 * @license
5238 * Copyright 2017 Google LLC
5239 *
5240 * Licensed under the Apache License, Version 2.0 (the "License");
5241 * you may not use this file except in compliance with the License.
5242 * You may obtain a copy of the License at
5243 *
5244 * http://www.apache.org/licenses/LICENSE-2.0
5245 *
5246 * Unless required by applicable law or agreed to in writing, software
5247 * distributed under the License is distributed on an "AS IS" BASIS,
5248 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5249 * See the License for the specific language governing permissions and
5250 * limitations under the License.
5251 */
5252var nodeFromJSON$1;
5253var MAX_NODE$1;
5254function setNodeFromJSON(val) {
5255 nodeFromJSON$1 = val;
5256}
5257function setMaxNode(val) {
5258 MAX_NODE$1 = val;
5259}
5260var PriorityIndex = /** @class */ (function (_super) {
5261 tslib.__extends(PriorityIndex, _super);
5262 function PriorityIndex() {
5263 return _super !== null && _super.apply(this, arguments) || this;
5264 }
5265 PriorityIndex.prototype.compare = function (a, b) {
5266 var aPriority = a.node.getPriority();
5267 var bPriority = b.node.getPriority();
5268 var indexCmp = aPriority.compareTo(bPriority);
5269 if (indexCmp === 0) {
5270 return nameCompare(a.name, b.name);
5271 }
5272 else {
5273 return indexCmp;
5274 }
5275 };
5276 PriorityIndex.prototype.isDefinedOn = function (node) {
5277 return !node.getPriority().isEmpty();
5278 };
5279 PriorityIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
5280 return !oldNode.getPriority().equals(newNode.getPriority());
5281 };
5282 PriorityIndex.prototype.minPost = function () {
5283 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5284 return NamedNode.MIN;
5285 };
5286 PriorityIndex.prototype.maxPost = function () {
5287 return new NamedNode(MAX_NAME, new LeafNode('[PRIORITY-POST]', MAX_NODE$1));
5288 };
5289 PriorityIndex.prototype.makePost = function (indexValue, name) {
5290 var priorityNode = nodeFromJSON$1(indexValue);
5291 return new NamedNode(name, new LeafNode('[PRIORITY-POST]', priorityNode));
5292 };
5293 /**
5294 * @returns String representation for inclusion in a query spec
5295 */
5296 PriorityIndex.prototype.toString = function () {
5297 return '.priority';
5298 };
5299 return PriorityIndex;
5300}(Index));
5301var PRIORITY_INDEX = new PriorityIndex();
5302
5303/**
5304 * @license
5305 * Copyright 2017 Google LLC
5306 *
5307 * Licensed under the Apache License, Version 2.0 (the "License");
5308 * you may not use this file except in compliance with the License.
5309 * You may obtain a copy of the License at
5310 *
5311 * http://www.apache.org/licenses/LICENSE-2.0
5312 *
5313 * Unless required by applicable law or agreed to in writing, software
5314 * distributed under the License is distributed on an "AS IS" BASIS,
5315 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5316 * See the License for the specific language governing permissions and
5317 * limitations under the License.
5318 */
5319var LOG_2 = Math.log(2);
5320var Base12Num = /** @class */ (function () {
5321 function Base12Num(length) {
5322 var logBase2 = function (num) {
5323 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5324 return parseInt((Math.log(num) / LOG_2), 10);
5325 };
5326 var bitMask = function (bits) { return parseInt(Array(bits + 1).join('1'), 2); };
5327 this.count = logBase2(length + 1);
5328 this.current_ = this.count - 1;
5329 var mask = bitMask(this.count);
5330 this.bits_ = (length + 1) & mask;
5331 }
5332 Base12Num.prototype.nextBitIsOne = function () {
5333 //noinspection JSBitwiseOperatorUsage
5334 var result = !(this.bits_ & (0x1 << this.current_));
5335 this.current_--;
5336 return result;
5337 };
5338 return Base12Num;
5339}());
5340/**
5341 * Takes a list of child nodes and constructs a SortedSet using the given comparison
5342 * function
5343 *
5344 * Uses the algorithm described in the paper linked here:
5345 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458
5346 *
5347 * @param childList - Unsorted list of children
5348 * @param cmp - The comparison method to be used
5349 * @param keyFn - An optional function to extract K from a node wrapper, if K's
5350 * type is not NamedNode
5351 * @param mapSortFn - An optional override for comparator used by the generated sorted map
5352 */
5353var buildChildSet = function (childList, cmp, keyFn, mapSortFn) {
5354 childList.sort(cmp);
5355 var buildBalancedTree = function (low, high) {
5356 var length = high - low;
5357 var namedNode;
5358 var key;
5359 if (length === 0) {
5360 return null;
5361 }
5362 else if (length === 1) {
5363 namedNode = childList[low];
5364 key = keyFn ? keyFn(namedNode) : namedNode;
5365 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, null, null);
5366 }
5367 else {
5368 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5369 var middle = parseInt((length / 2), 10) + low;
5370 var left = buildBalancedTree(low, middle);
5371 var right = buildBalancedTree(middle + 1, high);
5372 namedNode = childList[middle];
5373 key = keyFn ? keyFn(namedNode) : namedNode;
5374 return new LLRBNode(key, namedNode.node, LLRBNode.BLACK, left, right);
5375 }
5376 };
5377 var buildFrom12Array = function (base12) {
5378 var node = null;
5379 var root = null;
5380 var index = childList.length;
5381 var buildPennant = function (chunkSize, color) {
5382 var low = index - chunkSize;
5383 var high = index;
5384 index -= chunkSize;
5385 var childTree = buildBalancedTree(low + 1, high);
5386 var namedNode = childList[low];
5387 var key = keyFn ? keyFn(namedNode) : namedNode;
5388 attachPennant(new LLRBNode(key, namedNode.node, color, null, childTree));
5389 };
5390 var attachPennant = function (pennant) {
5391 if (node) {
5392 node.left = pennant;
5393 node = pennant;
5394 }
5395 else {
5396 root = pennant;
5397 node = pennant;
5398 }
5399 };
5400 for (var i = 0; i < base12.count; ++i) {
5401 var isOne = base12.nextBitIsOne();
5402 // The number of nodes taken in each slice is 2^(arr.length - (i + 1))
5403 var chunkSize = Math.pow(2, base12.count - (i + 1));
5404 if (isOne) {
5405 buildPennant(chunkSize, LLRBNode.BLACK);
5406 }
5407 else {
5408 // current == 2
5409 buildPennant(chunkSize, LLRBNode.BLACK);
5410 buildPennant(chunkSize, LLRBNode.RED);
5411 }
5412 }
5413 return root;
5414 };
5415 var base12 = new Base12Num(childList.length);
5416 var root = buildFrom12Array(base12);
5417 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5418 return new SortedMap(mapSortFn || cmp, root);
5419};
5420
5421/**
5422 * @license
5423 * Copyright 2017 Google LLC
5424 *
5425 * Licensed under the Apache License, Version 2.0 (the "License");
5426 * you may not use this file except in compliance with the License.
5427 * You may obtain a copy of the License at
5428 *
5429 * http://www.apache.org/licenses/LICENSE-2.0
5430 *
5431 * Unless required by applicable law or agreed to in writing, software
5432 * distributed under the License is distributed on an "AS IS" BASIS,
5433 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5434 * See the License for the specific language governing permissions and
5435 * limitations under the License.
5436 */
5437var _defaultIndexMap;
5438var fallbackObject = {};
5439var IndexMap = /** @class */ (function () {
5440 function IndexMap(indexes_, indexSet_) {
5441 this.indexes_ = indexes_;
5442 this.indexSet_ = indexSet_;
5443 }
5444 Object.defineProperty(IndexMap, "Default", {
5445 /**
5446 * The default IndexMap for nodes without a priority
5447 */
5448 get: function () {
5449 util.assert(fallbackObject && PRIORITY_INDEX, 'ChildrenNode.ts has not been loaded');
5450 _defaultIndexMap =
5451 _defaultIndexMap ||
5452 new IndexMap({ '.priority': fallbackObject }, { '.priority': PRIORITY_INDEX });
5453 return _defaultIndexMap;
5454 },
5455 enumerable: false,
5456 configurable: true
5457 });
5458 IndexMap.prototype.get = function (indexKey) {
5459 var sortedMap = util.safeGet(this.indexes_, indexKey);
5460 if (!sortedMap) {
5461 throw new Error('No index defined for ' + indexKey);
5462 }
5463 if (sortedMap instanceof SortedMap) {
5464 return sortedMap;
5465 }
5466 else {
5467 // The index exists, but it falls back to just name comparison. Return null so that the calling code uses the
5468 // regular child map
5469 return null;
5470 }
5471 };
5472 IndexMap.prototype.hasIndex = function (indexDefinition) {
5473 return util.contains(this.indexSet_, indexDefinition.toString());
5474 };
5475 IndexMap.prototype.addIndex = function (indexDefinition, existingChildren) {
5476 util.assert(indexDefinition !== KEY_INDEX, "KeyIndex always exists and isn't meant to be added to the IndexMap.");
5477 var childList = [];
5478 var sawIndexedValue = false;
5479 var iter = existingChildren.getIterator(NamedNode.Wrap);
5480 var next = iter.getNext();
5481 while (next) {
5482 sawIndexedValue =
5483 sawIndexedValue || indexDefinition.isDefinedOn(next.node);
5484 childList.push(next);
5485 next = iter.getNext();
5486 }
5487 var newIndex;
5488 if (sawIndexedValue) {
5489 newIndex = buildChildSet(childList, indexDefinition.getCompare());
5490 }
5491 else {
5492 newIndex = fallbackObject;
5493 }
5494 var indexName = indexDefinition.toString();
5495 var newIndexSet = tslib.__assign({}, this.indexSet_);
5496 newIndexSet[indexName] = indexDefinition;
5497 var newIndexes = tslib.__assign({}, this.indexes_);
5498 newIndexes[indexName] = newIndex;
5499 return new IndexMap(newIndexes, newIndexSet);
5500 };
5501 /**
5502 * Ensure that this node is properly tracked in any indexes that we're maintaining
5503 */
5504 IndexMap.prototype.addToIndexes = function (namedNode, existingChildren) {
5505 var _this = this;
5506 var newIndexes = util.map(this.indexes_, function (indexedChildren, indexName) {
5507 var index = util.safeGet(_this.indexSet_, indexName);
5508 util.assert(index, 'Missing index implementation for ' + indexName);
5509 if (indexedChildren === fallbackObject) {
5510 // Check to see if we need to index everything
5511 if (index.isDefinedOn(namedNode.node)) {
5512 // We need to build this index
5513 var childList = [];
5514 var iter = existingChildren.getIterator(NamedNode.Wrap);
5515 var next = iter.getNext();
5516 while (next) {
5517 if (next.name !== namedNode.name) {
5518 childList.push(next);
5519 }
5520 next = iter.getNext();
5521 }
5522 childList.push(namedNode);
5523 return buildChildSet(childList, index.getCompare());
5524 }
5525 else {
5526 // No change, this remains a fallback
5527 return fallbackObject;
5528 }
5529 }
5530 else {
5531 var existingSnap = existingChildren.get(namedNode.name);
5532 var newChildren = indexedChildren;
5533 if (existingSnap) {
5534 newChildren = newChildren.remove(new NamedNode(namedNode.name, existingSnap));
5535 }
5536 return newChildren.insert(namedNode, namedNode.node);
5537 }
5538 });
5539 return new IndexMap(newIndexes, this.indexSet_);
5540 };
5541 /**
5542 * Create a new IndexMap instance with the given value removed
5543 */
5544 IndexMap.prototype.removeFromIndexes = function (namedNode, existingChildren) {
5545 var newIndexes = util.map(this.indexes_, function (indexedChildren) {
5546 if (indexedChildren === fallbackObject) {
5547 // This is the fallback. Just return it, nothing to do in this case
5548 return indexedChildren;
5549 }
5550 else {
5551 var existingSnap = existingChildren.get(namedNode.name);
5552 if (existingSnap) {
5553 return indexedChildren.remove(new NamedNode(namedNode.name, existingSnap));
5554 }
5555 else {
5556 // No record of this child
5557 return indexedChildren;
5558 }
5559 }
5560 });
5561 return new IndexMap(newIndexes, this.indexSet_);
5562 };
5563 return IndexMap;
5564}());
5565
5566/**
5567 * @license
5568 * Copyright 2017 Google LLC
5569 *
5570 * Licensed under the Apache License, Version 2.0 (the "License");
5571 * you may not use this file except in compliance with the License.
5572 * You may obtain a copy of the License at
5573 *
5574 * http://www.apache.org/licenses/LICENSE-2.0
5575 *
5576 * Unless required by applicable law or agreed to in writing, software
5577 * distributed under the License is distributed on an "AS IS" BASIS,
5578 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5579 * See the License for the specific language governing permissions and
5580 * limitations under the License.
5581 */
5582// TODO: For memory savings, don't store priorityNode_ if it's empty.
5583var EMPTY_NODE;
5584/**
5585 * ChildrenNode is a class for storing internal nodes in a DataSnapshot
5586 * (i.e. nodes with children). It implements Node and stores the
5587 * list of children in the children property, sorted by child name.
5588 */
5589var ChildrenNode = /** @class */ (function () {
5590 /**
5591 * @param children_ - List of children of this node..
5592 * @param priorityNode_ - The priority of this node (as a snapshot node).
5593 */
5594 function ChildrenNode(children_, priorityNode_, indexMap_) {
5595 this.children_ = children_;
5596 this.priorityNode_ = priorityNode_;
5597 this.indexMap_ = indexMap_;
5598 this.lazyHash_ = null;
5599 /**
5600 * Note: The only reason we allow null priority is for EMPTY_NODE, since we can't use
5601 * EMPTY_NODE as the priority of EMPTY_NODE. We might want to consider making EMPTY_NODE its own
5602 * class instead of an empty ChildrenNode.
5603 */
5604 if (this.priorityNode_) {
5605 validatePriorityNode(this.priorityNode_);
5606 }
5607 if (this.children_.isEmpty()) {
5608 util.assert(!this.priorityNode_ || this.priorityNode_.isEmpty(), 'An empty node cannot have a priority');
5609 }
5610 }
5611 Object.defineProperty(ChildrenNode, "EMPTY_NODE", {
5612 get: function () {
5613 return (EMPTY_NODE ||
5614 (EMPTY_NODE = new ChildrenNode(new SortedMap(NAME_COMPARATOR), null, IndexMap.Default)));
5615 },
5616 enumerable: false,
5617 configurable: true
5618 });
5619 /** @inheritDoc */
5620 ChildrenNode.prototype.isLeafNode = function () {
5621 return false;
5622 };
5623 /** @inheritDoc */
5624 ChildrenNode.prototype.getPriority = function () {
5625 return this.priorityNode_ || EMPTY_NODE;
5626 };
5627 /** @inheritDoc */
5628 ChildrenNode.prototype.updatePriority = function (newPriorityNode) {
5629 if (this.children_.isEmpty()) {
5630 // Don't allow priorities on empty nodes
5631 return this;
5632 }
5633 else {
5634 return new ChildrenNode(this.children_, newPriorityNode, this.indexMap_);
5635 }
5636 };
5637 /** @inheritDoc */
5638 ChildrenNode.prototype.getImmediateChild = function (childName) {
5639 // Hack to treat priority as a regular child
5640 if (childName === '.priority') {
5641 return this.getPriority();
5642 }
5643 else {
5644 var child = this.children_.get(childName);
5645 return child === null ? EMPTY_NODE : child;
5646 }
5647 };
5648 /** @inheritDoc */
5649 ChildrenNode.prototype.getChild = function (path) {
5650 var front = pathGetFront(path);
5651 if (front === null) {
5652 return this;
5653 }
5654 return this.getImmediateChild(front).getChild(pathPopFront(path));
5655 };
5656 /** @inheritDoc */
5657 ChildrenNode.prototype.hasChild = function (childName) {
5658 return this.children_.get(childName) !== null;
5659 };
5660 /** @inheritDoc */
5661 ChildrenNode.prototype.updateImmediateChild = function (childName, newChildNode) {
5662 util.assert(newChildNode, 'We should always be passing snapshot nodes');
5663 if (childName === '.priority') {
5664 return this.updatePriority(newChildNode);
5665 }
5666 else {
5667 var namedNode = new NamedNode(childName, newChildNode);
5668 var newChildren = void 0, newIndexMap = void 0;
5669 if (newChildNode.isEmpty()) {
5670 newChildren = this.children_.remove(childName);
5671 newIndexMap = this.indexMap_.removeFromIndexes(namedNode, this.children_);
5672 }
5673 else {
5674 newChildren = this.children_.insert(childName, newChildNode);
5675 newIndexMap = this.indexMap_.addToIndexes(namedNode, this.children_);
5676 }
5677 var newPriority = newChildren.isEmpty()
5678 ? EMPTY_NODE
5679 : this.priorityNode_;
5680 return new ChildrenNode(newChildren, newPriority, newIndexMap);
5681 }
5682 };
5683 /** @inheritDoc */
5684 ChildrenNode.prototype.updateChild = function (path, newChildNode) {
5685 var front = pathGetFront(path);
5686 if (front === null) {
5687 return newChildNode;
5688 }
5689 else {
5690 util.assert(pathGetFront(path) !== '.priority' || pathGetLength(path) === 1, '.priority must be the last token in a path');
5691 var newImmediateChild = this.getImmediateChild(front).updateChild(pathPopFront(path), newChildNode);
5692 return this.updateImmediateChild(front, newImmediateChild);
5693 }
5694 };
5695 /** @inheritDoc */
5696 ChildrenNode.prototype.isEmpty = function () {
5697 return this.children_.isEmpty();
5698 };
5699 /** @inheritDoc */
5700 ChildrenNode.prototype.numChildren = function () {
5701 return this.children_.count();
5702 };
5703 /** @inheritDoc */
5704 ChildrenNode.prototype.val = function (exportFormat) {
5705 if (this.isEmpty()) {
5706 return null;
5707 }
5708 var obj = {};
5709 var numKeys = 0, maxKey = 0, allIntegerKeys = true;
5710 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5711 obj[key] = childNode.val(exportFormat);
5712 numKeys++;
5713 if (allIntegerKeys && ChildrenNode.INTEGER_REGEXP_.test(key)) {
5714 maxKey = Math.max(maxKey, Number(key));
5715 }
5716 else {
5717 allIntegerKeys = false;
5718 }
5719 });
5720 if (!exportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
5721 // convert to array.
5722 var array = [];
5723 // eslint-disable-next-line guard-for-in
5724 for (var key in obj) {
5725 array[key] = obj[key];
5726 }
5727 return array;
5728 }
5729 else {
5730 if (exportFormat && !this.getPriority().isEmpty()) {
5731 obj['.priority'] = this.getPriority().val();
5732 }
5733 return obj;
5734 }
5735 };
5736 /** @inheritDoc */
5737 ChildrenNode.prototype.hash = function () {
5738 if (this.lazyHash_ === null) {
5739 var toHash_1 = '';
5740 if (!this.getPriority().isEmpty()) {
5741 toHash_1 +=
5742 'priority:' +
5743 priorityHashText(this.getPriority().val()) +
5744 ':';
5745 }
5746 this.forEachChild(PRIORITY_INDEX, function (key, childNode) {
5747 var childHash = childNode.hash();
5748 if (childHash !== '') {
5749 toHash_1 += ':' + key + ':' + childHash;
5750 }
5751 });
5752 this.lazyHash_ = toHash_1 === '' ? '' : sha1(toHash_1);
5753 }
5754 return this.lazyHash_;
5755 };
5756 /** @inheritDoc */
5757 ChildrenNode.prototype.getPredecessorChildName = function (childName, childNode, index) {
5758 var idx = this.resolveIndex_(index);
5759 if (idx) {
5760 var predecessor = idx.getPredecessorKey(new NamedNode(childName, childNode));
5761 return predecessor ? predecessor.name : null;
5762 }
5763 else {
5764 return this.children_.getPredecessorKey(childName);
5765 }
5766 };
5767 ChildrenNode.prototype.getFirstChildName = function (indexDefinition) {
5768 var idx = this.resolveIndex_(indexDefinition);
5769 if (idx) {
5770 var minKey = idx.minKey();
5771 return minKey && minKey.name;
5772 }
5773 else {
5774 return this.children_.minKey();
5775 }
5776 };
5777 ChildrenNode.prototype.getFirstChild = function (indexDefinition) {
5778 var minKey = this.getFirstChildName(indexDefinition);
5779 if (minKey) {
5780 return new NamedNode(minKey, this.children_.get(minKey));
5781 }
5782 else {
5783 return null;
5784 }
5785 };
5786 /**
5787 * Given an index, return the key name of the largest value we have, according to that index
5788 */
5789 ChildrenNode.prototype.getLastChildName = function (indexDefinition) {
5790 var idx = this.resolveIndex_(indexDefinition);
5791 if (idx) {
5792 var maxKey = idx.maxKey();
5793 return maxKey && maxKey.name;
5794 }
5795 else {
5796 return this.children_.maxKey();
5797 }
5798 };
5799 ChildrenNode.prototype.getLastChild = function (indexDefinition) {
5800 var maxKey = this.getLastChildName(indexDefinition);
5801 if (maxKey) {
5802 return new NamedNode(maxKey, this.children_.get(maxKey));
5803 }
5804 else {
5805 return null;
5806 }
5807 };
5808 ChildrenNode.prototype.forEachChild = function (index, action) {
5809 var idx = this.resolveIndex_(index);
5810 if (idx) {
5811 return idx.inorderTraversal(function (wrappedNode) {
5812 return action(wrappedNode.name, wrappedNode.node);
5813 });
5814 }
5815 else {
5816 return this.children_.inorderTraversal(action);
5817 }
5818 };
5819 ChildrenNode.prototype.getIterator = function (indexDefinition) {
5820 return this.getIteratorFrom(indexDefinition.minPost(), indexDefinition);
5821 };
5822 ChildrenNode.prototype.getIteratorFrom = function (startPost, indexDefinition) {
5823 var idx = this.resolveIndex_(indexDefinition);
5824 if (idx) {
5825 return idx.getIteratorFrom(startPost, function (key) { return key; });
5826 }
5827 else {
5828 var iterator = this.children_.getIteratorFrom(startPost.name, NamedNode.Wrap);
5829 var next = iterator.peek();
5830 while (next != null && indexDefinition.compare(next, startPost) < 0) {
5831 iterator.getNext();
5832 next = iterator.peek();
5833 }
5834 return iterator;
5835 }
5836 };
5837 ChildrenNode.prototype.getReverseIterator = function (indexDefinition) {
5838 return this.getReverseIteratorFrom(indexDefinition.maxPost(), indexDefinition);
5839 };
5840 ChildrenNode.prototype.getReverseIteratorFrom = function (endPost, indexDefinition) {
5841 var idx = this.resolveIndex_(indexDefinition);
5842 if (idx) {
5843 return idx.getReverseIteratorFrom(endPost, function (key) {
5844 return key;
5845 });
5846 }
5847 else {
5848 var iterator = this.children_.getReverseIteratorFrom(endPost.name, NamedNode.Wrap);
5849 var next = iterator.peek();
5850 while (next != null && indexDefinition.compare(next, endPost) > 0) {
5851 iterator.getNext();
5852 next = iterator.peek();
5853 }
5854 return iterator;
5855 }
5856 };
5857 ChildrenNode.prototype.compareTo = function (other) {
5858 if (this.isEmpty()) {
5859 if (other.isEmpty()) {
5860 return 0;
5861 }
5862 else {
5863 return -1;
5864 }
5865 }
5866 else if (other.isLeafNode() || other.isEmpty()) {
5867 return 1;
5868 }
5869 else if (other === MAX_NODE) {
5870 return -1;
5871 }
5872 else {
5873 // Must be another node with children.
5874 return 0;
5875 }
5876 };
5877 ChildrenNode.prototype.withIndex = function (indexDefinition) {
5878 if (indexDefinition === KEY_INDEX ||
5879 this.indexMap_.hasIndex(indexDefinition)) {
5880 return this;
5881 }
5882 else {
5883 var newIndexMap = this.indexMap_.addIndex(indexDefinition, this.children_);
5884 return new ChildrenNode(this.children_, this.priorityNode_, newIndexMap);
5885 }
5886 };
5887 ChildrenNode.prototype.isIndexed = function (index) {
5888 return index === KEY_INDEX || this.indexMap_.hasIndex(index);
5889 };
5890 ChildrenNode.prototype.equals = function (other) {
5891 if (other === this) {
5892 return true;
5893 }
5894 else if (other.isLeafNode()) {
5895 return false;
5896 }
5897 else {
5898 var otherChildrenNode = other;
5899 if (!this.getPriority().equals(otherChildrenNode.getPriority())) {
5900 return false;
5901 }
5902 else if (this.children_.count() === otherChildrenNode.children_.count()) {
5903 var thisIter = this.getIterator(PRIORITY_INDEX);
5904 var otherIter = otherChildrenNode.getIterator(PRIORITY_INDEX);
5905 var thisCurrent = thisIter.getNext();
5906 var otherCurrent = otherIter.getNext();
5907 while (thisCurrent && otherCurrent) {
5908 if (thisCurrent.name !== otherCurrent.name ||
5909 !thisCurrent.node.equals(otherCurrent.node)) {
5910 return false;
5911 }
5912 thisCurrent = thisIter.getNext();
5913 otherCurrent = otherIter.getNext();
5914 }
5915 return thisCurrent === null && otherCurrent === null;
5916 }
5917 else {
5918 return false;
5919 }
5920 }
5921 };
5922 /**
5923 * Returns a SortedMap ordered by index, or null if the default (by-key) ordering can be used
5924 * instead.
5925 *
5926 */
5927 ChildrenNode.prototype.resolveIndex_ = function (indexDefinition) {
5928 if (indexDefinition === KEY_INDEX) {
5929 return null;
5930 }
5931 else {
5932 return this.indexMap_.get(indexDefinition.toString());
5933 }
5934 };
5935 ChildrenNode.INTEGER_REGEXP_ = /^(0|[1-9]\d*)$/;
5936 return ChildrenNode;
5937}());
5938var MaxNode = /** @class */ (function (_super) {
5939 tslib.__extends(MaxNode, _super);
5940 function MaxNode() {
5941 return _super.call(this, new SortedMap(NAME_COMPARATOR), ChildrenNode.EMPTY_NODE, IndexMap.Default) || this;
5942 }
5943 MaxNode.prototype.compareTo = function (other) {
5944 if (other === this) {
5945 return 0;
5946 }
5947 else {
5948 return 1;
5949 }
5950 };
5951 MaxNode.prototype.equals = function (other) {
5952 // Not that we every compare it, but MAX_NODE is only ever equal to itself
5953 return other === this;
5954 };
5955 MaxNode.prototype.getPriority = function () {
5956 return this;
5957 };
5958 MaxNode.prototype.getImmediateChild = function (childName) {
5959 return ChildrenNode.EMPTY_NODE;
5960 };
5961 MaxNode.prototype.isEmpty = function () {
5962 return false;
5963 };
5964 return MaxNode;
5965}(ChildrenNode));
5966/**
5967 * Marker that will sort higher than any other snapshot.
5968 */
5969var MAX_NODE = new MaxNode();
5970Object.defineProperties(NamedNode, {
5971 MIN: {
5972 value: new NamedNode(MIN_NAME, ChildrenNode.EMPTY_NODE)
5973 },
5974 MAX: {
5975 value: new NamedNode(MAX_NAME, MAX_NODE)
5976 }
5977});
5978/**
5979 * Reference Extensions
5980 */
5981KeyIndex.__EMPTY_NODE = ChildrenNode.EMPTY_NODE;
5982LeafNode.__childrenNodeConstructor = ChildrenNode;
5983setMaxNode$1(MAX_NODE);
5984setMaxNode(MAX_NODE);
5985
5986/**
5987 * @license
5988 * Copyright 2017 Google LLC
5989 *
5990 * Licensed under the Apache License, Version 2.0 (the "License");
5991 * you may not use this file except in compliance with the License.
5992 * You may obtain a copy of the License at
5993 *
5994 * http://www.apache.org/licenses/LICENSE-2.0
5995 *
5996 * Unless required by applicable law or agreed to in writing, software
5997 * distributed under the License is distributed on an "AS IS" BASIS,
5998 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5999 * See the License for the specific language governing permissions and
6000 * limitations under the License.
6001 */
6002var USE_HINZE = true;
6003/**
6004 * Constructs a snapshot node representing the passed JSON and returns it.
6005 * @param json - JSON to create a node for.
6006 * @param priority - Optional priority to use. This will be ignored if the
6007 * passed JSON contains a .priority property.
6008 */
6009function nodeFromJSON(json, priority) {
6010 if (priority === void 0) { priority = null; }
6011 if (json === null) {
6012 return ChildrenNode.EMPTY_NODE;
6013 }
6014 if (typeof json === 'object' && '.priority' in json) {
6015 priority = json['.priority'];
6016 }
6017 util.assert(priority === null ||
6018 typeof priority === 'string' ||
6019 typeof priority === 'number' ||
6020 (typeof priority === 'object' && '.sv' in priority), 'Invalid priority type found: ' + typeof priority);
6021 if (typeof json === 'object' && '.value' in json && json['.value'] !== null) {
6022 json = json['.value'];
6023 }
6024 // Valid leaf nodes include non-objects or server-value wrapper objects
6025 if (typeof json !== 'object' || '.sv' in json) {
6026 var jsonLeaf = json;
6027 return new LeafNode(jsonLeaf, nodeFromJSON(priority));
6028 }
6029 if (!(json instanceof Array) && USE_HINZE) {
6030 var children_1 = [];
6031 var childrenHavePriority_1 = false;
6032 var hinzeJsonObj = json;
6033 each(hinzeJsonObj, function (key, child) {
6034 if (key.substring(0, 1) !== '.') {
6035 // Ignore metadata nodes
6036 var childNode = nodeFromJSON(child);
6037 if (!childNode.isEmpty()) {
6038 childrenHavePriority_1 =
6039 childrenHavePriority_1 || !childNode.getPriority().isEmpty();
6040 children_1.push(new NamedNode(key, childNode));
6041 }
6042 }
6043 });
6044 if (children_1.length === 0) {
6045 return ChildrenNode.EMPTY_NODE;
6046 }
6047 var childSet = buildChildSet(children_1, NAME_ONLY_COMPARATOR, function (namedNode) { return namedNode.name; }, NAME_COMPARATOR);
6048 if (childrenHavePriority_1) {
6049 var sortedChildSet = buildChildSet(children_1, PRIORITY_INDEX.getCompare());
6050 return new ChildrenNode(childSet, nodeFromJSON(priority), new IndexMap({ '.priority': sortedChildSet }, { '.priority': PRIORITY_INDEX }));
6051 }
6052 else {
6053 return new ChildrenNode(childSet, nodeFromJSON(priority), IndexMap.Default);
6054 }
6055 }
6056 else {
6057 var node_1 = ChildrenNode.EMPTY_NODE;
6058 each(json, function (key, childData) {
6059 if (util.contains(json, key)) {
6060 if (key.substring(0, 1) !== '.') {
6061 // ignore metadata nodes.
6062 var childNode = nodeFromJSON(childData);
6063 if (childNode.isLeafNode() || !childNode.isEmpty()) {
6064 node_1 = node_1.updateImmediateChild(key, childNode);
6065 }
6066 }
6067 }
6068 });
6069 return node_1.updatePriority(nodeFromJSON(priority));
6070 }
6071}
6072setNodeFromJSON(nodeFromJSON);
6073
6074/**
6075 * @license
6076 * Copyright 2017 Google LLC
6077 *
6078 * Licensed under the Apache License, Version 2.0 (the "License");
6079 * you may not use this file except in compliance with the License.
6080 * You may obtain a copy of the License at
6081 *
6082 * http://www.apache.org/licenses/LICENSE-2.0
6083 *
6084 * Unless required by applicable law or agreed to in writing, software
6085 * distributed under the License is distributed on an "AS IS" BASIS,
6086 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6087 * See the License for the specific language governing permissions and
6088 * limitations under the License.
6089 */
6090var PathIndex = /** @class */ (function (_super) {
6091 tslib.__extends(PathIndex, _super);
6092 function PathIndex(indexPath_) {
6093 var _this = _super.call(this) || this;
6094 _this.indexPath_ = indexPath_;
6095 util.assert(!pathIsEmpty(indexPath_) && pathGetFront(indexPath_) !== '.priority', "Can't create PathIndex with empty path or .priority key");
6096 return _this;
6097 }
6098 PathIndex.prototype.extractChild = function (snap) {
6099 return snap.getChild(this.indexPath_);
6100 };
6101 PathIndex.prototype.isDefinedOn = function (node) {
6102 return !node.getChild(this.indexPath_).isEmpty();
6103 };
6104 PathIndex.prototype.compare = function (a, b) {
6105 var aChild = this.extractChild(a.node);
6106 var bChild = this.extractChild(b.node);
6107 var indexCmp = aChild.compareTo(bChild);
6108 if (indexCmp === 0) {
6109 return nameCompare(a.name, b.name);
6110 }
6111 else {
6112 return indexCmp;
6113 }
6114 };
6115 PathIndex.prototype.makePost = function (indexValue, name) {
6116 var valueNode = nodeFromJSON(indexValue);
6117 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, valueNode);
6118 return new NamedNode(name, node);
6119 };
6120 PathIndex.prototype.maxPost = function () {
6121 var node = ChildrenNode.EMPTY_NODE.updateChild(this.indexPath_, MAX_NODE);
6122 return new NamedNode(MAX_NAME, node);
6123 };
6124 PathIndex.prototype.toString = function () {
6125 return pathSlice(this.indexPath_, 0).join('/');
6126 };
6127 return PathIndex;
6128}(Index));
6129
6130/**
6131 * @license
6132 * Copyright 2017 Google LLC
6133 *
6134 * Licensed under the Apache License, Version 2.0 (the "License");
6135 * you may not use this file except in compliance with the License.
6136 * You may obtain a copy of the License at
6137 *
6138 * http://www.apache.org/licenses/LICENSE-2.0
6139 *
6140 * Unless required by applicable law or agreed to in writing, software
6141 * distributed under the License is distributed on an "AS IS" BASIS,
6142 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6143 * See the License for the specific language governing permissions and
6144 * limitations under the License.
6145 */
6146var ValueIndex = /** @class */ (function (_super) {
6147 tslib.__extends(ValueIndex, _super);
6148 function ValueIndex() {
6149 return _super !== null && _super.apply(this, arguments) || this;
6150 }
6151 ValueIndex.prototype.compare = function (a, b) {
6152 var indexCmp = a.node.compareTo(b.node);
6153 if (indexCmp === 0) {
6154 return nameCompare(a.name, b.name);
6155 }
6156 else {
6157 return indexCmp;
6158 }
6159 };
6160 ValueIndex.prototype.isDefinedOn = function (node) {
6161 return true;
6162 };
6163 ValueIndex.prototype.indexedValueChanged = function (oldNode, newNode) {
6164 return !oldNode.equals(newNode);
6165 };
6166 ValueIndex.prototype.minPost = function () {
6167 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6168 return NamedNode.MIN;
6169 };
6170 ValueIndex.prototype.maxPost = function () {
6171 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6172 return NamedNode.MAX;
6173 };
6174 ValueIndex.prototype.makePost = function (indexValue, name) {
6175 var valueNode = nodeFromJSON(indexValue);
6176 return new NamedNode(name, valueNode);
6177 };
6178 /**
6179 * @returns String representation for inclusion in a query spec
6180 */
6181 ValueIndex.prototype.toString = function () {
6182 return '.value';
6183 };
6184 return ValueIndex;
6185}(Index));
6186var VALUE_INDEX = new ValueIndex();
6187
6188/**
6189 * @license
6190 * Copyright 2017 Google LLC
6191 *
6192 * Licensed under the Apache License, Version 2.0 (the "License");
6193 * you may not use this file except in compliance with the License.
6194 * You may obtain a copy of the License at
6195 *
6196 * http://www.apache.org/licenses/LICENSE-2.0
6197 *
6198 * Unless required by applicable law or agreed to in writing, software
6199 * distributed under the License is distributed on an "AS IS" BASIS,
6200 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6201 * See the License for the specific language governing permissions and
6202 * limitations under the License.
6203 */
6204function changeValue(snapshotNode) {
6205 return { type: "value" /* ChangeType.VALUE */, snapshotNode: snapshotNode };
6206}
6207function changeChildAdded(childName, snapshotNode) {
6208 return { type: "child_added" /* ChangeType.CHILD_ADDED */, snapshotNode: snapshotNode, childName: childName };
6209}
6210function changeChildRemoved(childName, snapshotNode) {
6211 return { type: "child_removed" /* ChangeType.CHILD_REMOVED */, snapshotNode: snapshotNode, childName: childName };
6212}
6213function changeChildChanged(childName, snapshotNode, oldSnap) {
6214 return {
6215 type: "child_changed" /* ChangeType.CHILD_CHANGED */,
6216 snapshotNode: snapshotNode,
6217 childName: childName,
6218 oldSnap: oldSnap
6219 };
6220}
6221function changeChildMoved(childName, snapshotNode) {
6222 return { type: "child_moved" /* ChangeType.CHILD_MOVED */, snapshotNode: snapshotNode, childName: childName };
6223}
6224
6225/**
6226 * @license
6227 * Copyright 2017 Google LLC
6228 *
6229 * Licensed under the Apache License, Version 2.0 (the "License");
6230 * you may not use this file except in compliance with the License.
6231 * You may obtain a copy of the License at
6232 *
6233 * http://www.apache.org/licenses/LICENSE-2.0
6234 *
6235 * Unless required by applicable law or agreed to in writing, software
6236 * distributed under the License is distributed on an "AS IS" BASIS,
6237 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6238 * See the License for the specific language governing permissions and
6239 * limitations under the License.
6240 */
6241/**
6242 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
6243 */
6244var IndexedFilter = /** @class */ (function () {
6245 function IndexedFilter(index_) {
6246 this.index_ = index_;
6247 }
6248 IndexedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6249 util.assert(snap.isIndexed(this.index_), 'A node must be indexed if only a child is updated');
6250 var oldChild = snap.getImmediateChild(key);
6251 // Check if anything actually changed.
6252 if (oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))) {
6253 // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
6254 // In this case, affectedPath will appear null in both the old and new snapshots. So we need
6255 // to avoid treating these cases as "nothing changed."
6256 if (oldChild.isEmpty() === newChild.isEmpty()) {
6257 // Nothing changed.
6258 // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
6259 //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
6260 return snap;
6261 }
6262 }
6263 if (optChangeAccumulator != null) {
6264 if (newChild.isEmpty()) {
6265 if (snap.hasChild(key)) {
6266 optChangeAccumulator.trackChildChange(changeChildRemoved(key, oldChild));
6267 }
6268 else {
6269 util.assert(snap.isLeafNode(), 'A child remove without an old child only makes sense on a leaf node');
6270 }
6271 }
6272 else if (oldChild.isEmpty()) {
6273 optChangeAccumulator.trackChildChange(changeChildAdded(key, newChild));
6274 }
6275 else {
6276 optChangeAccumulator.trackChildChange(changeChildChanged(key, newChild, oldChild));
6277 }
6278 }
6279 if (snap.isLeafNode() && newChild.isEmpty()) {
6280 return snap;
6281 }
6282 else {
6283 // Make sure the node is indexed
6284 return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
6285 }
6286 };
6287 IndexedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6288 if (optChangeAccumulator != null) {
6289 if (!oldSnap.isLeafNode()) {
6290 oldSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6291 if (!newSnap.hasChild(key)) {
6292 optChangeAccumulator.trackChildChange(changeChildRemoved(key, childNode));
6293 }
6294 });
6295 }
6296 if (!newSnap.isLeafNode()) {
6297 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6298 if (oldSnap.hasChild(key)) {
6299 var oldChild = oldSnap.getImmediateChild(key);
6300 if (!oldChild.equals(childNode)) {
6301 optChangeAccumulator.trackChildChange(changeChildChanged(key, childNode, oldChild));
6302 }
6303 }
6304 else {
6305 optChangeAccumulator.trackChildChange(changeChildAdded(key, childNode));
6306 }
6307 });
6308 }
6309 }
6310 return newSnap.withIndex(this.index_);
6311 };
6312 IndexedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6313 if (oldSnap.isEmpty()) {
6314 return ChildrenNode.EMPTY_NODE;
6315 }
6316 else {
6317 return oldSnap.updatePriority(newPriority);
6318 }
6319 };
6320 IndexedFilter.prototype.filtersNodes = function () {
6321 return false;
6322 };
6323 IndexedFilter.prototype.getIndexedFilter = function () {
6324 return this;
6325 };
6326 IndexedFilter.prototype.getIndex = function () {
6327 return this.index_;
6328 };
6329 return IndexedFilter;
6330}());
6331
6332/**
6333 * @license
6334 * Copyright 2017 Google LLC
6335 *
6336 * Licensed under the Apache License, Version 2.0 (the "License");
6337 * you may not use this file except in compliance with the License.
6338 * You may obtain a copy of the License at
6339 *
6340 * http://www.apache.org/licenses/LICENSE-2.0
6341 *
6342 * Unless required by applicable law or agreed to in writing, software
6343 * distributed under the License is distributed on an "AS IS" BASIS,
6344 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6345 * See the License for the specific language governing permissions and
6346 * limitations under the License.
6347 */
6348/**
6349 * Filters nodes by range and uses an IndexFilter to track any changes after filtering the node
6350 */
6351var RangedFilter = /** @class */ (function () {
6352 function RangedFilter(params) {
6353 this.indexedFilter_ = new IndexedFilter(params.getIndex());
6354 this.index_ = params.getIndex();
6355 this.startPost_ = RangedFilter.getStartPost_(params);
6356 this.endPost_ = RangedFilter.getEndPost_(params);
6357 this.startIsInclusive_ = !params.startAfterSet_;
6358 this.endIsInclusive_ = !params.endBeforeSet_;
6359 }
6360 RangedFilter.prototype.getStartPost = function () {
6361 return this.startPost_;
6362 };
6363 RangedFilter.prototype.getEndPost = function () {
6364 return this.endPost_;
6365 };
6366 RangedFilter.prototype.matches = function (node) {
6367 var isWithinStart = this.startIsInclusive_
6368 ? this.index_.compare(this.getStartPost(), node) <= 0
6369 : this.index_.compare(this.getStartPost(), node) < 0;
6370 var isWithinEnd = this.endIsInclusive_
6371 ? this.index_.compare(node, this.getEndPost()) <= 0
6372 : this.index_.compare(node, this.getEndPost()) < 0;
6373 return isWithinStart && isWithinEnd;
6374 };
6375 RangedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6376 if (!this.matches(new NamedNode(key, newChild))) {
6377 newChild = ChildrenNode.EMPTY_NODE;
6378 }
6379 return this.indexedFilter_.updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6380 };
6381 RangedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6382 if (newSnap.isLeafNode()) {
6383 // Make sure we have a children node with the correct index, not a leaf node;
6384 newSnap = ChildrenNode.EMPTY_NODE;
6385 }
6386 var filtered = newSnap.withIndex(this.index_);
6387 // Don't support priorities on queries
6388 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6389 var self = this;
6390 newSnap.forEachChild(PRIORITY_INDEX, function (key, childNode) {
6391 if (!self.matches(new NamedNode(key, childNode))) {
6392 filtered = filtered.updateImmediateChild(key, ChildrenNode.EMPTY_NODE);
6393 }
6394 });
6395 return this.indexedFilter_.updateFullNode(oldSnap, filtered, optChangeAccumulator);
6396 };
6397 RangedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6398 // Don't support priorities on queries
6399 return oldSnap;
6400 };
6401 RangedFilter.prototype.filtersNodes = function () {
6402 return true;
6403 };
6404 RangedFilter.prototype.getIndexedFilter = function () {
6405 return this.indexedFilter_;
6406 };
6407 RangedFilter.prototype.getIndex = function () {
6408 return this.index_;
6409 };
6410 RangedFilter.getStartPost_ = function (params) {
6411 if (params.hasStart()) {
6412 var startName = params.getIndexStartName();
6413 return params.getIndex().makePost(params.getIndexStartValue(), startName);
6414 }
6415 else {
6416 return params.getIndex().minPost();
6417 }
6418 };
6419 RangedFilter.getEndPost_ = function (params) {
6420 if (params.hasEnd()) {
6421 var endName = params.getIndexEndName();
6422 return params.getIndex().makePost(params.getIndexEndValue(), endName);
6423 }
6424 else {
6425 return params.getIndex().maxPost();
6426 }
6427 };
6428 return RangedFilter;
6429}());
6430
6431/**
6432 * @license
6433 * Copyright 2017 Google LLC
6434 *
6435 * Licensed under the Apache License, Version 2.0 (the "License");
6436 * you may not use this file except in compliance with the License.
6437 * You may obtain a copy of the License at
6438 *
6439 * http://www.apache.org/licenses/LICENSE-2.0
6440 *
6441 * Unless required by applicable law or agreed to in writing, software
6442 * distributed under the License is distributed on an "AS IS" BASIS,
6443 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6444 * See the License for the specific language governing permissions and
6445 * limitations under the License.
6446 */
6447/**
6448 * Applies a limit and a range to a node and uses RangedFilter to do the heavy lifting where possible
6449 */
6450var LimitedFilter = /** @class */ (function () {
6451 function LimitedFilter(params) {
6452 var _this = this;
6453 this.withinDirectionalStart = function (node) {
6454 return _this.reverse_ ? _this.withinEndPost(node) : _this.withinStartPost(node);
6455 };
6456 this.withinDirectionalEnd = function (node) {
6457 return _this.reverse_ ? _this.withinStartPost(node) : _this.withinEndPost(node);
6458 };
6459 this.withinStartPost = function (node) {
6460 var compareRes = _this.index_.compare(_this.rangedFilter_.getStartPost(), node);
6461 return _this.startIsInclusive_ ? compareRes <= 0 : compareRes < 0;
6462 };
6463 this.withinEndPost = function (node) {
6464 var compareRes = _this.index_.compare(node, _this.rangedFilter_.getEndPost());
6465 return _this.endIsInclusive_ ? compareRes <= 0 : compareRes < 0;
6466 };
6467 this.rangedFilter_ = new RangedFilter(params);
6468 this.index_ = params.getIndex();
6469 this.limit_ = params.getLimit();
6470 this.reverse_ = !params.isViewFromLeft();
6471 this.startIsInclusive_ = !params.startAfterSet_;
6472 this.endIsInclusive_ = !params.endBeforeSet_;
6473 }
6474 LimitedFilter.prototype.updateChild = function (snap, key, newChild, affectedPath, source, optChangeAccumulator) {
6475 if (!this.rangedFilter_.matches(new NamedNode(key, newChild))) {
6476 newChild = ChildrenNode.EMPTY_NODE;
6477 }
6478 if (snap.getImmediateChild(key).equals(newChild)) {
6479 // No change
6480 return snap;
6481 }
6482 else if (snap.numChildren() < this.limit_) {
6483 return this.rangedFilter_
6484 .getIndexedFilter()
6485 .updateChild(snap, key, newChild, affectedPath, source, optChangeAccumulator);
6486 }
6487 else {
6488 return this.fullLimitUpdateChild_(snap, key, newChild, source, optChangeAccumulator);
6489 }
6490 };
6491 LimitedFilter.prototype.updateFullNode = function (oldSnap, newSnap, optChangeAccumulator) {
6492 var filtered;
6493 if (newSnap.isLeafNode() || newSnap.isEmpty()) {
6494 // Make sure we have a children node with the correct index, not a leaf node;
6495 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6496 }
6497 else {
6498 if (this.limit_ * 2 < newSnap.numChildren() &&
6499 newSnap.isIndexed(this.index_)) {
6500 // Easier to build up a snapshot, since what we're given has more than twice the elements we want
6501 filtered = ChildrenNode.EMPTY_NODE.withIndex(this.index_);
6502 // anchor to the startPost, endPost, or last element as appropriate
6503 var iterator = void 0;
6504 if (this.reverse_) {
6505 iterator = newSnap.getReverseIteratorFrom(this.rangedFilter_.getEndPost(), this.index_);
6506 }
6507 else {
6508 iterator = newSnap.getIteratorFrom(this.rangedFilter_.getStartPost(), this.index_);
6509 }
6510 var count = 0;
6511 while (iterator.hasNext() && count < this.limit_) {
6512 var next = iterator.getNext();
6513 if (!this.withinDirectionalStart(next)) {
6514 // if we have not reached the start, skip to the next element
6515 continue;
6516 }
6517 else if (!this.withinDirectionalEnd(next)) {
6518 // if we have reached the end, stop adding elements
6519 break;
6520 }
6521 else {
6522 filtered = filtered.updateImmediateChild(next.name, next.node);
6523 count++;
6524 }
6525 }
6526 }
6527 else {
6528 // The snap contains less than twice the limit. Faster to delete from the snap than build up a new one
6529 filtered = newSnap.withIndex(this.index_);
6530 // Don't support priorities on queries
6531 filtered = filtered.updatePriority(ChildrenNode.EMPTY_NODE);
6532 var iterator = void 0;
6533 if (this.reverse_) {
6534 iterator = filtered.getReverseIterator(this.index_);
6535 }
6536 else {
6537 iterator = filtered.getIterator(this.index_);
6538 }
6539 var count = 0;
6540 while (iterator.hasNext()) {
6541 var next = iterator.getNext();
6542 var inRange = count < this.limit_ &&
6543 this.withinDirectionalStart(next) &&
6544 this.withinDirectionalEnd(next);
6545 if (inRange) {
6546 count++;
6547 }
6548 else {
6549 filtered = filtered.updateImmediateChild(next.name, ChildrenNode.EMPTY_NODE);
6550 }
6551 }
6552 }
6553 }
6554 return this.rangedFilter_
6555 .getIndexedFilter()
6556 .updateFullNode(oldSnap, filtered, optChangeAccumulator);
6557 };
6558 LimitedFilter.prototype.updatePriority = function (oldSnap, newPriority) {
6559 // Don't support priorities on queries
6560 return oldSnap;
6561 };
6562 LimitedFilter.prototype.filtersNodes = function () {
6563 return true;
6564 };
6565 LimitedFilter.prototype.getIndexedFilter = function () {
6566 return this.rangedFilter_.getIndexedFilter();
6567 };
6568 LimitedFilter.prototype.getIndex = function () {
6569 return this.index_;
6570 };
6571 LimitedFilter.prototype.fullLimitUpdateChild_ = function (snap, childKey, childSnap, source, changeAccumulator) {
6572 // TODO: rename all cache stuff etc to general snap terminology
6573 var cmp;
6574 if (this.reverse_) {
6575 var indexCmp_1 = this.index_.getCompare();
6576 cmp = function (a, b) { return indexCmp_1(b, a); };
6577 }
6578 else {
6579 cmp = this.index_.getCompare();
6580 }
6581 var oldEventCache = snap;
6582 util.assert(oldEventCache.numChildren() === this.limit_, '');
6583 var newChildNamedNode = new NamedNode(childKey, childSnap);
6584 var windowBoundary = this.reverse_
6585 ? oldEventCache.getFirstChild(this.index_)
6586 : oldEventCache.getLastChild(this.index_);
6587 var inRange = this.rangedFilter_.matches(newChildNamedNode);
6588 if (oldEventCache.hasChild(childKey)) {
6589 var oldChildSnap = oldEventCache.getImmediateChild(childKey);
6590 var nextChild = source.getChildAfterChild(this.index_, windowBoundary, this.reverse_);
6591 while (nextChild != null &&
6592 (nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))) {
6593 // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
6594 // been applied to the limited filter yet. Ignore this next child which will be updated later in
6595 // the limited filter...
6596 nextChild = source.getChildAfterChild(this.index_, nextChild, this.reverse_);
6597 }
6598 var compareNext = nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
6599 var remainsInWindow = inRange && !childSnap.isEmpty() && compareNext >= 0;
6600 if (remainsInWindow) {
6601 if (changeAccumulator != null) {
6602 changeAccumulator.trackChildChange(changeChildChanged(childKey, childSnap, oldChildSnap));
6603 }
6604 return oldEventCache.updateImmediateChild(childKey, childSnap);
6605 }
6606 else {
6607 if (changeAccumulator != null) {
6608 changeAccumulator.trackChildChange(changeChildRemoved(childKey, oldChildSnap));
6609 }
6610 var newEventCache = oldEventCache.updateImmediateChild(childKey, ChildrenNode.EMPTY_NODE);
6611 var nextChildInRange = nextChild != null && this.rangedFilter_.matches(nextChild);
6612 if (nextChildInRange) {
6613 if (changeAccumulator != null) {
6614 changeAccumulator.trackChildChange(changeChildAdded(nextChild.name, nextChild.node));
6615 }
6616 return newEventCache.updateImmediateChild(nextChild.name, nextChild.node);
6617 }
6618 else {
6619 return newEventCache;
6620 }
6621 }
6622 }
6623 else if (childSnap.isEmpty()) {
6624 // we're deleting a node, but it was not in the window, so ignore it
6625 return snap;
6626 }
6627 else if (inRange) {
6628 if (cmp(windowBoundary, newChildNamedNode) >= 0) {
6629 if (changeAccumulator != null) {
6630 changeAccumulator.trackChildChange(changeChildRemoved(windowBoundary.name, windowBoundary.node));
6631 changeAccumulator.trackChildChange(changeChildAdded(childKey, childSnap));
6632 }
6633 return oldEventCache
6634 .updateImmediateChild(childKey, childSnap)
6635 .updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
6636 }
6637 else {
6638 return snap;
6639 }
6640 }
6641 else {
6642 return snap;
6643 }
6644 };
6645 return LimitedFilter;
6646}());
6647
6648/**
6649 * @license
6650 * Copyright 2017 Google LLC
6651 *
6652 * Licensed under the Apache License, Version 2.0 (the "License");
6653 * you may not use this file except in compliance with the License.
6654 * You may obtain a copy of the License at
6655 *
6656 * http://www.apache.org/licenses/LICENSE-2.0
6657 *
6658 * Unless required by applicable law or agreed to in writing, software
6659 * distributed under the License is distributed on an "AS IS" BASIS,
6660 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6661 * See the License for the specific language governing permissions and
6662 * limitations under the License.
6663 */
6664/**
6665 * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a
6666 * range to be returned for a particular location. It is assumed that validation of parameters is done at the
6667 * user-facing API level, so it is not done here.
6668 *
6669 * @internal
6670 */
6671var QueryParams = /** @class */ (function () {
6672 function QueryParams() {
6673 this.limitSet_ = false;
6674 this.startSet_ = false;
6675 this.startNameSet_ = false;
6676 this.startAfterSet_ = false; // can only be true if startSet_ is true
6677 this.endSet_ = false;
6678 this.endNameSet_ = false;
6679 this.endBeforeSet_ = false; // can only be true if endSet_ is true
6680 this.limit_ = 0;
6681 this.viewFrom_ = '';
6682 this.indexStartValue_ = null;
6683 this.indexStartName_ = '';
6684 this.indexEndValue_ = null;
6685 this.indexEndName_ = '';
6686 this.index_ = PRIORITY_INDEX;
6687 }
6688 QueryParams.prototype.hasStart = function () {
6689 return this.startSet_;
6690 };
6691 /**
6692 * @returns True if it would return from left.
6693 */
6694 QueryParams.prototype.isViewFromLeft = function () {
6695 if (this.viewFrom_ === '') {
6696 // limit(), rather than limitToFirst or limitToLast was called.
6697 // This means that only one of startSet_ and endSet_ is true. Use them
6698 // to calculate which side of the view to anchor to. If neither is set,
6699 // anchor to the end.
6700 return this.startSet_;
6701 }
6702 else {
6703 return this.viewFrom_ === "l" /* WIRE_PROTOCOL_CONSTANTS.VIEW_FROM_LEFT */;
6704 }
6705 };
6706 /**
6707 * Only valid to call if hasStart() returns true
6708 */
6709 QueryParams.prototype.getIndexStartValue = function () {
6710 util.assert(this.startSet_, 'Only valid if start has been set');
6711 return this.indexStartValue_;
6712 };
6713 /**
6714 * Only valid to call if hasStart() returns true.
6715 * Returns the starting key name for the range defined by these query parameters
6716 */
6717 QueryParams.prototype.getIndexStartName = function () {
6718 util.assert(this.startSet_, 'Only valid if start has been set');
6719 if (this.startNameSet_) {
6720 return this.indexStartName_;
6721 }
6722 else {
6723 return MIN_NAME;
6724 }
6725 };
6726 QueryParams.prototype.hasEnd = function () {
6727 return this.endSet_;
6728 };
6729 /**
6730 * Only valid to call if hasEnd() returns true.
6731 */
6732 QueryParams.prototype.getIndexEndValue = function () {
6733 util.assert(this.endSet_, 'Only valid if end has been set');
6734 return this.indexEndValue_;
6735 };
6736 /**
6737 * Only valid to call if hasEnd() returns true.
6738 * Returns the end key name for the range defined by these query parameters
6739 */
6740 QueryParams.prototype.getIndexEndName = function () {
6741 util.assert(this.endSet_, 'Only valid if end has been set');
6742 if (this.endNameSet_) {
6743 return this.indexEndName_;
6744 }
6745 else {
6746 return MAX_NAME;
6747 }
6748 };
6749 QueryParams.prototype.hasLimit = function () {
6750 return this.limitSet_;
6751 };
6752 /**
6753 * @returns True if a limit has been set and it has been explicitly anchored
6754 */
6755 QueryParams.prototype.hasAnchoredLimit = function () {
6756 return this.limitSet_ && this.viewFrom_ !== '';
6757 };
6758 /**
6759 * Only valid to call if hasLimit() returns true
6760 */
6761 QueryParams.prototype.getLimit = function () {
6762 util.assert(this.limitSet_, 'Only valid if limit has been set');
6763 return this.limit_;
6764 };
6765 QueryParams.prototype.getIndex = function () {
6766 return this.index_;
6767 };
6768 QueryParams.prototype.loadsAllData = function () {
6769 return !(this.startSet_ || this.endSet_ || this.limitSet_);
6770 };
6771 QueryParams.prototype.isDefault = function () {
6772 return this.loadsAllData() && this.index_ === PRIORITY_INDEX;
6773 };
6774 QueryParams.prototype.copy = function () {
6775 var copy = new QueryParams();
6776 copy.limitSet_ = this.limitSet_;
6777 copy.limit_ = this.limit_;
6778 copy.startSet_ = this.startSet_;
6779 copy.startAfterSet_ = this.startAfterSet_;
6780 copy.indexStartValue_ = this.indexStartValue_;
6781 copy.startNameSet_ = this.startNameSet_;
6782 copy.indexStartName_ = this.indexStartName_;
6783 copy.endSet_ = this.endSet_;
6784 copy.endBeforeSet_ = this.endBeforeSet_;
6785 copy.indexEndValue_ = this.indexEndValue_;
6786 copy.endNameSet_ = this.endNameSet_;
6787 copy.indexEndName_ = this.indexEndName_;
6788 copy.index_ = this.index_;
6789 copy.viewFrom_ = this.viewFrom_;
6790 return copy;
6791 };
6792 return QueryParams;
6793}());
6794function queryParamsGetNodeFilter(queryParams) {
6795 if (queryParams.loadsAllData()) {
6796 return new IndexedFilter(queryParams.getIndex());
6797 }
6798 else if (queryParams.hasLimit()) {
6799 return new LimitedFilter(queryParams);
6800 }
6801 else {
6802 return new RangedFilter(queryParams);
6803 }
6804}
6805function queryParamsLimitToFirst(queryParams, newLimit) {
6806 var newParams = queryParams.copy();
6807 newParams.limitSet_ = true;
6808 newParams.limit_ = newLimit;
6809 newParams.viewFrom_ = "l" /* WIRE_PROTOCOL_CONSTANTS.VIEW_FROM_LEFT */;
6810 return newParams;
6811}
6812function queryParamsLimitToLast(queryParams, newLimit) {
6813 var newParams = queryParams.copy();
6814 newParams.limitSet_ = true;
6815 newParams.limit_ = newLimit;
6816 newParams.viewFrom_ = "r" /* WIRE_PROTOCOL_CONSTANTS.VIEW_FROM_RIGHT */;
6817 return newParams;
6818}
6819function queryParamsStartAt(queryParams, indexValue, key) {
6820 var newParams = queryParams.copy();
6821 newParams.startSet_ = true;
6822 if (indexValue === undefined) {
6823 indexValue = null;
6824 }
6825 newParams.indexStartValue_ = indexValue;
6826 if (key != null) {
6827 newParams.startNameSet_ = true;
6828 newParams.indexStartName_ = key;
6829 }
6830 else {
6831 newParams.startNameSet_ = false;
6832 newParams.indexStartName_ = '';
6833 }
6834 return newParams;
6835}
6836function queryParamsStartAfter(queryParams, indexValue, key) {
6837 var params;
6838 if (queryParams.index_ === KEY_INDEX || !!key) {
6839 params = queryParamsStartAt(queryParams, indexValue, key);
6840 }
6841 else {
6842 params = queryParamsStartAt(queryParams, indexValue, MAX_NAME);
6843 }
6844 params.startAfterSet_ = true;
6845 return params;
6846}
6847function queryParamsEndAt(queryParams, indexValue, key) {
6848 var newParams = queryParams.copy();
6849 newParams.endSet_ = true;
6850 if (indexValue === undefined) {
6851 indexValue = null;
6852 }
6853 newParams.indexEndValue_ = indexValue;
6854 if (key !== undefined) {
6855 newParams.endNameSet_ = true;
6856 newParams.indexEndName_ = key;
6857 }
6858 else {
6859 newParams.endNameSet_ = false;
6860 newParams.indexEndName_ = '';
6861 }
6862 return newParams;
6863}
6864function queryParamsEndBefore(queryParams, indexValue, key) {
6865 var params;
6866 if (queryParams.index_ === KEY_INDEX || !!key) {
6867 params = queryParamsEndAt(queryParams, indexValue, key);
6868 }
6869 else {
6870 params = queryParamsEndAt(queryParams, indexValue, MIN_NAME);
6871 }
6872 params.endBeforeSet_ = true;
6873 return params;
6874}
6875function queryParamsOrderBy(queryParams, index) {
6876 var newParams = queryParams.copy();
6877 newParams.index_ = index;
6878 return newParams;
6879}
6880/**
6881 * Returns a set of REST query string parameters representing this query.
6882 *
6883 * @returns query string parameters
6884 */
6885function queryParamsToRestQueryStringParameters(queryParams) {
6886 var qs = {};
6887 if (queryParams.isDefault()) {
6888 return qs;
6889 }
6890 var orderBy;
6891 if (queryParams.index_ === PRIORITY_INDEX) {
6892 orderBy = "$priority" /* REST_QUERY_CONSTANTS.PRIORITY_INDEX */;
6893 }
6894 else if (queryParams.index_ === VALUE_INDEX) {
6895 orderBy = "$value" /* REST_QUERY_CONSTANTS.VALUE_INDEX */;
6896 }
6897 else if (queryParams.index_ === KEY_INDEX) {
6898 orderBy = "$key" /* REST_QUERY_CONSTANTS.KEY_INDEX */;
6899 }
6900 else {
6901 util.assert(queryParams.index_ instanceof PathIndex, 'Unrecognized index type!');
6902 orderBy = queryParams.index_.toString();
6903 }
6904 qs["orderBy" /* REST_QUERY_CONSTANTS.ORDER_BY */] = util.stringify(orderBy);
6905 if (queryParams.startSet_) {
6906 var startParam = queryParams.startAfterSet_
6907 ? "startAfter" /* REST_QUERY_CONSTANTS.START_AFTER */
6908 : "startAt" /* REST_QUERY_CONSTANTS.START_AT */;
6909 qs[startParam] = util.stringify(queryParams.indexStartValue_);
6910 if (queryParams.startNameSet_) {
6911 qs[startParam] += ',' + util.stringify(queryParams.indexStartName_);
6912 }
6913 }
6914 if (queryParams.endSet_) {
6915 var endParam = queryParams.endBeforeSet_
6916 ? "endBefore" /* REST_QUERY_CONSTANTS.END_BEFORE */
6917 : "endAt" /* REST_QUERY_CONSTANTS.END_AT */;
6918 qs[endParam] = util.stringify(queryParams.indexEndValue_);
6919 if (queryParams.endNameSet_) {
6920 qs[endParam] += ',' + util.stringify(queryParams.indexEndName_);
6921 }
6922 }
6923 if (queryParams.limitSet_) {
6924 if (queryParams.isViewFromLeft()) {
6925 qs["limitToFirst" /* REST_QUERY_CONSTANTS.LIMIT_TO_FIRST */] = queryParams.limit_;
6926 }
6927 else {
6928 qs["limitToLast" /* REST_QUERY_CONSTANTS.LIMIT_TO_LAST */] = queryParams.limit_;
6929 }
6930 }
6931 return qs;
6932}
6933function queryParamsGetQueryObject(queryParams) {
6934 var obj = {};
6935 if (queryParams.startSet_) {
6936 obj["sp" /* WIRE_PROTOCOL_CONSTANTS.INDEX_START_VALUE */] =
6937 queryParams.indexStartValue_;
6938 if (queryParams.startNameSet_) {
6939 obj["sn" /* WIRE_PROTOCOL_CONSTANTS.INDEX_START_NAME */] =
6940 queryParams.indexStartName_;
6941 }
6942 obj["sin" /* WIRE_PROTOCOL_CONSTANTS.INDEX_START_IS_INCLUSIVE */] =
6943 !queryParams.startAfterSet_;
6944 }
6945 if (queryParams.endSet_) {
6946 obj["ep" /* WIRE_PROTOCOL_CONSTANTS.INDEX_END_VALUE */] = queryParams.indexEndValue_;
6947 if (queryParams.endNameSet_) {
6948 obj["en" /* WIRE_PROTOCOL_CONSTANTS.INDEX_END_NAME */] = queryParams.indexEndName_;
6949 }
6950 obj["ein" /* WIRE_PROTOCOL_CONSTANTS.INDEX_END_IS_INCLUSIVE */] =
6951 !queryParams.endBeforeSet_;
6952 }
6953 if (queryParams.limitSet_) {
6954 obj["l" /* WIRE_PROTOCOL_CONSTANTS.LIMIT */] = queryParams.limit_;
6955 var viewFrom = queryParams.viewFrom_;
6956 if (viewFrom === '') {
6957 if (queryParams.isViewFromLeft()) {
6958 viewFrom = "l" /* WIRE_PROTOCOL_CONSTANTS.VIEW_FROM_LEFT */;
6959 }
6960 else {
6961 viewFrom = "r" /* WIRE_PROTOCOL_CONSTANTS.VIEW_FROM_RIGHT */;
6962 }
6963 }
6964 obj["vf" /* WIRE_PROTOCOL_CONSTANTS.VIEW_FROM */] = viewFrom;
6965 }
6966 // For now, priority index is the default, so we only specify if it's some other index
6967 if (queryParams.index_ !== PRIORITY_INDEX) {
6968 obj["i" /* WIRE_PROTOCOL_CONSTANTS.INDEX */] = queryParams.index_.toString();
6969 }
6970 return obj;
6971}
6972
6973/**
6974 * @license
6975 * Copyright 2017 Google LLC
6976 *
6977 * Licensed under the Apache License, Version 2.0 (the "License");
6978 * you may not use this file except in compliance with the License.
6979 * You may obtain a copy of the License at
6980 *
6981 * http://www.apache.org/licenses/LICENSE-2.0
6982 *
6983 * Unless required by applicable law or agreed to in writing, software
6984 * distributed under the License is distributed on an "AS IS" BASIS,
6985 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6986 * See the License for the specific language governing permissions and
6987 * limitations under the License.
6988 */
6989/**
6990 * An implementation of ServerActions that communicates with the server via REST requests.
6991 * This is mostly useful for compatibility with crawlers, where we don't want to spin up a full
6992 * persistent connection (using WebSockets or long-polling)
6993 */
6994var ReadonlyRestClient = /** @class */ (function (_super) {
6995 tslib.__extends(ReadonlyRestClient, _super);
6996 /**
6997 * @param repoInfo_ - Data about the namespace we are connecting to
6998 * @param onDataUpdate_ - A callback for new data from the server
6999 */
7000 function ReadonlyRestClient(repoInfo_, onDataUpdate_, authTokenProvider_, appCheckTokenProvider_) {
7001 var _this = _super.call(this) || this;
7002 _this.repoInfo_ = repoInfo_;
7003 _this.onDataUpdate_ = onDataUpdate_;
7004 _this.authTokenProvider_ = authTokenProvider_;
7005 _this.appCheckTokenProvider_ = appCheckTokenProvider_;
7006 /** @private {function(...[*])} */
7007 _this.log_ = logWrapper('p:rest:');
7008 /**
7009 * We don't actually need to track listens, except to prevent us calling an onComplete for a listen
7010 * that's been removed. :-/
7011 */
7012 _this.listens_ = {};
7013 return _this;
7014 }
7015 ReadonlyRestClient.prototype.reportStats = function (stats) {
7016 throw new Error('Method not implemented.');
7017 };
7018 ReadonlyRestClient.getListenId_ = function (query, tag) {
7019 if (tag !== undefined) {
7020 return 'tag$' + tag;
7021 }
7022 else {
7023 util.assert(query._queryParams.isDefault(), "should have a tag if it's not a default query.");
7024 return query._path.toString();
7025 }
7026 };
7027 /** @inheritDoc */
7028 ReadonlyRestClient.prototype.listen = function (query, currentHashFn, tag, onComplete) {
7029 var _this = this;
7030 var pathString = query._path.toString();
7031 this.log_('Listen called for ' + pathString + ' ' + query._queryIdentifier);
7032 // Mark this listener so we can tell if it's removed.
7033 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7034 var thisListen = {};
7035 this.listens_[listenId] = thisListen;
7036 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7037 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7038 var data = result;
7039 if (error === 404) {
7040 data = null;
7041 error = null;
7042 }
7043 if (error === null) {
7044 _this.onDataUpdate_(pathString, data, /*isMerge=*/ false, tag);
7045 }
7046 if (util.safeGet(_this.listens_, listenId) === thisListen) {
7047 var status_1;
7048 if (!error) {
7049 status_1 = 'ok';
7050 }
7051 else if (error === 401) {
7052 status_1 = 'permission_denied';
7053 }
7054 else {
7055 status_1 = 'rest_error:' + error;
7056 }
7057 onComplete(status_1, null);
7058 }
7059 });
7060 };
7061 /** @inheritDoc */
7062 ReadonlyRestClient.prototype.unlisten = function (query, tag) {
7063 var listenId = ReadonlyRestClient.getListenId_(query, tag);
7064 delete this.listens_[listenId];
7065 };
7066 ReadonlyRestClient.prototype.get = function (query) {
7067 var _this = this;
7068 var queryStringParameters = queryParamsToRestQueryStringParameters(query._queryParams);
7069 var pathString = query._path.toString();
7070 var deferred = new util.Deferred();
7071 this.restRequest_(pathString + '.json', queryStringParameters, function (error, result) {
7072 var data = result;
7073 if (error === 404) {
7074 data = null;
7075 error = null;
7076 }
7077 if (error === null) {
7078 _this.onDataUpdate_(pathString, data,
7079 /*isMerge=*/ false,
7080 /*tag=*/ null);
7081 deferred.resolve(data);
7082 }
7083 else {
7084 deferred.reject(new Error(data));
7085 }
7086 });
7087 return deferred.promise;
7088 };
7089 /** @inheritDoc */
7090 ReadonlyRestClient.prototype.refreshAuthToken = function (token) {
7091 // no-op since we just always call getToken.
7092 };
7093 /**
7094 * Performs a REST request to the given path, with the provided query string parameters,
7095 * and any auth credentials we have.
7096 */
7097 ReadonlyRestClient.prototype.restRequest_ = function (pathString, queryStringParameters, callback) {
7098 var _this = this;
7099 if (queryStringParameters === void 0) { queryStringParameters = {}; }
7100 queryStringParameters['format'] = 'export';
7101 return Promise.all([
7102 this.authTokenProvider_.getToken(/*forceRefresh=*/ false),
7103 this.appCheckTokenProvider_.getToken(/*forceRefresh=*/ false)
7104 ]).then(function (_a) {
7105 var _b = tslib.__read(_a, 2), authToken = _b[0], appCheckToken = _b[1];
7106 if (authToken && authToken.accessToken) {
7107 queryStringParameters['auth'] = authToken.accessToken;
7108 }
7109 if (appCheckToken && appCheckToken.token) {
7110 queryStringParameters['ac'] = appCheckToken.token;
7111 }
7112 var url = (_this.repoInfo_.secure ? 'https://' : 'http://') +
7113 _this.repoInfo_.host +
7114 pathString +
7115 '?' +
7116 'ns=' +
7117 _this.repoInfo_.namespace +
7118 util.querystring(queryStringParameters);
7119 _this.log_('Sending REST request for ' + url);
7120 var xhr = new XMLHttpRequest();
7121 xhr.onreadystatechange = function () {
7122 if (callback && xhr.readyState === 4) {
7123 _this.log_('REST Response for ' + url + ' received. status:', xhr.status, 'response:', xhr.responseText);
7124 var res = null;
7125 if (xhr.status >= 200 && xhr.status < 300) {
7126 try {
7127 res = util.jsonEval(xhr.responseText);
7128 }
7129 catch (e) {
7130 warn('Failed to parse JSON response for ' +
7131 url +
7132 ': ' +
7133 xhr.responseText);
7134 }
7135 callback(null, res);
7136 }
7137 else {
7138 // 401 and 404 are expected.
7139 if (xhr.status !== 401 && xhr.status !== 404) {
7140 warn('Got unsuccessful REST response for ' +
7141 url +
7142 ' Status: ' +
7143 xhr.status);
7144 }
7145 callback(xhr.status);
7146 }
7147 callback = null;
7148 }
7149 };
7150 xhr.open('GET', url, /*asynchronous=*/ true);
7151 xhr.send();
7152 });
7153 };
7154 return ReadonlyRestClient;
7155}(ServerActions));
7156
7157/**
7158 * @license
7159 * Copyright 2017 Google LLC
7160 *
7161 * Licensed under the Apache License, Version 2.0 (the "License");
7162 * you may not use this file except in compliance with the License.
7163 * You may obtain a copy of the License at
7164 *
7165 * http://www.apache.org/licenses/LICENSE-2.0
7166 *
7167 * Unless required by applicable law or agreed to in writing, software
7168 * distributed under the License is distributed on an "AS IS" BASIS,
7169 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7170 * See the License for the specific language governing permissions and
7171 * limitations under the License.
7172 */
7173/**
7174 * Mutable object which basically just stores a reference to the "latest" immutable snapshot.
7175 */
7176var SnapshotHolder = /** @class */ (function () {
7177 function SnapshotHolder() {
7178 this.rootNode_ = ChildrenNode.EMPTY_NODE;
7179 }
7180 SnapshotHolder.prototype.getNode = function (path) {
7181 return this.rootNode_.getChild(path);
7182 };
7183 SnapshotHolder.prototype.updateSnapshot = function (path, newSnapshotNode) {
7184 this.rootNode_ = this.rootNode_.updateChild(path, newSnapshotNode);
7185 };
7186 return SnapshotHolder;
7187}());
7188
7189/**
7190 * @license
7191 * Copyright 2017 Google LLC
7192 *
7193 * Licensed under the Apache License, Version 2.0 (the "License");
7194 * you may not use this file except in compliance with the License.
7195 * You may obtain a copy of the License at
7196 *
7197 * http://www.apache.org/licenses/LICENSE-2.0
7198 *
7199 * Unless required by applicable law or agreed to in writing, software
7200 * distributed under the License is distributed on an "AS IS" BASIS,
7201 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7202 * See the License for the specific language governing permissions and
7203 * limitations under the License.
7204 */
7205function newSparseSnapshotTree() {
7206 return {
7207 value: null,
7208 children: new Map()
7209 };
7210}
7211/**
7212 * Stores the given node at the specified path. If there is already a node
7213 * at a shallower path, it merges the new data into that snapshot node.
7214 *
7215 * @param path - Path to look up snapshot for.
7216 * @param data - The new data, or null.
7217 */
7218function sparseSnapshotTreeRemember(sparseSnapshotTree, path, data) {
7219 if (pathIsEmpty(path)) {
7220 sparseSnapshotTree.value = data;
7221 sparseSnapshotTree.children.clear();
7222 }
7223 else if (sparseSnapshotTree.value !== null) {
7224 sparseSnapshotTree.value = sparseSnapshotTree.value.updateChild(path, data);
7225 }
7226 else {
7227 var childKey = pathGetFront(path);
7228 if (!sparseSnapshotTree.children.has(childKey)) {
7229 sparseSnapshotTree.children.set(childKey, newSparseSnapshotTree());
7230 }
7231 var child = sparseSnapshotTree.children.get(childKey);
7232 path = pathPopFront(path);
7233 sparseSnapshotTreeRemember(child, path, data);
7234 }
7235}
7236/**
7237 * Purge the data at path from the cache.
7238 *
7239 * @param path - Path to look up snapshot for.
7240 * @returns True if this node should now be removed.
7241 */
7242function sparseSnapshotTreeForget(sparseSnapshotTree, path) {
7243 if (pathIsEmpty(path)) {
7244 sparseSnapshotTree.value = null;
7245 sparseSnapshotTree.children.clear();
7246 return true;
7247 }
7248 else {
7249 if (sparseSnapshotTree.value !== null) {
7250 if (sparseSnapshotTree.value.isLeafNode()) {
7251 // We're trying to forget a node that doesn't exist
7252 return false;
7253 }
7254 else {
7255 var value = sparseSnapshotTree.value;
7256 sparseSnapshotTree.value = null;
7257 value.forEachChild(PRIORITY_INDEX, function (key, tree) {
7258 sparseSnapshotTreeRemember(sparseSnapshotTree, new Path(key), tree);
7259 });
7260 return sparseSnapshotTreeForget(sparseSnapshotTree, path);
7261 }
7262 }
7263 else if (sparseSnapshotTree.children.size > 0) {
7264 var childKey = pathGetFront(path);
7265 path = pathPopFront(path);
7266 if (sparseSnapshotTree.children.has(childKey)) {
7267 var safeToRemove = sparseSnapshotTreeForget(sparseSnapshotTree.children.get(childKey), path);
7268 if (safeToRemove) {
7269 sparseSnapshotTree.children.delete(childKey);
7270 }
7271 }
7272 return sparseSnapshotTree.children.size === 0;
7273 }
7274 else {
7275 return true;
7276 }
7277 }
7278}
7279/**
7280 * Recursively iterates through all of the stored tree and calls the
7281 * callback on each one.
7282 *
7283 * @param prefixPath - Path to look up node for.
7284 * @param func - The function to invoke for each tree.
7285 */
7286function sparseSnapshotTreeForEachTree(sparseSnapshotTree, prefixPath, func) {
7287 if (sparseSnapshotTree.value !== null) {
7288 func(prefixPath, sparseSnapshotTree.value);
7289 }
7290 else {
7291 sparseSnapshotTreeForEachChild(sparseSnapshotTree, function (key, tree) {
7292 var path = new Path(prefixPath.toString() + '/' + key);
7293 sparseSnapshotTreeForEachTree(tree, path, func);
7294 });
7295 }
7296}
7297/**
7298 * Iterates through each immediate child and triggers the callback.
7299 * Only seems to be used in tests.
7300 *
7301 * @param func - The function to invoke for each child.
7302 */
7303function sparseSnapshotTreeForEachChild(sparseSnapshotTree, func) {
7304 sparseSnapshotTree.children.forEach(function (tree, key) {
7305 func(key, tree);
7306 });
7307}
7308
7309/**
7310 * @license
7311 * Copyright 2017 Google LLC
7312 *
7313 * Licensed under the Apache License, Version 2.0 (the "License");
7314 * you may not use this file except in compliance with the License.
7315 * You may obtain a copy of the License at
7316 *
7317 * http://www.apache.org/licenses/LICENSE-2.0
7318 *
7319 * Unless required by applicable law or agreed to in writing, software
7320 * distributed under the License is distributed on an "AS IS" BASIS,
7321 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7322 * See the License for the specific language governing permissions and
7323 * limitations under the License.
7324 */
7325/**
7326 * Returns the delta from the previous call to get stats.
7327 *
7328 * @param collection_ - The collection to "listen" to.
7329 */
7330var StatsListener = /** @class */ (function () {
7331 function StatsListener(collection_) {
7332 this.collection_ = collection_;
7333 this.last_ = null;
7334 }
7335 StatsListener.prototype.get = function () {
7336 var newStats = this.collection_.get();
7337 var delta = tslib.__assign({}, newStats);
7338 if (this.last_) {
7339 each(this.last_, function (stat, value) {
7340 delta[stat] = delta[stat] - value;
7341 });
7342 }
7343 this.last_ = newStats;
7344 return delta;
7345 };
7346 return StatsListener;
7347}());
7348
7349/**
7350 * @license
7351 * Copyright 2017 Google LLC
7352 *
7353 * Licensed under the Apache License, Version 2.0 (the "License");
7354 * you may not use this file except in compliance with the License.
7355 * You may obtain a copy of the License at
7356 *
7357 * http://www.apache.org/licenses/LICENSE-2.0
7358 *
7359 * Unless required by applicable law or agreed to in writing, software
7360 * distributed under the License is distributed on an "AS IS" BASIS,
7361 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7362 * See the License for the specific language governing permissions and
7363 * limitations under the License.
7364 */
7365// Assuming some apps may have a short amount of time on page, and a bulk of firebase operations probably
7366// happen on page load, we try to report our first set of stats pretty quickly, but we wait at least 10
7367// seconds to try to ensure the Firebase connection is established / settled.
7368var FIRST_STATS_MIN_TIME = 10 * 1000;
7369var FIRST_STATS_MAX_TIME = 30 * 1000;
7370// We'll continue to report stats on average every 5 minutes.
7371var REPORT_STATS_INTERVAL = 5 * 60 * 1000;
7372var StatsReporter = /** @class */ (function () {
7373 function StatsReporter(collection, server_) {
7374 this.server_ = server_;
7375 this.statsToReport_ = {};
7376 this.statsListener_ = new StatsListener(collection);
7377 var timeout = FIRST_STATS_MIN_TIME +
7378 (FIRST_STATS_MAX_TIME - FIRST_STATS_MIN_TIME) * Math.random();
7379 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(timeout));
7380 }
7381 StatsReporter.prototype.reportStats_ = function () {
7382 var _this = this;
7383 var stats = this.statsListener_.get();
7384 var reportedStats = {};
7385 var haveStatsToReport = false;
7386 each(stats, function (stat, value) {
7387 if (value > 0 && util.contains(_this.statsToReport_, stat)) {
7388 reportedStats[stat] = value;
7389 haveStatsToReport = true;
7390 }
7391 });
7392 if (haveStatsToReport) {
7393 this.server_.reportStats(reportedStats);
7394 }
7395 // queue our next run.
7396 setTimeoutNonBlocking(this.reportStats_.bind(this), Math.floor(Math.random() * 2 * REPORT_STATS_INTERVAL));
7397 };
7398 return StatsReporter;
7399}());
7400
7401/**
7402 * @license
7403 * Copyright 2017 Google LLC
7404 *
7405 * Licensed under the Apache License, Version 2.0 (the "License");
7406 * you may not use this file except in compliance with the License.
7407 * You may obtain a copy of the License at
7408 *
7409 * http://www.apache.org/licenses/LICENSE-2.0
7410 *
7411 * Unless required by applicable law or agreed to in writing, software
7412 * distributed under the License is distributed on an "AS IS" BASIS,
7413 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7414 * See the License for the specific language governing permissions and
7415 * limitations under the License.
7416 */
7417/**
7418 *
7419 * @enum
7420 */
7421var OperationType;
7422(function (OperationType) {
7423 OperationType[OperationType["OVERWRITE"] = 0] = "OVERWRITE";
7424 OperationType[OperationType["MERGE"] = 1] = "MERGE";
7425 OperationType[OperationType["ACK_USER_WRITE"] = 2] = "ACK_USER_WRITE";
7426 OperationType[OperationType["LISTEN_COMPLETE"] = 3] = "LISTEN_COMPLETE";
7427})(OperationType || (OperationType = {}));
7428function newOperationSourceUser() {
7429 return {
7430 fromUser: true,
7431 fromServer: false,
7432 queryId: null,
7433 tagged: false
7434 };
7435}
7436function newOperationSourceServer() {
7437 return {
7438 fromUser: false,
7439 fromServer: true,
7440 queryId: null,
7441 tagged: false
7442 };
7443}
7444function newOperationSourceServerTaggedQuery(queryId) {
7445 return {
7446 fromUser: false,
7447 fromServer: true,
7448 queryId: queryId,
7449 tagged: true
7450 };
7451}
7452
7453/**
7454 * @license
7455 * Copyright 2017 Google LLC
7456 *
7457 * Licensed under the Apache License, Version 2.0 (the "License");
7458 * you may not use this file except in compliance with the License.
7459 * You may obtain a copy of the License at
7460 *
7461 * http://www.apache.org/licenses/LICENSE-2.0
7462 *
7463 * Unless required by applicable law or agreed to in writing, software
7464 * distributed under the License is distributed on an "AS IS" BASIS,
7465 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7466 * See the License for the specific language governing permissions and
7467 * limitations under the License.
7468 */
7469var AckUserWrite = /** @class */ (function () {
7470 /**
7471 * @param affectedTree - A tree containing true for each affected path. Affected paths can't overlap.
7472 */
7473 function AckUserWrite(
7474 /** @inheritDoc */ path,
7475 /** @inheritDoc */ affectedTree,
7476 /** @inheritDoc */ revert) {
7477 this.path = path;
7478 this.affectedTree = affectedTree;
7479 this.revert = revert;
7480 /** @inheritDoc */
7481 this.type = OperationType.ACK_USER_WRITE;
7482 /** @inheritDoc */
7483 this.source = newOperationSourceUser();
7484 }
7485 AckUserWrite.prototype.operationForChild = function (childName) {
7486 if (!pathIsEmpty(this.path)) {
7487 util.assert(pathGetFront(this.path) === childName, 'operationForChild called for unrelated child.');
7488 return new AckUserWrite(pathPopFront(this.path), this.affectedTree, this.revert);
7489 }
7490 else if (this.affectedTree.value != null) {
7491 util.assert(this.affectedTree.children.isEmpty(), 'affectedTree should not have overlapping affected paths.');
7492 // All child locations are affected as well; just return same operation.
7493 return this;
7494 }
7495 else {
7496 var childTree = this.affectedTree.subtree(new Path(childName));
7497 return new AckUserWrite(newEmptyPath(), childTree, this.revert);
7498 }
7499 };
7500 return AckUserWrite;
7501}());
7502
7503/**
7504 * @license
7505 * Copyright 2017 Google LLC
7506 *
7507 * Licensed under the Apache License, Version 2.0 (the "License");
7508 * you may not use this file except in compliance with the License.
7509 * You may obtain a copy of the License at
7510 *
7511 * http://www.apache.org/licenses/LICENSE-2.0
7512 *
7513 * Unless required by applicable law or agreed to in writing, software
7514 * distributed under the License is distributed on an "AS IS" BASIS,
7515 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7516 * See the License for the specific language governing permissions and
7517 * limitations under the License.
7518 */
7519var ListenComplete = /** @class */ (function () {
7520 function ListenComplete(source, path) {
7521 this.source = source;
7522 this.path = path;
7523 /** @inheritDoc */
7524 this.type = OperationType.LISTEN_COMPLETE;
7525 }
7526 ListenComplete.prototype.operationForChild = function (childName) {
7527 if (pathIsEmpty(this.path)) {
7528 return new ListenComplete(this.source, newEmptyPath());
7529 }
7530 else {
7531 return new ListenComplete(this.source, pathPopFront(this.path));
7532 }
7533 };
7534 return ListenComplete;
7535}());
7536
7537/**
7538 * @license
7539 * Copyright 2017 Google LLC
7540 *
7541 * Licensed under the Apache License, Version 2.0 (the "License");
7542 * you may not use this file except in compliance with the License.
7543 * You may obtain a copy of the License at
7544 *
7545 * http://www.apache.org/licenses/LICENSE-2.0
7546 *
7547 * Unless required by applicable law or agreed to in writing, software
7548 * distributed under the License is distributed on an "AS IS" BASIS,
7549 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7550 * See the License for the specific language governing permissions and
7551 * limitations under the License.
7552 */
7553var Overwrite = /** @class */ (function () {
7554 function Overwrite(source, path, snap) {
7555 this.source = source;
7556 this.path = path;
7557 this.snap = snap;
7558 /** @inheritDoc */
7559 this.type = OperationType.OVERWRITE;
7560 }
7561 Overwrite.prototype.operationForChild = function (childName) {
7562 if (pathIsEmpty(this.path)) {
7563 return new Overwrite(this.source, newEmptyPath(), this.snap.getImmediateChild(childName));
7564 }
7565 else {
7566 return new Overwrite(this.source, pathPopFront(this.path), this.snap);
7567 }
7568 };
7569 return Overwrite;
7570}());
7571
7572/**
7573 * @license
7574 * Copyright 2017 Google LLC
7575 *
7576 * Licensed under the Apache License, Version 2.0 (the "License");
7577 * you may not use this file except in compliance with the License.
7578 * You may obtain a copy of the License at
7579 *
7580 * http://www.apache.org/licenses/LICENSE-2.0
7581 *
7582 * Unless required by applicable law or agreed to in writing, software
7583 * distributed under the License is distributed on an "AS IS" BASIS,
7584 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7585 * See the License for the specific language governing permissions and
7586 * limitations under the License.
7587 */
7588var Merge = /** @class */ (function () {
7589 function Merge(
7590 /** @inheritDoc */ source,
7591 /** @inheritDoc */ path,
7592 /** @inheritDoc */ children) {
7593 this.source = source;
7594 this.path = path;
7595 this.children = children;
7596 /** @inheritDoc */
7597 this.type = OperationType.MERGE;
7598 }
7599 Merge.prototype.operationForChild = function (childName) {
7600 if (pathIsEmpty(this.path)) {
7601 var childTree = this.children.subtree(new Path(childName));
7602 if (childTree.isEmpty()) {
7603 // This child is unaffected
7604 return null;
7605 }
7606 else if (childTree.value) {
7607 // We have a snapshot for the child in question. This becomes an overwrite of the child.
7608 return new Overwrite(this.source, newEmptyPath(), childTree.value);
7609 }
7610 else {
7611 // This is a merge at a deeper level
7612 return new Merge(this.source, newEmptyPath(), childTree);
7613 }
7614 }
7615 else {
7616 util.assert(pathGetFront(this.path) === childName, "Can't get a merge for a child not on the path of the operation");
7617 return new Merge(this.source, pathPopFront(this.path), this.children);
7618 }
7619 };
7620 Merge.prototype.toString = function () {
7621 return ('Operation(' +
7622 this.path +
7623 ': ' +
7624 this.source.toString() +
7625 ' merge: ' +
7626 this.children.toString() +
7627 ')');
7628 };
7629 return Merge;
7630}());
7631
7632/**
7633 * @license
7634 * Copyright 2017 Google LLC
7635 *
7636 * Licensed under the Apache License, Version 2.0 (the "License");
7637 * you may not use this file except in compliance with the License.
7638 * You may obtain a copy of the License at
7639 *
7640 * http://www.apache.org/licenses/LICENSE-2.0
7641 *
7642 * Unless required by applicable law or agreed to in writing, software
7643 * distributed under the License is distributed on an "AS IS" BASIS,
7644 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7645 * See the License for the specific language governing permissions and
7646 * limitations under the License.
7647 */
7648/**
7649 * A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
7650 * initialized in the sense that we know at one point in time this represented a valid state of the world, e.g.
7651 * initialized with data from the server, or a complete overwrite by the client. The filtered flag also tracks
7652 * whether a node potentially had children removed due to a filter.
7653 */
7654var CacheNode = /** @class */ (function () {
7655 function CacheNode(node_, fullyInitialized_, filtered_) {
7656 this.node_ = node_;
7657 this.fullyInitialized_ = fullyInitialized_;
7658 this.filtered_ = filtered_;
7659 }
7660 /**
7661 * Returns whether this node was fully initialized with either server data or a complete overwrite by the client
7662 */
7663 CacheNode.prototype.isFullyInitialized = function () {
7664 return this.fullyInitialized_;
7665 };
7666 /**
7667 * Returns whether this node is potentially missing children due to a filter applied to the node
7668 */
7669 CacheNode.prototype.isFiltered = function () {
7670 return this.filtered_;
7671 };
7672 CacheNode.prototype.isCompleteForPath = function (path) {
7673 if (pathIsEmpty(path)) {
7674 return this.isFullyInitialized() && !this.filtered_;
7675 }
7676 var childKey = pathGetFront(path);
7677 return this.isCompleteForChild(childKey);
7678 };
7679 CacheNode.prototype.isCompleteForChild = function (key) {
7680 return ((this.isFullyInitialized() && !this.filtered_) || this.node_.hasChild(key));
7681 };
7682 CacheNode.prototype.getNode = function () {
7683 return this.node_;
7684 };
7685 return CacheNode;
7686}());
7687
7688/**
7689 * @license
7690 * Copyright 2017 Google LLC
7691 *
7692 * Licensed under the Apache License, Version 2.0 (the "License");
7693 * you may not use this file except in compliance with the License.
7694 * You may obtain a copy of the License at
7695 *
7696 * http://www.apache.org/licenses/LICENSE-2.0
7697 *
7698 * Unless required by applicable law or agreed to in writing, software
7699 * distributed under the License is distributed on an "AS IS" BASIS,
7700 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7701 * See the License for the specific language governing permissions and
7702 * limitations under the License.
7703 */
7704/**
7705 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
7706 * CacheDiffer into actual events (Event) that can be raised. See generateEventsForChanges()
7707 * for details.
7708 *
7709 */
7710var EventGenerator = /** @class */ (function () {
7711 function EventGenerator(query_) {
7712 this.query_ = query_;
7713 this.index_ = this.query_._queryParams.getIndex();
7714 }
7715 return EventGenerator;
7716}());
7717/**
7718 * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
7719 * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
7720 *
7721 * Notes:
7722 * - child_moved events will be synthesized at this time for any child_changed events that affect
7723 * our index.
7724 * - prevName will be calculated based on the index ordering.
7725 */
7726function eventGeneratorGenerateEventsForChanges(eventGenerator, changes, eventCache, eventRegistrations) {
7727 var events = [];
7728 var moves = [];
7729 changes.forEach(function (change) {
7730 if (change.type === "child_changed" /* ChangeType.CHILD_CHANGED */ &&
7731 eventGenerator.index_.indexedValueChanged(change.oldSnap, change.snapshotNode)) {
7732 moves.push(changeChildMoved(change.childName, change.snapshotNode));
7733 }
7734 });
7735 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_removed" /* ChangeType.CHILD_REMOVED */, changes, eventRegistrations, eventCache);
7736 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_added" /* ChangeType.CHILD_ADDED */, changes, eventRegistrations, eventCache);
7737 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_moved" /* ChangeType.CHILD_MOVED */, moves, eventRegistrations, eventCache);
7738 eventGeneratorGenerateEventsForType(eventGenerator, events, "child_changed" /* ChangeType.CHILD_CHANGED */, changes, eventRegistrations, eventCache);
7739 eventGeneratorGenerateEventsForType(eventGenerator, events, "value" /* ChangeType.VALUE */, changes, eventRegistrations, eventCache);
7740 return events;
7741}
7742/**
7743 * Given changes of a single change type, generate the corresponding events.
7744 */
7745function eventGeneratorGenerateEventsForType(eventGenerator, events, eventType, changes, registrations, eventCache) {
7746 var filteredChanges = changes.filter(function (change) { return change.type === eventType; });
7747 filteredChanges.sort(function (a, b) {
7748 return eventGeneratorCompareChanges(eventGenerator, a, b);
7749 });
7750 filteredChanges.forEach(function (change) {
7751 var materializedChange = eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache);
7752 registrations.forEach(function (registration) {
7753 if (registration.respondsTo(change.type)) {
7754 events.push(registration.createEvent(materializedChange, eventGenerator.query_));
7755 }
7756 });
7757 });
7758}
7759function eventGeneratorMaterializeSingleChange(eventGenerator, change, eventCache) {
7760 if (change.type === 'value' || change.type === 'child_removed') {
7761 return change;
7762 }
7763 else {
7764 change.prevName = eventCache.getPredecessorChildName(change.childName, change.snapshotNode, eventGenerator.index_);
7765 return change;
7766 }
7767}
7768function eventGeneratorCompareChanges(eventGenerator, a, b) {
7769 if (a.childName == null || b.childName == null) {
7770 throw util.assertionError('Should only compare child_ events.');
7771 }
7772 var aWrapped = new NamedNode(a.childName, a.snapshotNode);
7773 var bWrapped = new NamedNode(b.childName, b.snapshotNode);
7774 return eventGenerator.index_.compare(aWrapped, bWrapped);
7775}
7776
7777/**
7778 * @license
7779 * Copyright 2017 Google LLC
7780 *
7781 * Licensed under the Apache License, Version 2.0 (the "License");
7782 * you may not use this file except in compliance with the License.
7783 * You may obtain a copy of the License at
7784 *
7785 * http://www.apache.org/licenses/LICENSE-2.0
7786 *
7787 * Unless required by applicable law or agreed to in writing, software
7788 * distributed under the License is distributed on an "AS IS" BASIS,
7789 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7790 * See the License for the specific language governing permissions and
7791 * limitations under the License.
7792 */
7793function newViewCache(eventCache, serverCache) {
7794 return { eventCache: eventCache, serverCache: serverCache };
7795}
7796function viewCacheUpdateEventSnap(viewCache, eventSnap, complete, filtered) {
7797 return newViewCache(new CacheNode(eventSnap, complete, filtered), viewCache.serverCache);
7798}
7799function viewCacheUpdateServerSnap(viewCache, serverSnap, complete, filtered) {
7800 return newViewCache(viewCache.eventCache, new CacheNode(serverSnap, complete, filtered));
7801}
7802function viewCacheGetCompleteEventSnap(viewCache) {
7803 return viewCache.eventCache.isFullyInitialized()
7804 ? viewCache.eventCache.getNode()
7805 : null;
7806}
7807function viewCacheGetCompleteServerSnap(viewCache) {
7808 return viewCache.serverCache.isFullyInitialized()
7809 ? viewCache.serverCache.getNode()
7810 : null;
7811}
7812
7813/**
7814 * @license
7815 * Copyright 2017 Google LLC
7816 *
7817 * Licensed under the Apache License, Version 2.0 (the "License");
7818 * you may not use this file except in compliance with the License.
7819 * You may obtain a copy of the License at
7820 *
7821 * http://www.apache.org/licenses/LICENSE-2.0
7822 *
7823 * Unless required by applicable law or agreed to in writing, software
7824 * distributed under the License is distributed on an "AS IS" BASIS,
7825 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7826 * See the License for the specific language governing permissions and
7827 * limitations under the License.
7828 */
7829var emptyChildrenSingleton;
7830/**
7831 * Singleton empty children collection.
7832 *
7833 */
7834var EmptyChildren = function () {
7835 if (!emptyChildrenSingleton) {
7836 emptyChildrenSingleton = new SortedMap(stringCompare);
7837 }
7838 return emptyChildrenSingleton;
7839};
7840/**
7841 * A tree with immutable elements.
7842 */
7843var ImmutableTree = /** @class */ (function () {
7844 function ImmutableTree(value, children) {
7845 if (children === void 0) { children = EmptyChildren(); }
7846 this.value = value;
7847 this.children = children;
7848 }
7849 ImmutableTree.fromObject = function (obj) {
7850 var tree = new ImmutableTree(null);
7851 each(obj, function (childPath, childSnap) {
7852 tree = tree.set(new Path(childPath), childSnap);
7853 });
7854 return tree;
7855 };
7856 /**
7857 * True if the value is empty and there are no children
7858 */
7859 ImmutableTree.prototype.isEmpty = function () {
7860 return this.value === null && this.children.isEmpty();
7861 };
7862 /**
7863 * Given a path and predicate, return the first node and the path to that node
7864 * where the predicate returns true.
7865 *
7866 * TODO Do a perf test -- If we're creating a bunch of `{path: value:}`
7867 * objects on the way back out, it may be better to pass down a pathSoFar obj.
7868 *
7869 * @param relativePath - The remainder of the path
7870 * @param predicate - The predicate to satisfy to return a node
7871 */
7872 ImmutableTree.prototype.findRootMostMatchingPathAndValue = function (relativePath, predicate) {
7873 if (this.value != null && predicate(this.value)) {
7874 return { path: newEmptyPath(), value: this.value };
7875 }
7876 else {
7877 if (pathIsEmpty(relativePath)) {
7878 return null;
7879 }
7880 else {
7881 var front = pathGetFront(relativePath);
7882 var child = this.children.get(front);
7883 if (child !== null) {
7884 var childExistingPathAndValue = child.findRootMostMatchingPathAndValue(pathPopFront(relativePath), predicate);
7885 if (childExistingPathAndValue != null) {
7886 var fullPath = pathChild(new Path(front), childExistingPathAndValue.path);
7887 return { path: fullPath, value: childExistingPathAndValue.value };
7888 }
7889 else {
7890 return null;
7891 }
7892 }
7893 else {
7894 return null;
7895 }
7896 }
7897 }
7898 };
7899 /**
7900 * Find, if it exists, the shortest subpath of the given path that points a defined
7901 * value in the tree
7902 */
7903 ImmutableTree.prototype.findRootMostValueAndPath = function (relativePath) {
7904 return this.findRootMostMatchingPathAndValue(relativePath, function () { return true; });
7905 };
7906 /**
7907 * @returns The subtree at the given path
7908 */
7909 ImmutableTree.prototype.subtree = function (relativePath) {
7910 if (pathIsEmpty(relativePath)) {
7911 return this;
7912 }
7913 else {
7914 var front = pathGetFront(relativePath);
7915 var childTree = this.children.get(front);
7916 if (childTree !== null) {
7917 return childTree.subtree(pathPopFront(relativePath));
7918 }
7919 else {
7920 return new ImmutableTree(null);
7921 }
7922 }
7923 };
7924 /**
7925 * Sets a value at the specified path.
7926 *
7927 * @param relativePath - Path to set value at.
7928 * @param toSet - Value to set.
7929 * @returns Resulting tree.
7930 */
7931 ImmutableTree.prototype.set = function (relativePath, toSet) {
7932 if (pathIsEmpty(relativePath)) {
7933 return new ImmutableTree(toSet, this.children);
7934 }
7935 else {
7936 var front = pathGetFront(relativePath);
7937 var child = this.children.get(front) || new ImmutableTree(null);
7938 var newChild = child.set(pathPopFront(relativePath), toSet);
7939 var newChildren = this.children.insert(front, newChild);
7940 return new ImmutableTree(this.value, newChildren);
7941 }
7942 };
7943 /**
7944 * Removes the value at the specified path.
7945 *
7946 * @param relativePath - Path to value to remove.
7947 * @returns Resulting tree.
7948 */
7949 ImmutableTree.prototype.remove = function (relativePath) {
7950 if (pathIsEmpty(relativePath)) {
7951 if (this.children.isEmpty()) {
7952 return new ImmutableTree(null);
7953 }
7954 else {
7955 return new ImmutableTree(null, this.children);
7956 }
7957 }
7958 else {
7959 var front = pathGetFront(relativePath);
7960 var child = this.children.get(front);
7961 if (child) {
7962 var newChild = child.remove(pathPopFront(relativePath));
7963 var newChildren = void 0;
7964 if (newChild.isEmpty()) {
7965 newChildren = this.children.remove(front);
7966 }
7967 else {
7968 newChildren = this.children.insert(front, newChild);
7969 }
7970 if (this.value === null && newChildren.isEmpty()) {
7971 return new ImmutableTree(null);
7972 }
7973 else {
7974 return new ImmutableTree(this.value, newChildren);
7975 }
7976 }
7977 else {
7978 return this;
7979 }
7980 }
7981 };
7982 /**
7983 * Gets a value from the tree.
7984 *
7985 * @param relativePath - Path to get value for.
7986 * @returns Value at path, or null.
7987 */
7988 ImmutableTree.prototype.get = function (relativePath) {
7989 if (pathIsEmpty(relativePath)) {
7990 return this.value;
7991 }
7992 else {
7993 var front = pathGetFront(relativePath);
7994 var child = this.children.get(front);
7995 if (child) {
7996 return child.get(pathPopFront(relativePath));
7997 }
7998 else {
7999 return null;
8000 }
8001 }
8002 };
8003 /**
8004 * Replace the subtree at the specified path with the given new tree.
8005 *
8006 * @param relativePath - Path to replace subtree for.
8007 * @param newTree - New tree.
8008 * @returns Resulting tree.
8009 */
8010 ImmutableTree.prototype.setTree = function (relativePath, newTree) {
8011 if (pathIsEmpty(relativePath)) {
8012 return newTree;
8013 }
8014 else {
8015 var front = pathGetFront(relativePath);
8016 var child = this.children.get(front) || new ImmutableTree(null);
8017 var newChild = child.setTree(pathPopFront(relativePath), newTree);
8018 var newChildren = void 0;
8019 if (newChild.isEmpty()) {
8020 newChildren = this.children.remove(front);
8021 }
8022 else {
8023 newChildren = this.children.insert(front, newChild);
8024 }
8025 return new ImmutableTree(this.value, newChildren);
8026 }
8027 };
8028 /**
8029 * Performs a depth first fold on this tree. Transforms a tree into a single
8030 * value, given a function that operates on the path to a node, an optional
8031 * current value, and a map of child names to folded subtrees
8032 */
8033 ImmutableTree.prototype.fold = function (fn) {
8034 return this.fold_(newEmptyPath(), fn);
8035 };
8036 /**
8037 * Recursive helper for public-facing fold() method
8038 */
8039 ImmutableTree.prototype.fold_ = function (pathSoFar, fn) {
8040 var accum = {};
8041 this.children.inorderTraversal(function (childKey, childTree) {
8042 accum[childKey] = childTree.fold_(pathChild(pathSoFar, childKey), fn);
8043 });
8044 return fn(pathSoFar, this.value, accum);
8045 };
8046 /**
8047 * Find the first matching value on the given path. Return the result of applying f to it.
8048 */
8049 ImmutableTree.prototype.findOnPath = function (path, f) {
8050 return this.findOnPath_(path, newEmptyPath(), f);
8051 };
8052 ImmutableTree.prototype.findOnPath_ = function (pathToFollow, pathSoFar, f) {
8053 var result = this.value ? f(pathSoFar, this.value) : false;
8054 if (result) {
8055 return result;
8056 }
8057 else {
8058 if (pathIsEmpty(pathToFollow)) {
8059 return null;
8060 }
8061 else {
8062 var front = pathGetFront(pathToFollow);
8063 var nextChild = this.children.get(front);
8064 if (nextChild) {
8065 return nextChild.findOnPath_(pathPopFront(pathToFollow), pathChild(pathSoFar, front), f);
8066 }
8067 else {
8068 return null;
8069 }
8070 }
8071 }
8072 };
8073 ImmutableTree.prototype.foreachOnPath = function (path, f) {
8074 return this.foreachOnPath_(path, newEmptyPath(), f);
8075 };
8076 ImmutableTree.prototype.foreachOnPath_ = function (pathToFollow, currentRelativePath, f) {
8077 if (pathIsEmpty(pathToFollow)) {
8078 return this;
8079 }
8080 else {
8081 if (this.value) {
8082 f(currentRelativePath, this.value);
8083 }
8084 var front = pathGetFront(pathToFollow);
8085 var nextChild = this.children.get(front);
8086 if (nextChild) {
8087 return nextChild.foreachOnPath_(pathPopFront(pathToFollow), pathChild(currentRelativePath, front), f);
8088 }
8089 else {
8090 return new ImmutableTree(null);
8091 }
8092 }
8093 };
8094 /**
8095 * Calls the given function for each node in the tree that has a value.
8096 *
8097 * @param f - A function to be called with the path from the root of the tree to
8098 * a node, and the value at that node. Called in depth-first order.
8099 */
8100 ImmutableTree.prototype.foreach = function (f) {
8101 this.foreach_(newEmptyPath(), f);
8102 };
8103 ImmutableTree.prototype.foreach_ = function (currentRelativePath, f) {
8104 this.children.inorderTraversal(function (childName, childTree) {
8105 childTree.foreach_(pathChild(currentRelativePath, childName), f);
8106 });
8107 if (this.value) {
8108 f(currentRelativePath, this.value);
8109 }
8110 };
8111 ImmutableTree.prototype.foreachChild = function (f) {
8112 this.children.inorderTraversal(function (childName, childTree) {
8113 if (childTree.value) {
8114 f(childName, childTree.value);
8115 }
8116 });
8117 };
8118 return ImmutableTree;
8119}());
8120
8121/**
8122 * @license
8123 * Copyright 2017 Google LLC
8124 *
8125 * Licensed under the Apache License, Version 2.0 (the "License");
8126 * you may not use this file except in compliance with the License.
8127 * You may obtain a copy of the License at
8128 *
8129 * http://www.apache.org/licenses/LICENSE-2.0
8130 *
8131 * Unless required by applicable law or agreed to in writing, software
8132 * distributed under the License is distributed on an "AS IS" BASIS,
8133 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8134 * See the License for the specific language governing permissions and
8135 * limitations under the License.
8136 */
8137/**
8138 * This class holds a collection of writes that can be applied to nodes in unison. It abstracts away the logic with
8139 * dealing with priority writes and multiple nested writes. At any given path there is only allowed to be one write
8140 * modifying that path. Any write to an existing path or shadowing an existing path will modify that existing write
8141 * to reflect the write added.
8142 */
8143var CompoundWrite = /** @class */ (function () {
8144 function CompoundWrite(writeTree_) {
8145 this.writeTree_ = writeTree_;
8146 }
8147 CompoundWrite.empty = function () {
8148 return new CompoundWrite(new ImmutableTree(null));
8149 };
8150 return CompoundWrite;
8151}());
8152function compoundWriteAddWrite(compoundWrite, path, node) {
8153 if (pathIsEmpty(path)) {
8154 return new CompoundWrite(new ImmutableTree(node));
8155 }
8156 else {
8157 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8158 if (rootmost != null) {
8159 var rootMostPath = rootmost.path;
8160 var value = rootmost.value;
8161 var relativePath = newRelativePath(rootMostPath, path);
8162 value = value.updateChild(relativePath, node);
8163 return new CompoundWrite(compoundWrite.writeTree_.set(rootMostPath, value));
8164 }
8165 else {
8166 var subtree = new ImmutableTree(node);
8167 var newWriteTree = compoundWrite.writeTree_.setTree(path, subtree);
8168 return new CompoundWrite(newWriteTree);
8169 }
8170 }
8171}
8172function compoundWriteAddWrites(compoundWrite, path, updates) {
8173 var newWrite = compoundWrite;
8174 each(updates, function (childKey, node) {
8175 newWrite = compoundWriteAddWrite(newWrite, pathChild(path, childKey), node);
8176 });
8177 return newWrite;
8178}
8179/**
8180 * Will remove a write at the given path and deeper paths. This will <em>not</em> modify a write at a higher
8181 * location, which must be removed by calling this method with that path.
8182 *
8183 * @param compoundWrite - The CompoundWrite to remove.
8184 * @param path - The path at which a write and all deeper writes should be removed
8185 * @returns The new CompoundWrite with the removed path
8186 */
8187function compoundWriteRemoveWrite(compoundWrite, path) {
8188 if (pathIsEmpty(path)) {
8189 return CompoundWrite.empty();
8190 }
8191 else {
8192 var newWriteTree = compoundWrite.writeTree_.setTree(path, new ImmutableTree(null));
8193 return new CompoundWrite(newWriteTree);
8194 }
8195}
8196/**
8197 * Returns whether this CompoundWrite will fully overwrite a node at a given location and can therefore be
8198 * considered "complete".
8199 *
8200 * @param compoundWrite - The CompoundWrite to check.
8201 * @param path - The path to check for
8202 * @returns Whether there is a complete write at that path
8203 */
8204function compoundWriteHasCompleteWrite(compoundWrite, path) {
8205 return compoundWriteGetCompleteNode(compoundWrite, path) != null;
8206}
8207/**
8208 * Returns a node for a path if and only if the node is a "complete" overwrite at that path. This will not aggregate
8209 * writes from deeper paths, but will return child nodes from a more shallow path.
8210 *
8211 * @param compoundWrite - The CompoundWrite to get the node from.
8212 * @param path - The path to get a complete write
8213 * @returns The node if complete at that path, or null otherwise.
8214 */
8215function compoundWriteGetCompleteNode(compoundWrite, path) {
8216 var rootmost = compoundWrite.writeTree_.findRootMostValueAndPath(path);
8217 if (rootmost != null) {
8218 return compoundWrite.writeTree_
8219 .get(rootmost.path)
8220 .getChild(newRelativePath(rootmost.path, path));
8221 }
8222 else {
8223 return null;
8224 }
8225}
8226/**
8227 * Returns all children that are guaranteed to be a complete overwrite.
8228 *
8229 * @param compoundWrite - The CompoundWrite to get children from.
8230 * @returns A list of all complete children.
8231 */
8232function compoundWriteGetCompleteChildren(compoundWrite) {
8233 var children = [];
8234 var node = compoundWrite.writeTree_.value;
8235 if (node != null) {
8236 // If it's a leaf node, it has no children; so nothing to do.
8237 if (!node.isLeafNode()) {
8238 node.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8239 children.push(new NamedNode(childName, childNode));
8240 });
8241 }
8242 }
8243 else {
8244 compoundWrite.writeTree_.children.inorderTraversal(function (childName, childTree) {
8245 if (childTree.value != null) {
8246 children.push(new NamedNode(childName, childTree.value));
8247 }
8248 });
8249 }
8250 return children;
8251}
8252function compoundWriteChildCompoundWrite(compoundWrite, path) {
8253 if (pathIsEmpty(path)) {
8254 return compoundWrite;
8255 }
8256 else {
8257 var shadowingNode = compoundWriteGetCompleteNode(compoundWrite, path);
8258 if (shadowingNode != null) {
8259 return new CompoundWrite(new ImmutableTree(shadowingNode));
8260 }
8261 else {
8262 return new CompoundWrite(compoundWrite.writeTree_.subtree(path));
8263 }
8264 }
8265}
8266/**
8267 * Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
8268 * @returns Whether this CompoundWrite is empty
8269 */
8270function compoundWriteIsEmpty(compoundWrite) {
8271 return compoundWrite.writeTree_.isEmpty();
8272}
8273/**
8274 * Applies this CompoundWrite to a node. The node is returned with all writes from this CompoundWrite applied to the
8275 * node
8276 * @param node - The node to apply this CompoundWrite to
8277 * @returns The node with all writes applied
8278 */
8279function compoundWriteApply(compoundWrite, node) {
8280 return applySubtreeWrite(newEmptyPath(), compoundWrite.writeTree_, node);
8281}
8282function applySubtreeWrite(relativePath, writeTree, node) {
8283 if (writeTree.value != null) {
8284 // Since there a write is always a leaf, we're done here
8285 return node.updateChild(relativePath, writeTree.value);
8286 }
8287 else {
8288 var priorityWrite_1 = null;
8289 writeTree.children.inorderTraversal(function (childKey, childTree) {
8290 if (childKey === '.priority') {
8291 // Apply priorities at the end so we don't update priorities for either empty nodes or forget
8292 // to apply priorities to empty nodes that are later filled
8293 util.assert(childTree.value !== null, 'Priority writes must always be leaf nodes');
8294 priorityWrite_1 = childTree.value;
8295 }
8296 else {
8297 node = applySubtreeWrite(pathChild(relativePath, childKey), childTree, node);
8298 }
8299 });
8300 // If there was a priority write, we only apply it if the node is not empty
8301 if (!node.getChild(relativePath).isEmpty() && priorityWrite_1 !== null) {
8302 node = node.updateChild(pathChild(relativePath, '.priority'), priorityWrite_1);
8303 }
8304 return node;
8305 }
8306}
8307
8308/**
8309 * @license
8310 * Copyright 2017 Google LLC
8311 *
8312 * Licensed under the Apache License, Version 2.0 (the "License");
8313 * you may not use this file except in compliance with the License.
8314 * You may obtain a copy of the License at
8315 *
8316 * http://www.apache.org/licenses/LICENSE-2.0
8317 *
8318 * Unless required by applicable law or agreed to in writing, software
8319 * distributed under the License is distributed on an "AS IS" BASIS,
8320 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8321 * See the License for the specific language governing permissions and
8322 * limitations under the License.
8323 */
8324/**
8325 * Create a new WriteTreeRef for the given path. For use with a new sync point at the given path.
8326 *
8327 */
8328function writeTreeChildWrites(writeTree, path) {
8329 return newWriteTreeRef(path, writeTree);
8330}
8331/**
8332 * Record a new overwrite from user code.
8333 *
8334 * @param visible - This is set to false by some transactions. It should be excluded from event caches
8335 */
8336function writeTreeAddOverwrite(writeTree, path, snap, writeId, visible) {
8337 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older write on top of newer ones');
8338 if (visible === undefined) {
8339 visible = true;
8340 }
8341 writeTree.allWrites.push({
8342 path: path,
8343 snap: snap,
8344 writeId: writeId,
8345 visible: visible
8346 });
8347 if (visible) {
8348 writeTree.visibleWrites = compoundWriteAddWrite(writeTree.visibleWrites, path, snap);
8349 }
8350 writeTree.lastWriteId = writeId;
8351}
8352/**
8353 * Record a new merge from user code.
8354 */
8355function writeTreeAddMerge(writeTree, path, changedChildren, writeId) {
8356 util.assert(writeId > writeTree.lastWriteId, 'Stacking an older merge on top of newer ones');
8357 writeTree.allWrites.push({
8358 path: path,
8359 children: changedChildren,
8360 writeId: writeId,
8361 visible: true
8362 });
8363 writeTree.visibleWrites = compoundWriteAddWrites(writeTree.visibleWrites, path, changedChildren);
8364 writeTree.lastWriteId = writeId;
8365}
8366function writeTreeGetWrite(writeTree, writeId) {
8367 for (var i = 0; i < writeTree.allWrites.length; i++) {
8368 var record = writeTree.allWrites[i];
8369 if (record.writeId === writeId) {
8370 return record;
8371 }
8372 }
8373 return null;
8374}
8375/**
8376 * Remove a write (either an overwrite or merge) that has been successfully acknowledge by the server. Recalculates
8377 * the tree if necessary. We return true if it may have been visible, meaning views need to reevaluate.
8378 *
8379 * @returns true if the write may have been visible (meaning we'll need to reevaluate / raise
8380 * events as a result).
8381 */
8382function writeTreeRemoveWrite(writeTree, writeId) {
8383 // Note: disabling this check. It could be a transaction that preempted another transaction, and thus was applied
8384 // out of order.
8385 //const validClear = revert || this.allWrites_.length === 0 || writeId <= this.allWrites_[0].writeId;
8386 //assert(validClear, "Either we don't have this write, or it's the first one in the queue");
8387 var idx = writeTree.allWrites.findIndex(function (s) {
8388 return s.writeId === writeId;
8389 });
8390 util.assert(idx >= 0, 'removeWrite called with nonexistent writeId.');
8391 var writeToRemove = writeTree.allWrites[idx];
8392 writeTree.allWrites.splice(idx, 1);
8393 var removedWriteWasVisible = writeToRemove.visible;
8394 var removedWriteOverlapsWithOtherWrites = false;
8395 var i = writeTree.allWrites.length - 1;
8396 while (removedWriteWasVisible && i >= 0) {
8397 var currentWrite = writeTree.allWrites[i];
8398 if (currentWrite.visible) {
8399 if (i >= idx &&
8400 writeTreeRecordContainsPath_(currentWrite, writeToRemove.path)) {
8401 // The removed write was completely shadowed by a subsequent write.
8402 removedWriteWasVisible = false;
8403 }
8404 else if (pathContains(writeToRemove.path, currentWrite.path)) {
8405 // Either we're covering some writes or they're covering part of us (depending on which came first).
8406 removedWriteOverlapsWithOtherWrites = true;
8407 }
8408 }
8409 i--;
8410 }
8411 if (!removedWriteWasVisible) {
8412 return false;
8413 }
8414 else if (removedWriteOverlapsWithOtherWrites) {
8415 // There's some shadowing going on. Just rebuild the visible writes from scratch.
8416 writeTreeResetTree_(writeTree);
8417 return true;
8418 }
8419 else {
8420 // There's no shadowing. We can safely just remove the write(s) from visibleWrites.
8421 if (writeToRemove.snap) {
8422 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, writeToRemove.path);
8423 }
8424 else {
8425 var children = writeToRemove.children;
8426 each(children, function (childName) {
8427 writeTree.visibleWrites = compoundWriteRemoveWrite(writeTree.visibleWrites, pathChild(writeToRemove.path, childName));
8428 });
8429 }
8430 return true;
8431 }
8432}
8433function writeTreeRecordContainsPath_(writeRecord, path) {
8434 if (writeRecord.snap) {
8435 return pathContains(writeRecord.path, path);
8436 }
8437 else {
8438 for (var childName in writeRecord.children) {
8439 if (writeRecord.children.hasOwnProperty(childName) &&
8440 pathContains(pathChild(writeRecord.path, childName), path)) {
8441 return true;
8442 }
8443 }
8444 return false;
8445 }
8446}
8447/**
8448 * Re-layer the writes and merges into a tree so we can efficiently calculate event snapshots
8449 */
8450function writeTreeResetTree_(writeTree) {
8451 writeTree.visibleWrites = writeTreeLayerTree_(writeTree.allWrites, writeTreeDefaultFilter_, newEmptyPath());
8452 if (writeTree.allWrites.length > 0) {
8453 writeTree.lastWriteId =
8454 writeTree.allWrites[writeTree.allWrites.length - 1].writeId;
8455 }
8456 else {
8457 writeTree.lastWriteId = -1;
8458 }
8459}
8460/**
8461 * The default filter used when constructing the tree. Keep everything that's visible.
8462 */
8463function writeTreeDefaultFilter_(write) {
8464 return write.visible;
8465}
8466/**
8467 * Static method. Given an array of WriteRecords, a filter for which ones to include, and a path, construct the tree of
8468 * event data at that path.
8469 */
8470function writeTreeLayerTree_(writes, filter, treeRoot) {
8471 var compoundWrite = CompoundWrite.empty();
8472 for (var i = 0; i < writes.length; ++i) {
8473 var write = writes[i];
8474 // Theory, a later set will either:
8475 // a) abort a relevant transaction, so no need to worry about excluding it from calculating that transaction
8476 // b) not be relevant to a transaction (separate branch), so again will not affect the data for that transaction
8477 if (filter(write)) {
8478 var writePath = write.path;
8479 var relativePath = void 0;
8480 if (write.snap) {
8481 if (pathContains(treeRoot, writePath)) {
8482 relativePath = newRelativePath(treeRoot, writePath);
8483 compoundWrite = compoundWriteAddWrite(compoundWrite, relativePath, write.snap);
8484 }
8485 else if (pathContains(writePath, treeRoot)) {
8486 relativePath = newRelativePath(writePath, treeRoot);
8487 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), write.snap.getChild(relativePath));
8488 }
8489 else ;
8490 }
8491 else if (write.children) {
8492 if (pathContains(treeRoot, writePath)) {
8493 relativePath = newRelativePath(treeRoot, writePath);
8494 compoundWrite = compoundWriteAddWrites(compoundWrite, relativePath, write.children);
8495 }
8496 else if (pathContains(writePath, treeRoot)) {
8497 relativePath = newRelativePath(writePath, treeRoot);
8498 if (pathIsEmpty(relativePath)) {
8499 compoundWrite = compoundWriteAddWrites(compoundWrite, newEmptyPath(), write.children);
8500 }
8501 else {
8502 var child = util.safeGet(write.children, pathGetFront(relativePath));
8503 if (child) {
8504 // There exists a child in this node that matches the root path
8505 var deepNode = child.getChild(pathPopFront(relativePath));
8506 compoundWrite = compoundWriteAddWrite(compoundWrite, newEmptyPath(), deepNode);
8507 }
8508 }
8509 }
8510 else ;
8511 }
8512 else {
8513 throw util.assertionError('WriteRecord should have .snap or .children');
8514 }
8515 }
8516 }
8517 return compoundWrite;
8518}
8519/**
8520 * Given optional, underlying server data, and an optional set of constraints (exclude some sets, include hidden
8521 * writes), attempt to calculate a complete snapshot for the given path
8522 *
8523 * @param writeIdsToExclude - An optional set to be excluded
8524 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8525 */
8526function writeTreeCalcCompleteEventCache(writeTree, treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8527 if (!writeIdsToExclude && !includeHiddenWrites) {
8528 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8529 if (shadowingNode != null) {
8530 return shadowingNode;
8531 }
8532 else {
8533 var subMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8534 if (compoundWriteIsEmpty(subMerge)) {
8535 return completeServerCache;
8536 }
8537 else if (completeServerCache == null &&
8538 !compoundWriteHasCompleteWrite(subMerge, newEmptyPath())) {
8539 // We wouldn't have a complete snapshot, since there's no underlying data and no complete shadow
8540 return null;
8541 }
8542 else {
8543 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8544 return compoundWriteApply(subMerge, layeredCache);
8545 }
8546 }
8547 }
8548 else {
8549 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8550 if (!includeHiddenWrites && compoundWriteIsEmpty(merge)) {
8551 return completeServerCache;
8552 }
8553 else {
8554 // If the server cache is null, and we don't have a complete cache, we need to return null
8555 if (!includeHiddenWrites &&
8556 completeServerCache == null &&
8557 !compoundWriteHasCompleteWrite(merge, newEmptyPath())) {
8558 return null;
8559 }
8560 else {
8561 var filter = function (write) {
8562 return ((write.visible || includeHiddenWrites) &&
8563 (!writeIdsToExclude ||
8564 !~writeIdsToExclude.indexOf(write.writeId)) &&
8565 (pathContains(write.path, treePath) ||
8566 pathContains(treePath, write.path)));
8567 };
8568 var mergeAtPath = writeTreeLayerTree_(writeTree.allWrites, filter, treePath);
8569 var layeredCache = completeServerCache || ChildrenNode.EMPTY_NODE;
8570 return compoundWriteApply(mergeAtPath, layeredCache);
8571 }
8572 }
8573 }
8574}
8575/**
8576 * With optional, underlying server data, attempt to return a children node of children that we have complete data for.
8577 * Used when creating new views, to pre-fill their complete event children snapshot.
8578 */
8579function writeTreeCalcCompleteEventChildren(writeTree, treePath, completeServerChildren) {
8580 var completeChildren = ChildrenNode.EMPTY_NODE;
8581 var topLevelSet = compoundWriteGetCompleteNode(writeTree.visibleWrites, treePath);
8582 if (topLevelSet) {
8583 if (!topLevelSet.isLeafNode()) {
8584 // we're shadowing everything. Return the children.
8585 topLevelSet.forEachChild(PRIORITY_INDEX, function (childName, childSnap) {
8586 completeChildren = completeChildren.updateImmediateChild(childName, childSnap);
8587 });
8588 }
8589 return completeChildren;
8590 }
8591 else if (completeServerChildren) {
8592 // Layer any children we have on top of this
8593 // We know we don't have a top-level set, so just enumerate existing children
8594 var merge_1 = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8595 completeServerChildren.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
8596 var node = compoundWriteApply(compoundWriteChildCompoundWrite(merge_1, new Path(childName)), childNode);
8597 completeChildren = completeChildren.updateImmediateChild(childName, node);
8598 });
8599 // Add any complete children we have from the set
8600 compoundWriteGetCompleteChildren(merge_1).forEach(function (namedNode) {
8601 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8602 });
8603 return completeChildren;
8604 }
8605 else {
8606 // We don't have anything to layer on top of. Layer on any children we have
8607 // Note that we can return an empty snap if we have a defined delete
8608 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8609 compoundWriteGetCompleteChildren(merge).forEach(function (namedNode) {
8610 completeChildren = completeChildren.updateImmediateChild(namedNode.name, namedNode.node);
8611 });
8612 return completeChildren;
8613 }
8614}
8615/**
8616 * Given that the underlying server data has updated, determine what, if anything, needs to be
8617 * applied to the event cache.
8618 *
8619 * Possibilities:
8620 *
8621 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8622 *
8623 * 2. Some write is completely shadowing. No events to be raised
8624 *
8625 * 3. Is partially shadowed. Events
8626 *
8627 * Either existingEventSnap or existingServerSnap must exist
8628 */
8629function writeTreeCalcEventCacheAfterServerOverwrite(writeTree, treePath, childPath, existingEventSnap, existingServerSnap) {
8630 util.assert(existingEventSnap || existingServerSnap, 'Either existingEventSnap or existingServerSnap must exist');
8631 var path = pathChild(treePath, childPath);
8632 if (compoundWriteHasCompleteWrite(writeTree.visibleWrites, path)) {
8633 // At this point we can probably guarantee that we're in case 2, meaning no events
8634 // May need to check visibility while doing the findRootMostValueAndPath call
8635 return null;
8636 }
8637 else {
8638 // No complete shadowing. We're either partially shadowing or not shadowing at all.
8639 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8640 if (compoundWriteIsEmpty(childMerge)) {
8641 // We're not shadowing at all. Case 1
8642 return existingServerSnap.getChild(childPath);
8643 }
8644 else {
8645 // This could be more efficient if the serverNode + updates doesn't change the eventSnap
8646 // However this is tricky to find out, since user updates don't necessary change the server
8647 // snap, e.g. priority updates on empty nodes, or deep deletes. Another special case is if the server
8648 // adds nodes, but doesn't change any existing writes. It is therefore not enough to
8649 // only check if the updates change the serverNode.
8650 // Maybe check if the merge tree contains these special cases and only do a full overwrite in that case?
8651 return compoundWriteApply(childMerge, existingServerSnap.getChild(childPath));
8652 }
8653 }
8654}
8655/**
8656 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8657 * complete child for this ChildKey.
8658 */
8659function writeTreeCalcCompleteChild(writeTree, treePath, childKey, existingServerSnap) {
8660 var path = pathChild(treePath, childKey);
8661 var shadowingNode = compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8662 if (shadowingNode != null) {
8663 return shadowingNode;
8664 }
8665 else {
8666 if (existingServerSnap.isCompleteForChild(childKey)) {
8667 var childMerge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, path);
8668 return compoundWriteApply(childMerge, existingServerSnap.getNode().getImmediateChild(childKey));
8669 }
8670 else {
8671 return null;
8672 }
8673 }
8674}
8675/**
8676 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8677 * a higher path, this will return the child of that write relative to the write and this path.
8678 * Returns null if there is no write at this path.
8679 */
8680function writeTreeShadowingWrite(writeTree, path) {
8681 return compoundWriteGetCompleteNode(writeTree.visibleWrites, path);
8682}
8683/**
8684 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8685 * the window, but may now be in the window.
8686 */
8687function writeTreeCalcIndexedSlice(writeTree, treePath, completeServerData, startPost, count, reverse, index) {
8688 var toIterate;
8689 var merge = compoundWriteChildCompoundWrite(writeTree.visibleWrites, treePath);
8690 var shadowingNode = compoundWriteGetCompleteNode(merge, newEmptyPath());
8691 if (shadowingNode != null) {
8692 toIterate = shadowingNode;
8693 }
8694 else if (completeServerData != null) {
8695 toIterate = compoundWriteApply(merge, completeServerData);
8696 }
8697 else {
8698 // no children to iterate on
8699 return [];
8700 }
8701 toIterate = toIterate.withIndex(index);
8702 if (!toIterate.isEmpty() && !toIterate.isLeafNode()) {
8703 var nodes = [];
8704 var cmp = index.getCompare();
8705 var iter = reverse
8706 ? toIterate.getReverseIteratorFrom(startPost, index)
8707 : toIterate.getIteratorFrom(startPost, index);
8708 var next = iter.getNext();
8709 while (next && nodes.length < count) {
8710 if (cmp(next, startPost) !== 0) {
8711 nodes.push(next);
8712 }
8713 next = iter.getNext();
8714 }
8715 return nodes;
8716 }
8717 else {
8718 return [];
8719 }
8720}
8721function newWriteTree() {
8722 return {
8723 visibleWrites: CompoundWrite.empty(),
8724 allWrites: [],
8725 lastWriteId: -1
8726 };
8727}
8728/**
8729 * If possible, returns a complete event cache, using the underlying server data if possible. In addition, can be used
8730 * to get a cache that includes hidden writes, and excludes arbitrary writes. Note that customizing the returned node
8731 * can lead to a more expensive calculation.
8732 *
8733 * @param writeIdsToExclude - Optional writes to exclude.
8734 * @param includeHiddenWrites - Defaults to false, whether or not to layer on writes with visible set to false
8735 */
8736function writeTreeRefCalcCompleteEventCache(writeTreeRef, completeServerCache, writeIdsToExclude, includeHiddenWrites) {
8737 return writeTreeCalcCompleteEventCache(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerCache, writeIdsToExclude, includeHiddenWrites);
8738}
8739/**
8740 * If possible, returns a children node containing all of the complete children we have data for. The returned data is a
8741 * mix of the given server data and write data.
8742 *
8743 */
8744function writeTreeRefCalcCompleteEventChildren(writeTreeRef, completeServerChildren) {
8745 return writeTreeCalcCompleteEventChildren(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerChildren);
8746}
8747/**
8748 * Given that either the underlying server data has updated or the outstanding writes have updated, determine what,
8749 * if anything, needs to be applied to the event cache.
8750 *
8751 * Possibilities:
8752 *
8753 * 1. No writes are shadowing. Events should be raised, the snap to be applied comes from the server data
8754 *
8755 * 2. Some write is completely shadowing. No events to be raised
8756 *
8757 * 3. Is partially shadowed. Events should be raised
8758 *
8759 * Either existingEventSnap or existingServerSnap must exist, this is validated via an assert
8760 *
8761 *
8762 */
8763function writeTreeRefCalcEventCacheAfterServerOverwrite(writeTreeRef, path, existingEventSnap, existingServerSnap) {
8764 return writeTreeCalcEventCacheAfterServerOverwrite(writeTreeRef.writeTree, writeTreeRef.treePath, path, existingEventSnap, existingServerSnap);
8765}
8766/**
8767 * Returns a node if there is a complete overwrite for this path. More specifically, if there is a write at
8768 * a higher path, this will return the child of that write relative to the write and this path.
8769 * Returns null if there is no write at this path.
8770 *
8771 */
8772function writeTreeRefShadowingWrite(writeTreeRef, path) {
8773 return writeTreeShadowingWrite(writeTreeRef.writeTree, pathChild(writeTreeRef.treePath, path));
8774}
8775/**
8776 * This method is used when processing child remove events on a query. If we can, we pull in children that were outside
8777 * the window, but may now be in the window
8778 */
8779function writeTreeRefCalcIndexedSlice(writeTreeRef, completeServerData, startPost, count, reverse, index) {
8780 return writeTreeCalcIndexedSlice(writeTreeRef.writeTree, writeTreeRef.treePath, completeServerData, startPost, count, reverse, index);
8781}
8782/**
8783 * Returns a complete child for a given server snap after applying all user writes or null if there is no
8784 * complete child for this ChildKey.
8785 */
8786function writeTreeRefCalcCompleteChild(writeTreeRef, childKey, existingServerCache) {
8787 return writeTreeCalcCompleteChild(writeTreeRef.writeTree, writeTreeRef.treePath, childKey, existingServerCache);
8788}
8789/**
8790 * Return a WriteTreeRef for a child.
8791 */
8792function writeTreeRefChild(writeTreeRef, childName) {
8793 return newWriteTreeRef(pathChild(writeTreeRef.treePath, childName), writeTreeRef.writeTree);
8794}
8795function newWriteTreeRef(path, writeTree) {
8796 return {
8797 treePath: path,
8798 writeTree: writeTree
8799 };
8800}
8801
8802/**
8803 * @license
8804 * Copyright 2017 Google LLC
8805 *
8806 * Licensed under the Apache License, Version 2.0 (the "License");
8807 * you may not use this file except in compliance with the License.
8808 * You may obtain a copy of the License at
8809 *
8810 * http://www.apache.org/licenses/LICENSE-2.0
8811 *
8812 * Unless required by applicable law or agreed to in writing, software
8813 * distributed under the License is distributed on an "AS IS" BASIS,
8814 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8815 * See the License for the specific language governing permissions and
8816 * limitations under the License.
8817 */
8818var ChildChangeAccumulator = /** @class */ (function () {
8819 function ChildChangeAccumulator() {
8820 this.changeMap = new Map();
8821 }
8822 ChildChangeAccumulator.prototype.trackChildChange = function (change) {
8823 var type = change.type;
8824 var childKey = change.childName;
8825 util.assert(type === "child_added" /* ChangeType.CHILD_ADDED */ ||
8826 type === "child_changed" /* ChangeType.CHILD_CHANGED */ ||
8827 type === "child_removed" /* ChangeType.CHILD_REMOVED */, 'Only child changes supported for tracking');
8828 util.assert(childKey !== '.priority', 'Only non-priority child changes can be tracked.');
8829 var oldChange = this.changeMap.get(childKey);
8830 if (oldChange) {
8831 var oldType = oldChange.type;
8832 if (type === "child_added" /* ChangeType.CHILD_ADDED */ &&
8833 oldType === "child_removed" /* ChangeType.CHILD_REMOVED */) {
8834 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.snapshotNode));
8835 }
8836 else if (type === "child_removed" /* ChangeType.CHILD_REMOVED */ &&
8837 oldType === "child_added" /* ChangeType.CHILD_ADDED */) {
8838 this.changeMap.delete(childKey);
8839 }
8840 else if (type === "child_removed" /* ChangeType.CHILD_REMOVED */ &&
8841 oldType === "child_changed" /* ChangeType.CHILD_CHANGED */) {
8842 this.changeMap.set(childKey, changeChildRemoved(childKey, oldChange.oldSnap));
8843 }
8844 else if (type === "child_changed" /* ChangeType.CHILD_CHANGED */ &&
8845 oldType === "child_added" /* ChangeType.CHILD_ADDED */) {
8846 this.changeMap.set(childKey, changeChildAdded(childKey, change.snapshotNode));
8847 }
8848 else if (type === "child_changed" /* ChangeType.CHILD_CHANGED */ &&
8849 oldType === "child_changed" /* ChangeType.CHILD_CHANGED */) {
8850 this.changeMap.set(childKey, changeChildChanged(childKey, change.snapshotNode, oldChange.oldSnap));
8851 }
8852 else {
8853 throw util.assertionError('Illegal combination of changes: ' +
8854 change +
8855 ' occurred after ' +
8856 oldChange);
8857 }
8858 }
8859 else {
8860 this.changeMap.set(childKey, change);
8861 }
8862 };
8863 ChildChangeAccumulator.prototype.getChanges = function () {
8864 return Array.from(this.changeMap.values());
8865 };
8866 return ChildChangeAccumulator;
8867}());
8868
8869/**
8870 * @license
8871 * Copyright 2017 Google LLC
8872 *
8873 * Licensed under the Apache License, Version 2.0 (the "License");
8874 * you may not use this file except in compliance with the License.
8875 * You may obtain a copy of the License at
8876 *
8877 * http://www.apache.org/licenses/LICENSE-2.0
8878 *
8879 * Unless required by applicable law or agreed to in writing, software
8880 * distributed under the License is distributed on an "AS IS" BASIS,
8881 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8882 * See the License for the specific language governing permissions and
8883 * limitations under the License.
8884 */
8885/**
8886 * An implementation of CompleteChildSource that never returns any additional children
8887 */
8888// eslint-disable-next-line @typescript-eslint/naming-convention
8889var NoCompleteChildSource_ = /** @class */ (function () {
8890 function NoCompleteChildSource_() {
8891 }
8892 NoCompleteChildSource_.prototype.getCompleteChild = function (childKey) {
8893 return null;
8894 };
8895 NoCompleteChildSource_.prototype.getChildAfterChild = function (index, child, reverse) {
8896 return null;
8897 };
8898 return NoCompleteChildSource_;
8899}());
8900/**
8901 * Singleton instance.
8902 */
8903var NO_COMPLETE_CHILD_SOURCE = new NoCompleteChildSource_();
8904/**
8905 * An implementation of CompleteChildSource that uses a WriteTree in addition to any other server data or
8906 * old event caches available to calculate complete children.
8907 */
8908var WriteTreeCompleteChildSource = /** @class */ (function () {
8909 function WriteTreeCompleteChildSource(writes_, viewCache_, optCompleteServerCache_) {
8910 if (optCompleteServerCache_ === void 0) { optCompleteServerCache_ = null; }
8911 this.writes_ = writes_;
8912 this.viewCache_ = viewCache_;
8913 this.optCompleteServerCache_ = optCompleteServerCache_;
8914 }
8915 WriteTreeCompleteChildSource.prototype.getCompleteChild = function (childKey) {
8916 var node = this.viewCache_.eventCache;
8917 if (node.isCompleteForChild(childKey)) {
8918 return node.getNode().getImmediateChild(childKey);
8919 }
8920 else {
8921 var serverNode = this.optCompleteServerCache_ != null
8922 ? new CacheNode(this.optCompleteServerCache_, true, false)
8923 : this.viewCache_.serverCache;
8924 return writeTreeRefCalcCompleteChild(this.writes_, childKey, serverNode);
8925 }
8926 };
8927 WriteTreeCompleteChildSource.prototype.getChildAfterChild = function (index, child, reverse) {
8928 var completeServerData = this.optCompleteServerCache_ != null
8929 ? this.optCompleteServerCache_
8930 : viewCacheGetCompleteServerSnap(this.viewCache_);
8931 var nodes = writeTreeRefCalcIndexedSlice(this.writes_, completeServerData, child, 1, reverse, index);
8932 if (nodes.length === 0) {
8933 return null;
8934 }
8935 else {
8936 return nodes[0];
8937 }
8938 };
8939 return WriteTreeCompleteChildSource;
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 */
8958function newViewProcessor(filter) {
8959 return { filter: filter };
8960}
8961function viewProcessorAssertIndexed(viewProcessor, viewCache) {
8962 util.assert(viewCache.eventCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Event snap not indexed');
8963 util.assert(viewCache.serverCache.getNode().isIndexed(viewProcessor.filter.getIndex()), 'Server snap not indexed');
8964}
8965function viewProcessorApplyOperation(viewProcessor, oldViewCache, operation, writesCache, completeCache) {
8966 var accumulator = new ChildChangeAccumulator();
8967 var newViewCache, filterServerNode;
8968 if (operation.type === OperationType.OVERWRITE) {
8969 var overwrite = operation;
8970 if (overwrite.source.fromUser) {
8971 newViewCache = viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, accumulator);
8972 }
8973 else {
8974 util.assert(overwrite.source.fromServer, 'Unknown source.');
8975 // We filter the node if it's a tagged update or the node has been previously filtered and the
8976 // update is not at the root in which case it is ok (and necessary) to mark the node unfiltered
8977 // again
8978 filterServerNode =
8979 overwrite.source.tagged ||
8980 (oldViewCache.serverCache.isFiltered() && !pathIsEmpty(overwrite.path));
8981 newViewCache = viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, overwrite.path, overwrite.snap, writesCache, completeCache, filterServerNode, accumulator);
8982 }
8983 }
8984 else if (operation.type === OperationType.MERGE) {
8985 var merge = operation;
8986 if (merge.source.fromUser) {
8987 newViewCache = viewProcessorApplyUserMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, accumulator);
8988 }
8989 else {
8990 util.assert(merge.source.fromServer, 'Unknown source.');
8991 // We filter the node if it's a tagged update or the node has been previously filtered
8992 filterServerNode =
8993 merge.source.tagged || oldViewCache.serverCache.isFiltered();
8994 newViewCache = viewProcessorApplyServerMerge(viewProcessor, oldViewCache, merge.path, merge.children, writesCache, completeCache, filterServerNode, accumulator);
8995 }
8996 }
8997 else if (operation.type === OperationType.ACK_USER_WRITE) {
8998 var ackUserWrite = operation;
8999 if (!ackUserWrite.revert) {
9000 newViewCache = viewProcessorAckUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, ackUserWrite.affectedTree, writesCache, completeCache, accumulator);
9001 }
9002 else {
9003 newViewCache = viewProcessorRevertUserWrite(viewProcessor, oldViewCache, ackUserWrite.path, writesCache, completeCache, accumulator);
9004 }
9005 }
9006 else if (operation.type === OperationType.LISTEN_COMPLETE) {
9007 newViewCache = viewProcessorListenComplete(viewProcessor, oldViewCache, operation.path, writesCache, accumulator);
9008 }
9009 else {
9010 throw util.assertionError('Unknown operation type: ' + operation.type);
9011 }
9012 var changes = accumulator.getChanges();
9013 viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, changes);
9014 return { viewCache: newViewCache, changes: changes };
9015}
9016function viewProcessorMaybeAddValueEvent(oldViewCache, newViewCache, accumulator) {
9017 var eventSnap = newViewCache.eventCache;
9018 if (eventSnap.isFullyInitialized()) {
9019 var isLeafOrEmpty = eventSnap.getNode().isLeafNode() || eventSnap.getNode().isEmpty();
9020 var oldCompleteSnap = viewCacheGetCompleteEventSnap(oldViewCache);
9021 if (accumulator.length > 0 ||
9022 !oldViewCache.eventCache.isFullyInitialized() ||
9023 (isLeafOrEmpty && !eventSnap.getNode().equals(oldCompleteSnap)) ||
9024 !eventSnap.getNode().getPriority().equals(oldCompleteSnap.getPriority())) {
9025 accumulator.push(changeValue(viewCacheGetCompleteEventSnap(newViewCache)));
9026 }
9027 }
9028}
9029function viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, viewCache, changePath, writesCache, source, accumulator) {
9030 var oldEventSnap = viewCache.eventCache;
9031 if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
9032 // we have a shadowing write, ignore changes
9033 return viewCache;
9034 }
9035 else {
9036 var newEventCache = void 0, serverNode = void 0;
9037 if (pathIsEmpty(changePath)) {
9038 // TODO: figure out how this plays with "sliding ack windows"
9039 util.assert(viewCache.serverCache.isFullyInitialized(), 'If change path is empty, we must have complete server data');
9040 if (viewCache.serverCache.isFiltered()) {
9041 // We need to special case this, because we need to only apply writes to complete children, or
9042 // we might end up raising events for incomplete children. If the server data is filtered deep
9043 // writes cannot be guaranteed to be complete
9044 var serverCache = viewCacheGetCompleteServerSnap(viewCache);
9045 var completeChildren = serverCache instanceof ChildrenNode
9046 ? serverCache
9047 : ChildrenNode.EMPTY_NODE;
9048 var completeEventChildren = writeTreeRefCalcCompleteEventChildren(writesCache, completeChildren);
9049 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeEventChildren, accumulator);
9050 }
9051 else {
9052 var completeNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9053 newEventCache = viewProcessor.filter.updateFullNode(viewCache.eventCache.getNode(), completeNode, accumulator);
9054 }
9055 }
9056 else {
9057 var childKey = pathGetFront(changePath);
9058 if (childKey === '.priority') {
9059 util.assert(pathGetLength(changePath) === 1, "Can't have a priority with additional path components");
9060 var oldEventNode = oldEventSnap.getNode();
9061 serverNode = viewCache.serverCache.getNode();
9062 // we might have overwrites for this priority
9063 var updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventNode, serverNode);
9064 if (updatedPriority != null) {
9065 newEventCache = viewProcessor.filter.updatePriority(oldEventNode, updatedPriority);
9066 }
9067 else {
9068 // priority didn't change, keep old node
9069 newEventCache = oldEventSnap.getNode();
9070 }
9071 }
9072 else {
9073 var childChangePath = pathPopFront(changePath);
9074 // update child
9075 var newEventChild = void 0;
9076 if (oldEventSnap.isCompleteForChild(childKey)) {
9077 serverNode = viewCache.serverCache.getNode();
9078 var eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite(writesCache, changePath, oldEventSnap.getNode(), serverNode);
9079 if (eventChildUpdate != null) {
9080 newEventChild = oldEventSnap
9081 .getNode()
9082 .getImmediateChild(childKey)
9083 .updateChild(childChangePath, eventChildUpdate);
9084 }
9085 else {
9086 // Nothing changed, just keep the old child
9087 newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
9088 }
9089 }
9090 else {
9091 newEventChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9092 }
9093 if (newEventChild != null) {
9094 newEventCache = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newEventChild, childChangePath, source, accumulator);
9095 }
9096 else {
9097 // no complete child available or no change
9098 newEventCache = oldEventSnap.getNode();
9099 }
9100 }
9101 }
9102 return viewCacheUpdateEventSnap(viewCache, newEventCache, oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath), viewProcessor.filter.filtersNodes());
9103 }
9104}
9105function viewProcessorApplyServerOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, filterServerNode, accumulator) {
9106 var oldServerSnap = oldViewCache.serverCache;
9107 var newServerCache;
9108 var serverFilter = filterServerNode
9109 ? viewProcessor.filter
9110 : viewProcessor.filter.getIndexedFilter();
9111 if (pathIsEmpty(changePath)) {
9112 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), changedSnap, null);
9113 }
9114 else if (serverFilter.filtersNodes() && !oldServerSnap.isFiltered()) {
9115 // we want to filter the server node, but we didn't filter the server node yet, so simulate a full update
9116 var newServerNode = oldServerSnap
9117 .getNode()
9118 .updateChild(changePath, changedSnap);
9119 newServerCache = serverFilter.updateFullNode(oldServerSnap.getNode(), newServerNode, null);
9120 }
9121 else {
9122 var childKey = pathGetFront(changePath);
9123 if (!oldServerSnap.isCompleteForPath(changePath) &&
9124 pathGetLength(changePath) > 1) {
9125 // We don't update incomplete nodes with updates intended for other listeners
9126 return oldViewCache;
9127 }
9128 var childChangePath = pathPopFront(changePath);
9129 var childNode = oldServerSnap.getNode().getImmediateChild(childKey);
9130 var newChildNode = childNode.updateChild(childChangePath, changedSnap);
9131 if (childKey === '.priority') {
9132 newServerCache = serverFilter.updatePriority(oldServerSnap.getNode(), newChildNode);
9133 }
9134 else {
9135 newServerCache = serverFilter.updateChild(oldServerSnap.getNode(), childKey, newChildNode, childChangePath, NO_COMPLETE_CHILD_SOURCE, null);
9136 }
9137 }
9138 var newViewCache = viewCacheUpdateServerSnap(oldViewCache, newServerCache, oldServerSnap.isFullyInitialized() || pathIsEmpty(changePath), serverFilter.filtersNodes());
9139 var source = new WriteTreeCompleteChildSource(writesCache, newViewCache, completeCache);
9140 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, changePath, writesCache, source, accumulator);
9141}
9142function viewProcessorApplyUserOverwrite(viewProcessor, oldViewCache, changePath, changedSnap, writesCache, completeCache, accumulator) {
9143 var oldEventSnap = oldViewCache.eventCache;
9144 var newViewCache, newEventCache;
9145 var source = new WriteTreeCompleteChildSource(writesCache, oldViewCache, completeCache);
9146 if (pathIsEmpty(changePath)) {
9147 newEventCache = viewProcessor.filter.updateFullNode(oldViewCache.eventCache.getNode(), changedSnap, accumulator);
9148 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, true, viewProcessor.filter.filtersNodes());
9149 }
9150 else {
9151 var childKey = pathGetFront(changePath);
9152 if (childKey === '.priority') {
9153 newEventCache = viewProcessor.filter.updatePriority(oldViewCache.eventCache.getNode(), changedSnap);
9154 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventCache, oldEventSnap.isFullyInitialized(), oldEventSnap.isFiltered());
9155 }
9156 else {
9157 var childChangePath = pathPopFront(changePath);
9158 var oldChild = oldEventSnap.getNode().getImmediateChild(childKey);
9159 var newChild = void 0;
9160 if (pathIsEmpty(childChangePath)) {
9161 // Child overwrite, we can replace the child
9162 newChild = changedSnap;
9163 }
9164 else {
9165 var childNode = source.getCompleteChild(childKey);
9166 if (childNode != null) {
9167 if (pathGetBack(childChangePath) === '.priority' &&
9168 childNode.getChild(pathParent(childChangePath)).isEmpty()) {
9169 // This is a priority update on an empty node. If this node exists on the server, the
9170 // server will send down the priority in the update, so ignore for now
9171 newChild = childNode;
9172 }
9173 else {
9174 newChild = childNode.updateChild(childChangePath, changedSnap);
9175 }
9176 }
9177 else {
9178 // There is no complete child node available
9179 newChild = ChildrenNode.EMPTY_NODE;
9180 }
9181 }
9182 if (!oldChild.equals(newChild)) {
9183 var newEventSnap = viewProcessor.filter.updateChild(oldEventSnap.getNode(), childKey, newChild, childChangePath, source, accumulator);
9184 newViewCache = viewCacheUpdateEventSnap(oldViewCache, newEventSnap, oldEventSnap.isFullyInitialized(), viewProcessor.filter.filtersNodes());
9185 }
9186 else {
9187 newViewCache = oldViewCache;
9188 }
9189 }
9190 }
9191 return newViewCache;
9192}
9193function viewProcessorCacheHasChild(viewCache, childKey) {
9194 return viewCache.eventCache.isCompleteForChild(childKey);
9195}
9196function viewProcessorApplyUserMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, accumulator) {
9197 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9198 // window leaving room for new items. It's important we process these changes first, so we
9199 // iterate the changes twice, first processing any that affect items currently in view.
9200 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9201 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9202 // not the other.
9203 var curViewCache = viewCache;
9204 changedChildren.foreach(function (relativePath, childNode) {
9205 var writePath = pathChild(path, relativePath);
9206 if (viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9207 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9208 }
9209 });
9210 changedChildren.foreach(function (relativePath, childNode) {
9211 var writePath = pathChild(path, relativePath);
9212 if (!viewProcessorCacheHasChild(viewCache, pathGetFront(writePath))) {
9213 curViewCache = viewProcessorApplyUserOverwrite(viewProcessor, curViewCache, writePath, childNode, writesCache, serverCache, accumulator);
9214 }
9215 });
9216 return curViewCache;
9217}
9218function viewProcessorApplyMerge(viewProcessor, node, merge) {
9219 merge.foreach(function (relativePath, childNode) {
9220 node = node.updateChild(relativePath, childNode);
9221 });
9222 return node;
9223}
9224function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedChildren, writesCache, serverCache, filterServerNode, accumulator) {
9225 // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
9226 // wait for the complete data update coming soon.
9227 if (viewCache.serverCache.getNode().isEmpty() &&
9228 !viewCache.serverCache.isFullyInitialized()) {
9229 return viewCache;
9230 }
9231 // HACK: In the case of a limit query, there may be some changes that bump things out of the
9232 // window leaving room for new items. It's important we process these changes first, so we
9233 // iterate the changes twice, first processing any that affect items currently in view.
9234 // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
9235 // and event snap. I'm not sure if this will result in edge cases when a child is in one but
9236 // not the other.
9237 var curViewCache = viewCache;
9238 var viewMergeTree;
9239 if (pathIsEmpty(path)) {
9240 viewMergeTree = changedChildren;
9241 }
9242 else {
9243 viewMergeTree = new ImmutableTree(null).setTree(path, changedChildren);
9244 }
9245 var serverNode = viewCache.serverCache.getNode();
9246 viewMergeTree.children.inorderTraversal(function (childKey, childTree) {
9247 if (serverNode.hasChild(childKey)) {
9248 var serverChild = viewCache.serverCache
9249 .getNode()
9250 .getImmediateChild(childKey);
9251 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childTree);
9252 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9253 }
9254 });
9255 viewMergeTree.children.inorderTraversal(function (childKey, childMergeTree) {
9256 var isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9257 childMergeTree.value === null;
9258 if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9259 var serverChild = viewCache.serverCache
9260 .getNode()
9261 .getImmediateChild(childKey);
9262 var newChild = viewProcessorApplyMerge(viewProcessor, serverChild, childMergeTree);
9263 curViewCache = viewProcessorApplyServerOverwrite(viewProcessor, curViewCache, new Path(childKey), newChild, writesCache, serverCache, filterServerNode, accumulator);
9264 }
9265 });
9266 return curViewCache;
9267}
9268function viewProcessorAckUserWrite(viewProcessor, viewCache, ackPath, affectedTree, writesCache, completeCache, accumulator) {
9269 if (writeTreeRefShadowingWrite(writesCache, ackPath) != null) {
9270 return viewCache;
9271 }
9272 // Only filter server node if it is currently filtered
9273 var filterServerNode = viewCache.serverCache.isFiltered();
9274 // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
9275 // now that it won't be shadowed.
9276 var serverCache = viewCache.serverCache;
9277 if (affectedTree.value != null) {
9278 // This is an overwrite.
9279 if ((pathIsEmpty(ackPath) && serverCache.isFullyInitialized()) ||
9280 serverCache.isCompleteForPath(ackPath)) {
9281 return viewProcessorApplyServerOverwrite(viewProcessor, viewCache, ackPath, serverCache.getNode().getChild(ackPath), writesCache, completeCache, filterServerNode, accumulator);
9282 }
9283 else if (pathIsEmpty(ackPath)) {
9284 // This is a goofy edge case where we are acking data at this location but don't have full data. We
9285 // should just re-apply whatever we have in our cache as a merge.
9286 var changedChildren_1 = new ImmutableTree(null);
9287 serverCache.getNode().forEachChild(KEY_INDEX, function (name, node) {
9288 changedChildren_1 = changedChildren_1.set(new Path(name), node);
9289 });
9290 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_1, writesCache, completeCache, filterServerNode, accumulator);
9291 }
9292 else {
9293 return viewCache;
9294 }
9295 }
9296 else {
9297 // This is a merge.
9298 var changedChildren_2 = new ImmutableTree(null);
9299 affectedTree.foreach(function (mergePath, value) {
9300 var serverCachePath = pathChild(ackPath, mergePath);
9301 if (serverCache.isCompleteForPath(serverCachePath)) {
9302 changedChildren_2 = changedChildren_2.set(mergePath, serverCache.getNode().getChild(serverCachePath));
9303 }
9304 });
9305 return viewProcessorApplyServerMerge(viewProcessor, viewCache, ackPath, changedChildren_2, writesCache, completeCache, filterServerNode, accumulator);
9306 }
9307}
9308function viewProcessorListenComplete(viewProcessor, viewCache, path, writesCache, accumulator) {
9309 var oldServerNode = viewCache.serverCache;
9310 var newViewCache = viewCacheUpdateServerSnap(viewCache, oldServerNode.getNode(), oldServerNode.isFullyInitialized() || pathIsEmpty(path), oldServerNode.isFiltered());
9311 return viewProcessorGenerateEventCacheAfterServerEvent(viewProcessor, newViewCache, path, writesCache, NO_COMPLETE_CHILD_SOURCE, accumulator);
9312}
9313function viewProcessorRevertUserWrite(viewProcessor, viewCache, path, writesCache, completeServerCache, accumulator) {
9314 var complete;
9315 if (writeTreeRefShadowingWrite(writesCache, path) != null) {
9316 return viewCache;
9317 }
9318 else {
9319 var source = new WriteTreeCompleteChildSource(writesCache, viewCache, completeServerCache);
9320 var oldEventCache = viewCache.eventCache.getNode();
9321 var newEventCache = void 0;
9322 if (pathIsEmpty(path) || pathGetFront(path) === '.priority') {
9323 var newNode = void 0;
9324 if (viewCache.serverCache.isFullyInitialized()) {
9325 newNode = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9326 }
9327 else {
9328 var serverChildren = viewCache.serverCache.getNode();
9329 util.assert(serverChildren instanceof ChildrenNode, 'serverChildren would be complete if leaf node');
9330 newNode = writeTreeRefCalcCompleteEventChildren(writesCache, serverChildren);
9331 }
9332 newNode = newNode;
9333 newEventCache = viewProcessor.filter.updateFullNode(oldEventCache, newNode, accumulator);
9334 }
9335 else {
9336 var childKey = pathGetFront(path);
9337 var newChild = writeTreeRefCalcCompleteChild(writesCache, childKey, viewCache.serverCache);
9338 if (newChild == null &&
9339 viewCache.serverCache.isCompleteForChild(childKey)) {
9340 newChild = oldEventCache.getImmediateChild(childKey);
9341 }
9342 if (newChild != null) {
9343 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, newChild, pathPopFront(path), source, accumulator);
9344 }
9345 else if (viewCache.eventCache.getNode().hasChild(childKey)) {
9346 // No complete child available, delete the existing one, if any
9347 newEventCache = viewProcessor.filter.updateChild(oldEventCache, childKey, ChildrenNode.EMPTY_NODE, pathPopFront(path), source, accumulator);
9348 }
9349 else {
9350 newEventCache = oldEventCache;
9351 }
9352 if (newEventCache.isEmpty() &&
9353 viewCache.serverCache.isFullyInitialized()) {
9354 // We might have reverted all child writes. Maybe the old event was a leaf node
9355 complete = writeTreeRefCalcCompleteEventCache(writesCache, viewCacheGetCompleteServerSnap(viewCache));
9356 if (complete.isLeafNode()) {
9357 newEventCache = viewProcessor.filter.updateFullNode(newEventCache, complete, accumulator);
9358 }
9359 }
9360 }
9361 complete =
9362 viewCache.serverCache.isFullyInitialized() ||
9363 writeTreeRefShadowingWrite(writesCache, newEmptyPath()) != null;
9364 return viewCacheUpdateEventSnap(viewCache, newEventCache, complete, viewProcessor.filter.filtersNodes());
9365 }
9366}
9367
9368/**
9369 * @license
9370 * Copyright 2017 Google LLC
9371 *
9372 * Licensed under the Apache License, Version 2.0 (the "License");
9373 * you may not use this file except in compliance with the License.
9374 * You may obtain a copy of the License at
9375 *
9376 * http://www.apache.org/licenses/LICENSE-2.0
9377 *
9378 * Unless required by applicable law or agreed to in writing, software
9379 * distributed under the License is distributed on an "AS IS" BASIS,
9380 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9381 * See the License for the specific language governing permissions and
9382 * limitations under the License.
9383 */
9384/**
9385 * A view represents a specific location and query that has 1 or more event registrations.
9386 *
9387 * It does several things:
9388 * - Maintains the list of event registrations for this location/query.
9389 * - Maintains a cache of the data visible for this location/query.
9390 * - Applies new operations (via applyOperation), updates the cache, and based on the event
9391 * registrations returns the set of events to be raised.
9392 */
9393var View = /** @class */ (function () {
9394 function View(query_, initialViewCache) {
9395 this.query_ = query_;
9396 this.eventRegistrations_ = [];
9397 var params = this.query_._queryParams;
9398 var indexFilter = new IndexedFilter(params.getIndex());
9399 var filter = queryParamsGetNodeFilter(params);
9400 this.processor_ = newViewProcessor(filter);
9401 var initialServerCache = initialViewCache.serverCache;
9402 var initialEventCache = initialViewCache.eventCache;
9403 // Don't filter server node with other filter than index, wait for tagged listen
9404 var serverSnap = indexFilter.updateFullNode(ChildrenNode.EMPTY_NODE, initialServerCache.getNode(), null);
9405 var eventSnap = filter.updateFullNode(ChildrenNode.EMPTY_NODE, initialEventCache.getNode(), null);
9406 var newServerCache = new CacheNode(serverSnap, initialServerCache.isFullyInitialized(), indexFilter.filtersNodes());
9407 var newEventCache = new CacheNode(eventSnap, initialEventCache.isFullyInitialized(), filter.filtersNodes());
9408 this.viewCache_ = newViewCache(newEventCache, newServerCache);
9409 this.eventGenerator_ = new EventGenerator(this.query_);
9410 }
9411 Object.defineProperty(View.prototype, "query", {
9412 get: function () {
9413 return this.query_;
9414 },
9415 enumerable: false,
9416 configurable: true
9417 });
9418 return View;
9419}());
9420function viewGetServerCache(view) {
9421 return view.viewCache_.serverCache.getNode();
9422}
9423function viewGetCompleteNode(view) {
9424 return viewCacheGetCompleteEventSnap(view.viewCache_);
9425}
9426function viewGetCompleteServerCache(view, path) {
9427 var cache = viewCacheGetCompleteServerSnap(view.viewCache_);
9428 if (cache) {
9429 // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
9430 // we need to see if it contains the child we're interested in.
9431 if (view.query._queryParams.loadsAllData() ||
9432 (!pathIsEmpty(path) &&
9433 !cache.getImmediateChild(pathGetFront(path)).isEmpty())) {
9434 return cache.getChild(path);
9435 }
9436 }
9437 return null;
9438}
9439function viewIsEmpty(view) {
9440 return view.eventRegistrations_.length === 0;
9441}
9442function viewAddEventRegistration(view, eventRegistration) {
9443 view.eventRegistrations_.push(eventRegistration);
9444}
9445/**
9446 * @param eventRegistration - If null, remove all callbacks.
9447 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9448 * @returns Cancel events, if cancelError was provided.
9449 */
9450function viewRemoveEventRegistration(view, eventRegistration, cancelError) {
9451 var cancelEvents = [];
9452 if (cancelError) {
9453 util.assert(eventRegistration == null, 'A cancel should cancel all event registrations.');
9454 var path_1 = view.query._path;
9455 view.eventRegistrations_.forEach(function (registration) {
9456 var maybeEvent = registration.createCancelEvent(cancelError, path_1);
9457 if (maybeEvent) {
9458 cancelEvents.push(maybeEvent);
9459 }
9460 });
9461 }
9462 if (eventRegistration) {
9463 var remaining = [];
9464 for (var i = 0; i < view.eventRegistrations_.length; ++i) {
9465 var existing = view.eventRegistrations_[i];
9466 if (!existing.matches(eventRegistration)) {
9467 remaining.push(existing);
9468 }
9469 else if (eventRegistration.hasAnyCallback()) {
9470 // We're removing just this one
9471 remaining = remaining.concat(view.eventRegistrations_.slice(i + 1));
9472 break;
9473 }
9474 }
9475 view.eventRegistrations_ = remaining;
9476 }
9477 else {
9478 view.eventRegistrations_ = [];
9479 }
9480 return cancelEvents;
9481}
9482/**
9483 * Applies the given Operation, updates our cache, and returns the appropriate events.
9484 */
9485function viewApplyOperation(view, operation, writesCache, completeServerCache) {
9486 if (operation.type === OperationType.MERGE &&
9487 operation.source.queryId !== null) {
9488 util.assert(viewCacheGetCompleteServerSnap(view.viewCache_), 'We should always have a full cache before handling merges');
9489 util.assert(viewCacheGetCompleteEventSnap(view.viewCache_), 'Missing event cache, even though we have a server cache');
9490 }
9491 var oldViewCache = view.viewCache_;
9492 var result = viewProcessorApplyOperation(view.processor_, oldViewCache, operation, writesCache, completeServerCache);
9493 viewProcessorAssertIndexed(view.processor_, result.viewCache);
9494 util.assert(result.viewCache.serverCache.isFullyInitialized() ||
9495 !oldViewCache.serverCache.isFullyInitialized(), 'Once a server snap is complete, it should never go back');
9496 view.viewCache_ = result.viewCache;
9497 return viewGenerateEventsForChanges_(view, result.changes, result.viewCache.eventCache.getNode(), null);
9498}
9499function viewGetInitialEvents(view, registration) {
9500 var eventSnap = view.viewCache_.eventCache;
9501 var initialChanges = [];
9502 if (!eventSnap.getNode().isLeafNode()) {
9503 var eventNode = eventSnap.getNode();
9504 eventNode.forEachChild(PRIORITY_INDEX, function (key, childNode) {
9505 initialChanges.push(changeChildAdded(key, childNode));
9506 });
9507 }
9508 if (eventSnap.isFullyInitialized()) {
9509 initialChanges.push(changeValue(eventSnap.getNode()));
9510 }
9511 return viewGenerateEventsForChanges_(view, initialChanges, eventSnap.getNode(), registration);
9512}
9513function viewGenerateEventsForChanges_(view, changes, eventCache, eventRegistration) {
9514 var registrations = eventRegistration
9515 ? [eventRegistration]
9516 : view.eventRegistrations_;
9517 return eventGeneratorGenerateEventsForChanges(view.eventGenerator_, changes, eventCache, registrations);
9518}
9519
9520/**
9521 * @license
9522 * Copyright 2017 Google LLC
9523 *
9524 * Licensed under the Apache License, Version 2.0 (the "License");
9525 * you may not use this file except in compliance with the License.
9526 * You may obtain a copy of the License at
9527 *
9528 * http://www.apache.org/licenses/LICENSE-2.0
9529 *
9530 * Unless required by applicable law or agreed to in writing, software
9531 * distributed under the License is distributed on an "AS IS" BASIS,
9532 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9533 * See the License for the specific language governing permissions and
9534 * limitations under the License.
9535 */
9536var referenceConstructor$1;
9537/**
9538 * SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
9539 * maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
9540 * and user writes (set, transaction, update).
9541 *
9542 * It's responsible for:
9543 * - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
9544 * - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
9545 * applyUserOverwrite, etc.)
9546 */
9547var SyncPoint = /** @class */ (function () {
9548 function SyncPoint() {
9549 /**
9550 * The Views being tracked at this location in the tree, stored as a map where the key is a
9551 * queryId and the value is the View for that query.
9552 *
9553 * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
9554 */
9555 this.views = new Map();
9556 }
9557 return SyncPoint;
9558}());
9559function syncPointSetReferenceConstructor(val) {
9560 util.assert(!referenceConstructor$1, '__referenceConstructor has already been defined');
9561 referenceConstructor$1 = val;
9562}
9563function syncPointGetReferenceConstructor() {
9564 util.assert(referenceConstructor$1, 'Reference.ts has not been loaded');
9565 return referenceConstructor$1;
9566}
9567function syncPointIsEmpty(syncPoint) {
9568 return syncPoint.views.size === 0;
9569}
9570function syncPointApplyOperation(syncPoint, operation, writesCache, optCompleteServerCache) {
9571 var e_1, _a;
9572 var queryId = operation.source.queryId;
9573 if (queryId !== null) {
9574 var view = syncPoint.views.get(queryId);
9575 util.assert(view != null, 'SyncTree gave us an op for an invalid query.');
9576 return viewApplyOperation(view, operation, writesCache, optCompleteServerCache);
9577 }
9578 else {
9579 var events = [];
9580 try {
9581 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9582 var view = _c.value;
9583 events = events.concat(viewApplyOperation(view, operation, writesCache, optCompleteServerCache));
9584 }
9585 }
9586 catch (e_1_1) { e_1 = { error: e_1_1 }; }
9587 finally {
9588 try {
9589 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9590 }
9591 finally { if (e_1) throw e_1.error; }
9592 }
9593 return events;
9594 }
9595}
9596/**
9597 * Get a view for the specified query.
9598 *
9599 * @param query - The query to return a view for
9600 * @param writesCache
9601 * @param serverCache
9602 * @param serverCacheComplete
9603 * @returns Events to raise.
9604 */
9605function syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete) {
9606 var queryId = query._queryIdentifier;
9607 var view = syncPoint.views.get(queryId);
9608 if (!view) {
9609 // TODO: make writesCache take flag for complete server node
9610 var eventCache = writeTreeRefCalcCompleteEventCache(writesCache, serverCacheComplete ? serverCache : null);
9611 var eventCacheComplete = false;
9612 if (eventCache) {
9613 eventCacheComplete = true;
9614 }
9615 else if (serverCache instanceof ChildrenNode) {
9616 eventCache = writeTreeRefCalcCompleteEventChildren(writesCache, serverCache);
9617 eventCacheComplete = false;
9618 }
9619 else {
9620 eventCache = ChildrenNode.EMPTY_NODE;
9621 eventCacheComplete = false;
9622 }
9623 var viewCache = newViewCache(new CacheNode(eventCache, eventCacheComplete, false), new CacheNode(serverCache, serverCacheComplete, false));
9624 return new View(query, viewCache);
9625 }
9626 return view;
9627}
9628/**
9629 * Add an event callback for the specified query.
9630 *
9631 * @param query
9632 * @param eventRegistration
9633 * @param writesCache
9634 * @param serverCache - Complete server cache, if we have it.
9635 * @param serverCacheComplete
9636 * @returns Events to raise.
9637 */
9638function syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete) {
9639 var view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9640 if (!syncPoint.views.has(query._queryIdentifier)) {
9641 syncPoint.views.set(query._queryIdentifier, view);
9642 }
9643 // This is guaranteed to exist now, we just created anything that was missing
9644 viewAddEventRegistration(view, eventRegistration);
9645 return viewGetInitialEvents(view, eventRegistration);
9646}
9647/**
9648 * Remove event callback(s). Return cancelEvents if a cancelError is specified.
9649 *
9650 * If query is the default query, we'll check all views for the specified eventRegistration.
9651 * If eventRegistration is null, we'll remove all callbacks for the specified view(s).
9652 *
9653 * @param eventRegistration - If null, remove all callbacks.
9654 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9655 * @returns removed queries and any cancel events
9656 */
9657function syncPointRemoveEventRegistration(syncPoint, query, eventRegistration, cancelError) {
9658 var e_2, _a;
9659 var queryId = query._queryIdentifier;
9660 var removed = [];
9661 var cancelEvents = [];
9662 var hadCompleteView = syncPointHasCompleteView(syncPoint);
9663 if (queryId === 'default') {
9664 try {
9665 // When you do ref.off(...), we search all views for the registration to remove.
9666 for (var _b = tslib.__values(syncPoint.views.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
9667 var _d = tslib.__read(_c.value, 2), viewQueryId = _d[0], view = _d[1];
9668 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9669 if (viewIsEmpty(view)) {
9670 syncPoint.views.delete(viewQueryId);
9671 // We'll deal with complete views later.
9672 if (!view.query._queryParams.loadsAllData()) {
9673 removed.push(view.query);
9674 }
9675 }
9676 }
9677 }
9678 catch (e_2_1) { e_2 = { error: e_2_1 }; }
9679 finally {
9680 try {
9681 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9682 }
9683 finally { if (e_2) throw e_2.error; }
9684 }
9685 }
9686 else {
9687 // remove the callback from the specific view.
9688 var view = syncPoint.views.get(queryId);
9689 if (view) {
9690 cancelEvents = cancelEvents.concat(viewRemoveEventRegistration(view, eventRegistration, cancelError));
9691 if (viewIsEmpty(view)) {
9692 syncPoint.views.delete(queryId);
9693 // We'll deal with complete views later.
9694 if (!view.query._queryParams.loadsAllData()) {
9695 removed.push(view.query);
9696 }
9697 }
9698 }
9699 }
9700 if (hadCompleteView && !syncPointHasCompleteView(syncPoint)) {
9701 // We removed our last complete view.
9702 removed.push(new (syncPointGetReferenceConstructor())(query._repo, query._path));
9703 }
9704 return { removed: removed, events: cancelEvents };
9705}
9706function syncPointGetQueryViews(syncPoint) {
9707 var e_3, _a;
9708 var result = [];
9709 try {
9710 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9711 var view = _c.value;
9712 if (!view.query._queryParams.loadsAllData()) {
9713 result.push(view);
9714 }
9715 }
9716 }
9717 catch (e_3_1) { e_3 = { error: e_3_1 }; }
9718 finally {
9719 try {
9720 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9721 }
9722 finally { if (e_3) throw e_3.error; }
9723 }
9724 return result;
9725}
9726/**
9727 * @param path - The path to the desired complete snapshot
9728 * @returns A complete cache, if it exists
9729 */
9730function syncPointGetCompleteServerCache(syncPoint, path) {
9731 var e_4, _a;
9732 var serverCache = null;
9733 try {
9734 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9735 var view = _c.value;
9736 serverCache = serverCache || viewGetCompleteServerCache(view, path);
9737 }
9738 }
9739 catch (e_4_1) { e_4 = { error: e_4_1 }; }
9740 finally {
9741 try {
9742 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9743 }
9744 finally { if (e_4) throw e_4.error; }
9745 }
9746 return serverCache;
9747}
9748function syncPointViewForQuery(syncPoint, query) {
9749 var params = query._queryParams;
9750 if (params.loadsAllData()) {
9751 return syncPointGetCompleteView(syncPoint);
9752 }
9753 else {
9754 var queryId = query._queryIdentifier;
9755 return syncPoint.views.get(queryId);
9756 }
9757}
9758function syncPointViewExistsForQuery(syncPoint, query) {
9759 return syncPointViewForQuery(syncPoint, query) != null;
9760}
9761function syncPointHasCompleteView(syncPoint) {
9762 return syncPointGetCompleteView(syncPoint) != null;
9763}
9764function syncPointGetCompleteView(syncPoint) {
9765 var e_5, _a;
9766 try {
9767 for (var _b = tslib.__values(syncPoint.views.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
9768 var view = _c.value;
9769 if (view.query._queryParams.loadsAllData()) {
9770 return view;
9771 }
9772 }
9773 }
9774 catch (e_5_1) { e_5 = { error: e_5_1 }; }
9775 finally {
9776 try {
9777 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
9778 }
9779 finally { if (e_5) throw e_5.error; }
9780 }
9781 return null;
9782}
9783
9784/**
9785 * @license
9786 * Copyright 2017 Google LLC
9787 *
9788 * Licensed under the Apache License, Version 2.0 (the "License");
9789 * you may not use this file except in compliance with the License.
9790 * You may obtain a copy of the License at
9791 *
9792 * http://www.apache.org/licenses/LICENSE-2.0
9793 *
9794 * Unless required by applicable law or agreed to in writing, software
9795 * distributed under the License is distributed on an "AS IS" BASIS,
9796 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9797 * See the License for the specific language governing permissions and
9798 * limitations under the License.
9799 */
9800var referenceConstructor;
9801function syncTreeSetReferenceConstructor(val) {
9802 util.assert(!referenceConstructor, '__referenceConstructor has already been defined');
9803 referenceConstructor = val;
9804}
9805function syncTreeGetReferenceConstructor() {
9806 util.assert(referenceConstructor, 'Reference.ts has not been loaded');
9807 return referenceConstructor;
9808}
9809/**
9810 * Static tracker for next query tag.
9811 */
9812var syncTreeNextQueryTag_ = 1;
9813/**
9814 * SyncTree is the central class for managing event callback registration, data caching, views
9815 * (query processing), and event generation. There are typically two SyncTree instances for
9816 * each Repo, one for the normal Firebase data, and one for the .info data.
9817 *
9818 * It has a number of responsibilities, including:
9819 * - Tracking all user event callbacks (registered via addEventRegistration() and removeEventRegistration()).
9820 * - Applying and caching data changes for user set(), transaction(), and update() calls
9821 * (applyUserOverwrite(), applyUserMerge()).
9822 * - Applying and caching data changes for server data changes (applyServerOverwrite(),
9823 * applyServerMerge()).
9824 * - Generating user-facing events for server and user changes (all of the apply* methods
9825 * return the set of events that need to be raised as a result).
9826 * - Maintaining the appropriate set of server listens to ensure we are always subscribed
9827 * to the correct set of paths and queries to satisfy the current set of user event
9828 * callbacks (listens are started/stopped using the provided listenProvider).
9829 *
9830 * NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
9831 * events are returned to the caller rather than raised synchronously.
9832 *
9833 */
9834var SyncTree = /** @class */ (function () {
9835 /**
9836 * @param listenProvider_ - Used by SyncTree to start / stop listening
9837 * to server data.
9838 */
9839 function SyncTree(listenProvider_) {
9840 this.listenProvider_ = listenProvider_;
9841 /**
9842 * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
9843 */
9844 this.syncPointTree_ = new ImmutableTree(null);
9845 /**
9846 * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
9847 */
9848 this.pendingWriteTree_ = newWriteTree();
9849 this.tagToQueryMap = new Map();
9850 this.queryToTagMap = new Map();
9851 }
9852 return SyncTree;
9853}());
9854/**
9855 * Apply the data changes for a user-generated set() or transaction() call.
9856 *
9857 * @returns Events to raise.
9858 */
9859function syncTreeApplyUserOverwrite(syncTree, path, newData, writeId, visible) {
9860 // Record pending write.
9861 writeTreeAddOverwrite(syncTree.pendingWriteTree_, path, newData, writeId, visible);
9862 if (!visible) {
9863 return [];
9864 }
9865 else {
9866 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceUser(), path, newData));
9867 }
9868}
9869/**
9870 * Apply the data from a user-generated update() call
9871 *
9872 * @returns Events to raise.
9873 */
9874function syncTreeApplyUserMerge(syncTree, path, changedChildren, writeId) {
9875 // Record pending merge.
9876 writeTreeAddMerge(syncTree.pendingWriteTree_, path, changedChildren, writeId);
9877 var changeTree = ImmutableTree.fromObject(changedChildren);
9878 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceUser(), path, changeTree));
9879}
9880/**
9881 * Acknowledge a pending user write that was previously registered with applyUserOverwrite() or applyUserMerge().
9882 *
9883 * @param revert - True if the given write failed and needs to be reverted
9884 * @returns Events to raise.
9885 */
9886function syncTreeAckUserWrite(syncTree, writeId, revert) {
9887 if (revert === void 0) { revert = false; }
9888 var write = writeTreeGetWrite(syncTree.pendingWriteTree_, writeId);
9889 var needToReevaluate = writeTreeRemoveWrite(syncTree.pendingWriteTree_, writeId);
9890 if (!needToReevaluate) {
9891 return [];
9892 }
9893 else {
9894 var affectedTree_1 = new ImmutableTree(null);
9895 if (write.snap != null) {
9896 // overwrite
9897 affectedTree_1 = affectedTree_1.set(newEmptyPath(), true);
9898 }
9899 else {
9900 each(write.children, function (pathString) {
9901 affectedTree_1 = affectedTree_1.set(new Path(pathString), true);
9902 });
9903 }
9904 return syncTreeApplyOperationToSyncPoints_(syncTree, new AckUserWrite(write.path, affectedTree_1, revert));
9905 }
9906}
9907/**
9908 * Apply new server data for the specified path..
9909 *
9910 * @returns Events to raise.
9911 */
9912function syncTreeApplyServerOverwrite(syncTree, path, newData) {
9913 return syncTreeApplyOperationToSyncPoints_(syncTree, new Overwrite(newOperationSourceServer(), path, newData));
9914}
9915/**
9916 * Apply new server data to be merged in at the specified path.
9917 *
9918 * @returns Events to raise.
9919 */
9920function syncTreeApplyServerMerge(syncTree, path, changedChildren) {
9921 var changeTree = ImmutableTree.fromObject(changedChildren);
9922 return syncTreeApplyOperationToSyncPoints_(syncTree, new Merge(newOperationSourceServer(), path, changeTree));
9923}
9924/**
9925 * Apply a listen complete for a query
9926 *
9927 * @returns Events to raise.
9928 */
9929function syncTreeApplyListenComplete(syncTree, path) {
9930 return syncTreeApplyOperationToSyncPoints_(syncTree, new ListenComplete(newOperationSourceServer(), path));
9931}
9932/**
9933 * Apply a listen complete for a tagged query
9934 *
9935 * @returns Events to raise.
9936 */
9937function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
9938 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
9939 if (queryKey) {
9940 var r = syncTreeParseQueryKey_(queryKey);
9941 var queryPath = r.path, queryId = r.queryId;
9942 var relativePath = newRelativePath(queryPath, path);
9943 var op = new ListenComplete(newOperationSourceServerTaggedQuery(queryId), relativePath);
9944 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
9945 }
9946 else {
9947 // We've already removed the query. No big deal, ignore the update
9948 return [];
9949 }
9950}
9951/**
9952 * Remove event callback(s).
9953 *
9954 * If query is the default query, we'll check all queries for the specified eventRegistration.
9955 * If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
9956 *
9957 * @param eventRegistration - If null, all callbacks are removed.
9958 * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9959 * @param skipListenerDedup - When performing a `get()`, we don't add any new listeners, so no
9960 * deduping needs to take place. This flag allows toggling of that behavior
9961 * @returns Cancel events, if cancelError was provided.
9962 */
9963function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError, skipListenerDedup) {
9964 if (skipListenerDedup === void 0) { skipListenerDedup = false; }
9965 // Find the syncPoint first. Then deal with whether or not it has matching listeners
9966 var path = query._path;
9967 var maybeSyncPoint = syncTree.syncPointTree_.get(path);
9968 var cancelEvents = [];
9969 // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
9970 // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
9971 // not loadsAllData().
9972 if (maybeSyncPoint &&
9973 (query._queryIdentifier === 'default' ||
9974 syncPointViewExistsForQuery(maybeSyncPoint, query))) {
9975 var removedAndEvents = syncPointRemoveEventRegistration(maybeSyncPoint, query, eventRegistration, cancelError);
9976 if (syncPointIsEmpty(maybeSyncPoint)) {
9977 syncTree.syncPointTree_ = syncTree.syncPointTree_.remove(path);
9978 }
9979 var removed = removedAndEvents.removed;
9980 cancelEvents = removedAndEvents.events;
9981 if (!skipListenerDedup) {
9982 /**
9983 * We may have just removed one of many listeners and can short-circuit this whole process
9984 * We may also not have removed a default listener, in which case all of the descendant listeners should already be
9985 * properly set up.
9986 */
9987 // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
9988 // queryId === 'default'
9989 var removingDefault = -1 !==
9990 removed.findIndex(function (query) {
9991 return query._queryParams.loadsAllData();
9992 });
9993 var covered = syncTree.syncPointTree_.findOnPath(path, function (relativePath, parentSyncPoint) {
9994 return syncPointHasCompleteView(parentSyncPoint);
9995 });
9996 if (removingDefault && !covered) {
9997 var subtree = syncTree.syncPointTree_.subtree(path);
9998 // There are potentially child listeners. Determine what if any listens we need to send before executing the
9999 // removal
10000 if (!subtree.isEmpty()) {
10001 // We need to fold over our subtree and collect the listeners to send
10002 var newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
10003 // Ok, we've collected all the listens we need. Set them up.
10004 for (var i = 0; i < newViews.length; ++i) {
10005 var view = newViews[i], newQuery = view.query;
10006 var listener = syncTreeCreateListenerForView_(syncTree, view);
10007 syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery(syncTree, newQuery), listener.hashFn, listener.onComplete);
10008 }
10009 }
10010 // Otherwise there's nothing below us, so nothing we need to start listening on
10011 }
10012 // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
10013 // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
10014 // Also, note that if we have a cancelError, it's already been removed at the provider level.
10015 if (!covered && removed.length > 0 && !cancelError) {
10016 // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
10017 // default. Otherwise, we need to iterate through and cancel each individual query
10018 if (removingDefault) {
10019 // We don't tag default listeners
10020 var defaultTag = null;
10021 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
10022 }
10023 else {
10024 removed.forEach(function (queryToRemove) {
10025 var tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
10026 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
10027 });
10028 }
10029 }
10030 }
10031 // Now, clear all of the tags we're tracking for the removed listens
10032 syncTreeRemoveTags_(syncTree, removed);
10033 }
10034 return cancelEvents;
10035}
10036/**
10037 * Apply new server data for the specified tagged query.
10038 *
10039 * @returns Events to raise.
10040 */
10041function syncTreeApplyTaggedQueryOverwrite(syncTree, path, snap, tag) {
10042 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10043 if (queryKey != null) {
10044 var r = syncTreeParseQueryKey_(queryKey);
10045 var queryPath = r.path, queryId = r.queryId;
10046 var relativePath = newRelativePath(queryPath, path);
10047 var op = new Overwrite(newOperationSourceServerTaggedQuery(queryId), relativePath, snap);
10048 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10049 }
10050 else {
10051 // Query must have been removed already
10052 return [];
10053 }
10054}
10055/**
10056 * Apply server data to be merged in for the specified tagged query.
10057 *
10058 * @returns Events to raise.
10059 */
10060function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
10061 var queryKey = syncTreeQueryKeyForTag_(syncTree, tag);
10062 if (queryKey) {
10063 var r = syncTreeParseQueryKey_(queryKey);
10064 var queryPath = r.path, queryId = r.queryId;
10065 var relativePath = newRelativePath(queryPath, path);
10066 var changeTree = ImmutableTree.fromObject(changedChildren);
10067 var op = new Merge(newOperationSourceServerTaggedQuery(queryId), relativePath, changeTree);
10068 return syncTreeApplyTaggedOperation_(syncTree, queryPath, op);
10069 }
10070 else {
10071 // We've already removed the query. No big deal, ignore the update
10072 return [];
10073 }
10074}
10075/**
10076 * Add an event callback for the specified query.
10077 *
10078 * @returns Events to raise.
10079 */
10080function syncTreeAddEventRegistration(syncTree, query, eventRegistration, skipSetupListener) {
10081 if (skipSetupListener === void 0) { skipSetupListener = false; }
10082 var path = query._path;
10083 var serverCache = null;
10084 var foundAncestorDefaultView = false;
10085 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10086 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10087 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10088 var relativePath = newRelativePath(pathToSyncPoint, path);
10089 serverCache =
10090 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10091 foundAncestorDefaultView =
10092 foundAncestorDefaultView || syncPointHasCompleteView(sp);
10093 });
10094 var syncPoint = syncTree.syncPointTree_.get(path);
10095 if (!syncPoint) {
10096 syncPoint = new SyncPoint();
10097 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10098 }
10099 else {
10100 foundAncestorDefaultView =
10101 foundAncestorDefaultView || syncPointHasCompleteView(syncPoint);
10102 serverCache =
10103 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10104 }
10105 var serverCacheComplete;
10106 if (serverCache != null) {
10107 serverCacheComplete = true;
10108 }
10109 else {
10110 serverCacheComplete = false;
10111 serverCache = ChildrenNode.EMPTY_NODE;
10112 var subtree = syncTree.syncPointTree_.subtree(path);
10113 subtree.foreachChild(function (childName, childSyncPoint) {
10114 var completeCache = syncPointGetCompleteServerCache(childSyncPoint, newEmptyPath());
10115 if (completeCache) {
10116 serverCache = serverCache.updateImmediateChild(childName, completeCache);
10117 }
10118 });
10119 }
10120 var viewAlreadyExists = syncPointViewExistsForQuery(syncPoint, query);
10121 if (!viewAlreadyExists && !query._queryParams.loadsAllData()) {
10122 // We need to track a tag for this query
10123 var queryKey = syncTreeMakeQueryKey_(query);
10124 util.assert(!syncTree.queryToTagMap.has(queryKey), 'View does not exist, but we have a tag');
10125 var tag = syncTreeGetNextQueryTag_();
10126 syncTree.queryToTagMap.set(queryKey, tag);
10127 syncTree.tagToQueryMap.set(tag, queryKey);
10128 }
10129 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
10130 var events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
10131 if (!viewAlreadyExists && !foundAncestorDefaultView && !skipSetupListener) {
10132 var view = syncPointViewForQuery(syncPoint, query);
10133 events = events.concat(syncTreeSetupListener_(syncTree, query, view));
10134 }
10135 return events;
10136}
10137/**
10138 * Returns a complete cache, if we have one, of the data at a particular path. If the location does not have a
10139 * listener above it, we will get a false "null". This shouldn't be a problem because transactions will always
10140 * have a listener above, and atomic operations would correctly show a jitter of <increment value> ->
10141 * <incremented total> as the write is applied locally and then acknowledged at the server.
10142 *
10143 * Note: this method will *include* hidden writes from transaction with applyLocally set to false.
10144 *
10145 * @param path - The path to the data we want
10146 * @param writeIdsToExclude - A specific set to be excluded
10147 */
10148function syncTreeCalcCompleteEventCache(syncTree, path, writeIdsToExclude) {
10149 var includeHiddenSets = true;
10150 var writeTree = syncTree.pendingWriteTree_;
10151 var serverCache = syncTree.syncPointTree_.findOnPath(path, function (pathSoFar, syncPoint) {
10152 var relativePath = newRelativePath(pathSoFar, path);
10153 var serverCache = syncPointGetCompleteServerCache(syncPoint, relativePath);
10154 if (serverCache) {
10155 return serverCache;
10156 }
10157 });
10158 return writeTreeCalcCompleteEventCache(writeTree, path, serverCache, writeIdsToExclude, includeHiddenSets);
10159}
10160function syncTreeGetServerValue(syncTree, query) {
10161 var path = query._path;
10162 var serverCache = null;
10163 // Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
10164 // Consider optimizing this once there's a better understanding of what actual behavior will be.
10165 syncTree.syncPointTree_.foreachOnPath(path, function (pathToSyncPoint, sp) {
10166 var relativePath = newRelativePath(pathToSyncPoint, path);
10167 serverCache =
10168 serverCache || syncPointGetCompleteServerCache(sp, relativePath);
10169 });
10170 var syncPoint = syncTree.syncPointTree_.get(path);
10171 if (!syncPoint) {
10172 syncPoint = new SyncPoint();
10173 syncTree.syncPointTree_ = syncTree.syncPointTree_.set(path, syncPoint);
10174 }
10175 else {
10176 serverCache =
10177 serverCache || syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10178 }
10179 var serverCacheComplete = serverCache != null;
10180 var serverCacheNode = serverCacheComplete
10181 ? new CacheNode(serverCache, true, false)
10182 : null;
10183 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, query._path);
10184 var view = syncPointGetView(syncPoint, query, writesCache, serverCacheComplete ? serverCacheNode.getNode() : ChildrenNode.EMPTY_NODE, serverCacheComplete);
10185 return viewGetCompleteNode(view);
10186}
10187/**
10188 * A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
10189 *
10190 * NOTES:
10191 * - Descendant SyncPoints will be visited first (since we raise events depth-first).
10192 *
10193 * - We call applyOperation() on each SyncPoint passing three things:
10194 * 1. A version of the Operation that has been made relative to the SyncPoint location.
10195 * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
10196 * 3. A snapshot Node with cached server data, if we have it.
10197 *
10198 * - We concatenate all of the events returned by each SyncPoint and return the result.
10199 */
10200function syncTreeApplyOperationToSyncPoints_(syncTree, operation) {
10201 return syncTreeApplyOperationHelper_(operation, syncTree.syncPointTree_,
10202 /*serverCache=*/ null, writeTreeChildWrites(syncTree.pendingWriteTree_, newEmptyPath()));
10203}
10204/**
10205 * Recursive helper for applyOperationToSyncPoints_
10206 */
10207function syncTreeApplyOperationHelper_(operation, syncPointTree, serverCache, writesCache) {
10208 if (pathIsEmpty(operation.path)) {
10209 return syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache);
10210 }
10211 else {
10212 var syncPoint = syncPointTree.get(newEmptyPath());
10213 // If we don't have cached server data, see if we can get it from this SyncPoint.
10214 if (serverCache == null && syncPoint != null) {
10215 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10216 }
10217 var events = [];
10218 var childName = pathGetFront(operation.path);
10219 var childOperation = operation.operationForChild(childName);
10220 var childTree = syncPointTree.children.get(childName);
10221 if (childTree && childOperation) {
10222 var childServerCache = serverCache
10223 ? serverCache.getImmediateChild(childName)
10224 : null;
10225 var childWritesCache = writeTreeRefChild(writesCache, childName);
10226 events = events.concat(syncTreeApplyOperationHelper_(childOperation, childTree, childServerCache, childWritesCache));
10227 }
10228 if (syncPoint) {
10229 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10230 }
10231 return events;
10232 }
10233}
10234/**
10235 * Recursive helper for applyOperationToSyncPoints_
10236 */
10237function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serverCache, writesCache) {
10238 var syncPoint = syncPointTree.get(newEmptyPath());
10239 // If we don't have cached server data, see if we can get it from this SyncPoint.
10240 if (serverCache == null && syncPoint != null) {
10241 serverCache = syncPointGetCompleteServerCache(syncPoint, newEmptyPath());
10242 }
10243 var events = [];
10244 syncPointTree.children.inorderTraversal(function (childName, childTree) {
10245 var childServerCache = serverCache
10246 ? serverCache.getImmediateChild(childName)
10247 : null;
10248 var childWritesCache = writeTreeRefChild(writesCache, childName);
10249 var childOperation = operation.operationForChild(childName);
10250 if (childOperation) {
10251 events = events.concat(syncTreeApplyOperationDescendantsHelper_(childOperation, childTree, childServerCache, childWritesCache));
10252 }
10253 });
10254 if (syncPoint) {
10255 events = events.concat(syncPointApplyOperation(syncPoint, operation, writesCache, serverCache));
10256 }
10257 return events;
10258}
10259function syncTreeCreateListenerForView_(syncTree, view) {
10260 var query = view.query;
10261 var tag = syncTreeTagForQuery(syncTree, query);
10262 return {
10263 hashFn: function () {
10264 var cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
10265 return cache.hash();
10266 },
10267 onComplete: function (status) {
10268 if (status === 'ok') {
10269 if (tag) {
10270 return syncTreeApplyTaggedListenComplete(syncTree, query._path, tag);
10271 }
10272 else {
10273 return syncTreeApplyListenComplete(syncTree, query._path);
10274 }
10275 }
10276 else {
10277 // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
10278 // Note that this may need to be scoped to just this listener if we change permissions on filtered children
10279 var error = errorForServerCode(status, query);
10280 return syncTreeRemoveEventRegistration(syncTree, query,
10281 /*eventRegistration*/ null, error);
10282 }
10283 }
10284 };
10285}
10286/**
10287 * Return the tag associated with the given query.
10288 */
10289function syncTreeTagForQuery(syncTree, query) {
10290 var queryKey = syncTreeMakeQueryKey_(query);
10291 return syncTree.queryToTagMap.get(queryKey);
10292}
10293/**
10294 * Given a query, computes a "queryKey" suitable for use in our queryToTagMap_.
10295 */
10296function syncTreeMakeQueryKey_(query) {
10297 return query._path.toString() + '$' + query._queryIdentifier;
10298}
10299/**
10300 * Return the query associated with the given tag, if we have one
10301 */
10302function syncTreeQueryKeyForTag_(syncTree, tag) {
10303 return syncTree.tagToQueryMap.get(tag);
10304}
10305/**
10306 * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId.
10307 */
10308function syncTreeParseQueryKey_(queryKey) {
10309 var splitIndex = queryKey.indexOf('$');
10310 util.assert(splitIndex !== -1 && splitIndex < queryKey.length - 1, 'Bad queryKey.');
10311 return {
10312 queryId: queryKey.substr(splitIndex + 1),
10313 path: new Path(queryKey.substr(0, splitIndex))
10314 };
10315}
10316/**
10317 * A helper method to apply tagged operations
10318 */
10319function syncTreeApplyTaggedOperation_(syncTree, queryPath, operation) {
10320 var syncPoint = syncTree.syncPointTree_.get(queryPath);
10321 util.assert(syncPoint, "Missing sync point for query tag that we're tracking");
10322 var writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, queryPath);
10323 return syncPointApplyOperation(syncPoint, operation, writesCache, null);
10324}
10325/**
10326 * This collapses multiple unfiltered views into a single view, since we only need a single
10327 * listener for them.
10328 */
10329function syncTreeCollectDistinctViewsForSubTree_(subtree) {
10330 return subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10331 if (maybeChildSyncPoint && syncPointHasCompleteView(maybeChildSyncPoint)) {
10332 var completeView = syncPointGetCompleteView(maybeChildSyncPoint);
10333 return [completeView];
10334 }
10335 else {
10336 // No complete view here, flatten any deeper listens into an array
10337 var views_1 = [];
10338 if (maybeChildSyncPoint) {
10339 views_1 = syncPointGetQueryViews(maybeChildSyncPoint);
10340 }
10341 each(childMap, function (_key, childViews) {
10342 views_1 = views_1.concat(childViews);
10343 });
10344 return views_1;
10345 }
10346 });
10347}
10348/**
10349 * Normalizes a query to a query we send the server for listening
10350 *
10351 * @returns The normalized query
10352 */
10353function syncTreeQueryForListening_(query) {
10354 if (query._queryParams.loadsAllData() && !query._queryParams.isDefault()) {
10355 // We treat queries that load all data as default queries
10356 // Cast is necessary because ref() technically returns Firebase which is actually fb.api.Firebase which inherits
10357 // from Query
10358 return new (syncTreeGetReferenceConstructor())(query._repo, query._path);
10359 }
10360 else {
10361 return query;
10362 }
10363}
10364function syncTreeRemoveTags_(syncTree, queries) {
10365 for (var j = 0; j < queries.length; ++j) {
10366 var removedQuery = queries[j];
10367 if (!removedQuery._queryParams.loadsAllData()) {
10368 // We should have a tag for this
10369 var removedQueryKey = syncTreeMakeQueryKey_(removedQuery);
10370 var removedQueryTag = syncTree.queryToTagMap.get(removedQueryKey);
10371 syncTree.queryToTagMap.delete(removedQueryKey);
10372 syncTree.tagToQueryMap.delete(removedQueryTag);
10373 }
10374 }
10375}
10376/**
10377 * Static accessor for query tags.
10378 */
10379function syncTreeGetNextQueryTag_() {
10380 return syncTreeNextQueryTag_++;
10381}
10382/**
10383 * For a given new listen, manage the de-duplication of outstanding subscriptions.
10384 *
10385 * @returns This method can return events to support synchronous data sources
10386 */
10387function syncTreeSetupListener_(syncTree, query, view) {
10388 var path = query._path;
10389 var tag = syncTreeTagForQuery(syncTree, query);
10390 var listener = syncTreeCreateListenerForView_(syncTree, view);
10391 var events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10392 var subtree = syncTree.syncPointTree_.subtree(path);
10393 // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
10394 // may need to shadow other listens as well.
10395 if (tag) {
10396 util.assert(!syncPointHasCompleteView(subtree.value), "If we're adding a query, it shouldn't be shadowed");
10397 }
10398 else {
10399 // Shadow everything at or below this location, this is a default listener.
10400 var queriesToStop = subtree.fold(function (relativePath, maybeChildSyncPoint, childMap) {
10401 if (!pathIsEmpty(relativePath) &&
10402 maybeChildSyncPoint &&
10403 syncPointHasCompleteView(maybeChildSyncPoint)) {
10404 return [syncPointGetCompleteView(maybeChildSyncPoint).query];
10405 }
10406 else {
10407 // No default listener here, flatten any deeper queries into an array
10408 var queries_1 = [];
10409 if (maybeChildSyncPoint) {
10410 queries_1 = queries_1.concat(syncPointGetQueryViews(maybeChildSyncPoint).map(function (view) { return view.query; }));
10411 }
10412 each(childMap, function (_key, childQueries) {
10413 queries_1 = queries_1.concat(childQueries);
10414 });
10415 return queries_1;
10416 }
10417 });
10418 for (var i = 0; i < queriesToStop.length; ++i) {
10419 var queryToStop = queriesToStop[i];
10420 syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery(syncTree, queryToStop));
10421 }
10422 }
10423 return events;
10424}
10425
10426/**
10427 * @license
10428 * Copyright 2017 Google LLC
10429 *
10430 * Licensed under the Apache License, Version 2.0 (the "License");
10431 * you may not use this file except in compliance with the License.
10432 * You may obtain a copy of the License at
10433 *
10434 * http://www.apache.org/licenses/LICENSE-2.0
10435 *
10436 * Unless required by applicable law or agreed to in writing, software
10437 * distributed under the License is distributed on an "AS IS" BASIS,
10438 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10439 * See the License for the specific language governing permissions and
10440 * limitations under the License.
10441 */
10442var ExistingValueProvider = /** @class */ (function () {
10443 function ExistingValueProvider(node_) {
10444 this.node_ = node_;
10445 }
10446 ExistingValueProvider.prototype.getImmediateChild = function (childName) {
10447 var child = this.node_.getImmediateChild(childName);
10448 return new ExistingValueProvider(child);
10449 };
10450 ExistingValueProvider.prototype.node = function () {
10451 return this.node_;
10452 };
10453 return ExistingValueProvider;
10454}());
10455var DeferredValueProvider = /** @class */ (function () {
10456 function DeferredValueProvider(syncTree, path) {
10457 this.syncTree_ = syncTree;
10458 this.path_ = path;
10459 }
10460 DeferredValueProvider.prototype.getImmediateChild = function (childName) {
10461 var childPath = pathChild(this.path_, childName);
10462 return new DeferredValueProvider(this.syncTree_, childPath);
10463 };
10464 DeferredValueProvider.prototype.node = function () {
10465 return syncTreeCalcCompleteEventCache(this.syncTree_, this.path_);
10466 };
10467 return DeferredValueProvider;
10468}());
10469/**
10470 * Generate placeholders for deferred values.
10471 */
10472var generateWithValues = function (values) {
10473 values = values || {};
10474 values['timestamp'] = values['timestamp'] || new Date().getTime();
10475 return values;
10476};
10477/**
10478 * Value to use when firing local events. When writing server values, fire
10479 * local events with an approximate value, otherwise return value as-is.
10480 */
10481var resolveDeferredLeafValue = function (value, existingVal, serverValues) {
10482 if (!value || typeof value !== 'object') {
10483 return value;
10484 }
10485 util.assert('.sv' in value, 'Unexpected leaf node or priority contents');
10486 if (typeof value['.sv'] === 'string') {
10487 return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
10488 }
10489 else if (typeof value['.sv'] === 'object') {
10490 return resolveComplexDeferredValue(value['.sv'], existingVal);
10491 }
10492 else {
10493 util.assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
10494 }
10495};
10496var resolveScalarDeferredValue = function (op, existing, serverValues) {
10497 switch (op) {
10498 case 'timestamp':
10499 return serverValues['timestamp'];
10500 default:
10501 util.assert(false, 'Unexpected server value: ' + op);
10502 }
10503};
10504var resolveComplexDeferredValue = function (op, existing, unused) {
10505 if (!op.hasOwnProperty('increment')) {
10506 util.assert(false, 'Unexpected server value: ' + JSON.stringify(op, null, 2));
10507 }
10508 var delta = op['increment'];
10509 if (typeof delta !== 'number') {
10510 util.assert(false, 'Unexpected increment value: ' + delta);
10511 }
10512 var existingNode = existing.node();
10513 util.assert(existingNode !== null && typeof existingNode !== 'undefined', 'Expected ChildrenNode.EMPTY_NODE for nulls');
10514 // Incrementing a non-number sets the value to the incremented amount
10515 if (!existingNode.isLeafNode()) {
10516 return delta;
10517 }
10518 var leaf = existingNode;
10519 var existingVal = leaf.getValue();
10520 if (typeof existingVal !== 'number') {
10521 return delta;
10522 }
10523 // No need to do over/underflow arithmetic here because JS only handles floats under the covers
10524 return existingVal + delta;
10525};
10526/**
10527 * Recursively replace all deferred values and priorities in the tree with the
10528 * specified generated replacement values.
10529 * @param path - path to which write is relative
10530 * @param node - new data written at path
10531 * @param syncTree - current data
10532 */
10533var resolveDeferredValueTree = function (path, node, syncTree, serverValues) {
10534 return resolveDeferredValue(node, new DeferredValueProvider(syncTree, path), serverValues);
10535};
10536/**
10537 * Recursively replace all deferred values and priorities in the node with the
10538 * specified generated replacement values. If there are no server values in the node,
10539 * it'll be returned as-is.
10540 */
10541var resolveDeferredValueSnapshot = function (node, existing, serverValues) {
10542 return resolveDeferredValue(node, new ExistingValueProvider(existing), serverValues);
10543};
10544function resolveDeferredValue(node, existingVal, serverValues) {
10545 var rawPri = node.getPriority().val();
10546 var priority = resolveDeferredLeafValue(rawPri, existingVal.getImmediateChild('.priority'), serverValues);
10547 var newNode;
10548 if (node.isLeafNode()) {
10549 var leafNode = node;
10550 var value = resolveDeferredLeafValue(leafNode.getValue(), existingVal, serverValues);
10551 if (value !== leafNode.getValue() ||
10552 priority !== leafNode.getPriority().val()) {
10553 return new LeafNode(value, nodeFromJSON(priority));
10554 }
10555 else {
10556 return node;
10557 }
10558 }
10559 else {
10560 var childrenNode = node;
10561 newNode = childrenNode;
10562 if (priority !== childrenNode.getPriority().val()) {
10563 newNode = newNode.updatePriority(new LeafNode(priority));
10564 }
10565 childrenNode.forEachChild(PRIORITY_INDEX, function (childName, childNode) {
10566 var newChildNode = resolveDeferredValue(childNode, existingVal.getImmediateChild(childName), serverValues);
10567 if (newChildNode !== childNode) {
10568 newNode = newNode.updateImmediateChild(childName, newChildNode);
10569 }
10570 });
10571 return newNode;
10572 }
10573}
10574
10575/**
10576 * @license
10577 * Copyright 2017 Google LLC
10578 *
10579 * Licensed under the Apache License, Version 2.0 (the "License");
10580 * you may not use this file except in compliance with the License.
10581 * You may obtain a copy of the License at
10582 *
10583 * http://www.apache.org/licenses/LICENSE-2.0
10584 *
10585 * Unless required by applicable law or agreed to in writing, software
10586 * distributed under the License is distributed on an "AS IS" BASIS,
10587 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10588 * See the License for the specific language governing permissions and
10589 * limitations under the License.
10590 */
10591/**
10592 * A light-weight tree, traversable by path. Nodes can have both values and children.
10593 * Nodes are not enumerated (by forEachChild) unless they have a value or non-empty
10594 * children.
10595 */
10596var Tree = /** @class */ (function () {
10597 /**
10598 * @param name - Optional name of the node.
10599 * @param parent - Optional parent node.
10600 * @param node - Optional node to wrap.
10601 */
10602 function Tree(name, parent, node) {
10603 if (name === void 0) { name = ''; }
10604 if (parent === void 0) { parent = null; }
10605 if (node === void 0) { node = { children: {}, childCount: 0 }; }
10606 this.name = name;
10607 this.parent = parent;
10608 this.node = node;
10609 }
10610 return Tree;
10611}());
10612/**
10613 * Returns a sub-Tree for the given path.
10614 *
10615 * @param pathObj - Path to look up.
10616 * @returns Tree for path.
10617 */
10618function treeSubTree(tree, pathObj) {
10619 // TODO: Require pathObj to be Path?
10620 var path = pathObj instanceof Path ? pathObj : new Path(pathObj);
10621 var child = tree, next = pathGetFront(path);
10622 while (next !== null) {
10623 var childNode = util.safeGet(child.node.children, next) || {
10624 children: {},
10625 childCount: 0
10626 };
10627 child = new Tree(next, child, childNode);
10628 path = pathPopFront(path);
10629 next = pathGetFront(path);
10630 }
10631 return child;
10632}
10633/**
10634 * Returns the data associated with this tree node.
10635 *
10636 * @returns The data or null if no data exists.
10637 */
10638function treeGetValue(tree) {
10639 return tree.node.value;
10640}
10641/**
10642 * Sets data to this tree node.
10643 *
10644 * @param value - Value to set.
10645 */
10646function treeSetValue(tree, value) {
10647 tree.node.value = value;
10648 treeUpdateParents(tree);
10649}
10650/**
10651 * @returns Whether the tree has any children.
10652 */
10653function treeHasChildren(tree) {
10654 return tree.node.childCount > 0;
10655}
10656/**
10657 * @returns Whethe rthe tree is empty (no value or children).
10658 */
10659function treeIsEmpty(tree) {
10660 return treeGetValue(tree) === undefined && !treeHasChildren(tree);
10661}
10662/**
10663 * Calls action for each child of this tree node.
10664 *
10665 * @param action - Action to be called for each child.
10666 */
10667function treeForEachChild(tree, action) {
10668 each(tree.node.children, function (child, childTree) {
10669 action(new Tree(child, tree, childTree));
10670 });
10671}
10672/**
10673 * Does a depth-first traversal of this node's descendants, calling action for each one.
10674 *
10675 * @param action - Action to be called for each child.
10676 * @param includeSelf - Whether to call action on this node as well. Defaults to
10677 * false.
10678 * @param childrenFirst - Whether to call action on children before calling it on
10679 * parent.
10680 */
10681function treeForEachDescendant(tree, action, includeSelf, childrenFirst) {
10682 if (includeSelf && !childrenFirst) {
10683 action(tree);
10684 }
10685 treeForEachChild(tree, function (child) {
10686 treeForEachDescendant(child, action, true, childrenFirst);
10687 });
10688 if (includeSelf && childrenFirst) {
10689 action(tree);
10690 }
10691}
10692/**
10693 * Calls action on each ancestor node.
10694 *
10695 * @param action - Action to be called on each parent; return
10696 * true to abort.
10697 * @param includeSelf - Whether to call action on this node as well.
10698 * @returns true if the action callback returned true.
10699 */
10700function treeForEachAncestor(tree, action, includeSelf) {
10701 var node = includeSelf ? tree : tree.parent;
10702 while (node !== null) {
10703 if (action(node)) {
10704 return true;
10705 }
10706 node = node.parent;
10707 }
10708 return false;
10709}
10710/**
10711 * @returns The path of this tree node, as a Path.
10712 */
10713function treeGetPath(tree) {
10714 return new Path(tree.parent === null
10715 ? tree.name
10716 : treeGetPath(tree.parent) + '/' + tree.name);
10717}
10718/**
10719 * Adds or removes this child from its parent based on whether it's empty or not.
10720 */
10721function treeUpdateParents(tree) {
10722 if (tree.parent !== null) {
10723 treeUpdateChild(tree.parent, tree.name, tree);
10724 }
10725}
10726/**
10727 * Adds or removes the passed child to this tree node, depending on whether it's empty.
10728 *
10729 * @param childName - The name of the child to update.
10730 * @param child - The child to update.
10731 */
10732function treeUpdateChild(tree, childName, child) {
10733 var childEmpty = treeIsEmpty(child);
10734 var childExists = util.contains(tree.node.children, childName);
10735 if (childEmpty && childExists) {
10736 delete tree.node.children[childName];
10737 tree.node.childCount--;
10738 treeUpdateParents(tree);
10739 }
10740 else if (!childEmpty && !childExists) {
10741 tree.node.children[childName] = child.node;
10742 tree.node.childCount++;
10743 treeUpdateParents(tree);
10744 }
10745}
10746
10747/**
10748 * @license
10749 * Copyright 2017 Google LLC
10750 *
10751 * Licensed under the Apache License, Version 2.0 (the "License");
10752 * you may not use this file except in compliance with the License.
10753 * You may obtain a copy of the License at
10754 *
10755 * http://www.apache.org/licenses/LICENSE-2.0
10756 *
10757 * Unless required by applicable law or agreed to in writing, software
10758 * distributed under the License is distributed on an "AS IS" BASIS,
10759 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10760 * See the License for the specific language governing permissions and
10761 * limitations under the License.
10762 */
10763/**
10764 * True for invalid Firebase keys
10765 */
10766var INVALID_KEY_REGEX_ = /[\[\].#$\/\u0000-\u001F\u007F]/;
10767/**
10768 * True for invalid Firebase paths.
10769 * Allows '/' in paths.
10770 */
10771var INVALID_PATH_REGEX_ = /[\[\].#$\u0000-\u001F\u007F]/;
10772/**
10773 * Maximum number of characters to allow in leaf value
10774 */
10775var MAX_LEAF_SIZE_ = 10 * 1024 * 1024;
10776var isValidKey = function (key) {
10777 return (typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX_.test(key));
10778};
10779var isValidPathString = function (pathString) {
10780 return (typeof pathString === 'string' &&
10781 pathString.length !== 0 &&
10782 !INVALID_PATH_REGEX_.test(pathString));
10783};
10784var isValidRootPathString = function (pathString) {
10785 if (pathString) {
10786 // Allow '/.info/' at the beginning.
10787 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10788 }
10789 return isValidPathString(pathString);
10790};
10791var isValidPriority = function (priority) {
10792 return (priority === null ||
10793 typeof priority === 'string' ||
10794 (typeof priority === 'number' && !isInvalidJSONNumber(priority)) ||
10795 (priority &&
10796 typeof priority === 'object' &&
10797 // eslint-disable-next-line @typescript-eslint/no-explicit-any
10798 util.contains(priority, '.sv')));
10799};
10800/**
10801 * Pre-validate a datum passed as an argument to Firebase function.
10802 */
10803var validateFirebaseDataArg = function (fnName, value, path, optional) {
10804 if (optional && value === undefined) {
10805 return;
10806 }
10807 validateFirebaseData(util.errorPrefix(fnName, 'value'), value, path);
10808};
10809/**
10810 * Validate a data object client-side before sending to server.
10811 */
10812var validateFirebaseData = function (errorPrefix, data, path_) {
10813 var path = path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;
10814 if (data === undefined) {
10815 throw new Error(errorPrefix + 'contains undefined ' + validationPathToErrorString(path));
10816 }
10817 if (typeof data === 'function') {
10818 throw new Error(errorPrefix +
10819 'contains a function ' +
10820 validationPathToErrorString(path) +
10821 ' with contents = ' +
10822 data.toString());
10823 }
10824 if (isInvalidJSONNumber(data)) {
10825 throw new Error(errorPrefix +
10826 'contains ' +
10827 data.toString() +
10828 ' ' +
10829 validationPathToErrorString(path));
10830 }
10831 // Check max leaf size, but try to avoid the utf8 conversion if we can.
10832 if (typeof data === 'string' &&
10833 data.length > MAX_LEAF_SIZE_ / 3 &&
10834 util.stringLength(data) > MAX_LEAF_SIZE_) {
10835 throw new Error(errorPrefix +
10836 'contains a string greater than ' +
10837 MAX_LEAF_SIZE_ +
10838 ' utf8 bytes ' +
10839 validationPathToErrorString(path) +
10840 " ('" +
10841 data.substring(0, 50) +
10842 "...')");
10843 }
10844 // TODO = Perf = Consider combining the recursive validation of keys into NodeFromJSON
10845 // to save extra walking of large objects.
10846 if (data && typeof data === 'object') {
10847 var hasDotValue_1 = false;
10848 var hasActualChild_1 = false;
10849 each(data, function (key, value) {
10850 if (key === '.value') {
10851 hasDotValue_1 = true;
10852 }
10853 else if (key !== '.priority' && key !== '.sv') {
10854 hasActualChild_1 = true;
10855 if (!isValidKey(key)) {
10856 throw new Error(errorPrefix +
10857 ' contains an invalid key (' +
10858 key +
10859 ') ' +
10860 validationPathToErrorString(path) +
10861 '. Keys must be non-empty strings ' +
10862 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
10863 }
10864 }
10865 validationPathPush(path, key);
10866 validateFirebaseData(errorPrefix, value, path);
10867 validationPathPop(path);
10868 });
10869 if (hasDotValue_1 && hasActualChild_1) {
10870 throw new Error(errorPrefix +
10871 ' contains ".value" child ' +
10872 validationPathToErrorString(path) +
10873 ' in addition to actual children.');
10874 }
10875 }
10876};
10877/**
10878 * Pre-validate paths passed in the firebase function.
10879 */
10880var validateFirebaseMergePaths = function (errorPrefix, mergePaths) {
10881 var i, curPath;
10882 for (i = 0; i < mergePaths.length; i++) {
10883 curPath = mergePaths[i];
10884 var keys = pathSlice(curPath);
10885 for (var j = 0; j < keys.length; j++) {
10886 if (keys[j] === '.priority' && j === keys.length - 1) ;
10887 else if (!isValidKey(keys[j])) {
10888 throw new Error(errorPrefix +
10889 'contains an invalid key (' +
10890 keys[j] +
10891 ') in path ' +
10892 curPath.toString() +
10893 '. Keys must be non-empty strings ' +
10894 'and can\'t contain ".", "#", "$", "/", "[", or "]"');
10895 }
10896 }
10897 }
10898 // Check that update keys are not descendants of each other.
10899 // We rely on the property that sorting guarantees that ancestors come
10900 // right before descendants.
10901 mergePaths.sort(pathCompare);
10902 var prevPath = null;
10903 for (i = 0; i < mergePaths.length; i++) {
10904 curPath = mergePaths[i];
10905 if (prevPath !== null && pathContains(prevPath, curPath)) {
10906 throw new Error(errorPrefix +
10907 'contains a path ' +
10908 prevPath.toString() +
10909 ' that is ancestor of another path ' +
10910 curPath.toString());
10911 }
10912 prevPath = curPath;
10913 }
10914};
10915/**
10916 * pre-validate an object passed as an argument to firebase function (
10917 * must be an object - e.g. for firebase.update()).
10918 */
10919var validateFirebaseMergeDataArg = function (fnName, data, path, optional) {
10920 if (optional && data === undefined) {
10921 return;
10922 }
10923 var errorPrefix = util.errorPrefix(fnName, 'values');
10924 if (!(data && typeof data === 'object') || Array.isArray(data)) {
10925 throw new Error(errorPrefix + ' must be an object containing the children to replace.');
10926 }
10927 var mergePaths = [];
10928 each(data, function (key, value) {
10929 var curPath = new Path(key);
10930 validateFirebaseData(errorPrefix, value, pathChild(path, curPath));
10931 if (pathGetBack(curPath) === '.priority') {
10932 if (!isValidPriority(value)) {
10933 throw new Error(errorPrefix +
10934 "contains an invalid value for '" +
10935 curPath.toString() +
10936 "', which must be a valid " +
10937 'Firebase priority (a string, finite number, server value, or null).');
10938 }
10939 }
10940 mergePaths.push(curPath);
10941 });
10942 validateFirebaseMergePaths(errorPrefix, mergePaths);
10943};
10944var validatePriority = function (fnName, priority, optional) {
10945 if (optional && priority === undefined) {
10946 return;
10947 }
10948 if (isInvalidJSONNumber(priority)) {
10949 throw new Error(util.errorPrefix(fnName, 'priority') +
10950 'is ' +
10951 priority.toString() +
10952 ', but must be a valid Firebase priority (a string, finite number, ' +
10953 'server value, or null).');
10954 }
10955 // Special case to allow importing data with a .sv.
10956 if (!isValidPriority(priority)) {
10957 throw new Error(util.errorPrefix(fnName, 'priority') +
10958 'must be a valid Firebase priority ' +
10959 '(a string, finite number, server value, or null).');
10960 }
10961};
10962var validateKey = function (fnName, argumentName, key, optional) {
10963 if (optional && key === undefined) {
10964 return;
10965 }
10966 if (!isValidKey(key)) {
10967 throw new Error(util.errorPrefix(fnName, argumentName) +
10968 'was an invalid key = "' +
10969 key +
10970 '". Firebase keys must be non-empty strings and ' +
10971 'can\'t contain ".", "#", "$", "/", "[", or "]").');
10972 }
10973};
10974/**
10975 * @internal
10976 */
10977var validatePathString = function (fnName, argumentName, pathString, optional) {
10978 if (optional && pathString === undefined) {
10979 return;
10980 }
10981 if (!isValidPathString(pathString)) {
10982 throw new Error(util.errorPrefix(fnName, argumentName) +
10983 'was an invalid path = "' +
10984 pathString +
10985 '". Paths must be non-empty strings and ' +
10986 'can\'t contain ".", "#", "$", "[", or "]"');
10987 }
10988};
10989var validateRootPathString = function (fnName, argumentName, pathString, optional) {
10990 if (pathString) {
10991 // Allow '/.info/' at the beginning.
10992 pathString = pathString.replace(/^\/*\.info(\/|$)/, '/');
10993 }
10994 validatePathString(fnName, argumentName, pathString, optional);
10995};
10996/**
10997 * @internal
10998 */
10999var validateWritablePath = function (fnName, path) {
11000 if (pathGetFront(path) === '.info') {
11001 throw new Error(fnName + " failed = Can't modify data under /.info/");
11002 }
11003};
11004var validateUrl = function (fnName, parsedUrl) {
11005 // TODO = Validate server better.
11006 var pathString = parsedUrl.path.toString();
11007 if (!(typeof parsedUrl.repoInfo.host === 'string') ||
11008 parsedUrl.repoInfo.host.length === 0 ||
11009 (!isValidKey(parsedUrl.repoInfo.namespace) &&
11010 parsedUrl.repoInfo.host.split(':')[0] !== 'localhost') ||
11011 (pathString.length !== 0 && !isValidRootPathString(pathString))) {
11012 throw new Error(util.errorPrefix(fnName, 'url') +
11013 'must be a valid firebase URL and ' +
11014 'the path can\'t contain ".", "#", "$", "[", or "]".');
11015 }
11016};
11017
11018/**
11019 * @license
11020 * Copyright 2017 Google LLC
11021 *
11022 * Licensed under the Apache License, Version 2.0 (the "License");
11023 * you may not use this file except in compliance with the License.
11024 * You may obtain a copy of the License at
11025 *
11026 * http://www.apache.org/licenses/LICENSE-2.0
11027 *
11028 * Unless required by applicable law or agreed to in writing, software
11029 * distributed under the License is distributed on an "AS IS" BASIS,
11030 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11031 * See the License for the specific language governing permissions and
11032 * limitations under the License.
11033 */
11034/**
11035 * The event queue serves a few purposes:
11036 * 1. It ensures we maintain event order in the face of event callbacks doing operations that result in more
11037 * events being queued.
11038 * 2. raiseQueuedEvents() handles being called reentrantly nicely. That is, if in the course of raising events,
11039 * raiseQueuedEvents() is called again, the "inner" call will pick up raising events where the "outer" call
11040 * left off, ensuring that the events are still raised synchronously and in order.
11041 * 3. You can use raiseEventsAtPath and raiseEventsForChangedPath to ensure only relevant previously-queued
11042 * events are raised synchronously.
11043 *
11044 * NOTE: This can all go away if/when we move to async events.
11045 *
11046 */
11047var EventQueue = /** @class */ (function () {
11048 function EventQueue() {
11049 this.eventLists_ = [];
11050 /**
11051 * Tracks recursion depth of raiseQueuedEvents_, for debugging purposes.
11052 */
11053 this.recursionDepth_ = 0;
11054 }
11055 return EventQueue;
11056}());
11057/**
11058 * @param eventDataList - The new events to queue.
11059 */
11060function eventQueueQueueEvents(eventQueue, eventDataList) {
11061 // We group events by path, storing them in a single EventList, to make it easier to skip over them quickly.
11062 var currList = null;
11063 for (var i = 0; i < eventDataList.length; i++) {
11064 var data = eventDataList[i];
11065 var path = data.getPath();
11066 if (currList !== null && !pathEquals(path, currList.path)) {
11067 eventQueue.eventLists_.push(currList);
11068 currList = null;
11069 }
11070 if (currList === null) {
11071 currList = { events: [], path: path };
11072 }
11073 currList.events.push(data);
11074 }
11075 if (currList) {
11076 eventQueue.eventLists_.push(currList);
11077 }
11078}
11079/**
11080 * Queues the specified events and synchronously raises all events (including previously queued ones)
11081 * for the specified path.
11082 *
11083 * It is assumed that the new events are all for the specified path.
11084 *
11085 * @param path - The path to raise events for.
11086 * @param eventDataList - The new events to raise.
11087 */
11088function eventQueueRaiseEventsAtPath(eventQueue, path, eventDataList) {
11089 eventQueueQueueEvents(eventQueue, eventDataList);
11090 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11091 return pathEquals(eventPath, path);
11092 });
11093}
11094/**
11095 * Queues the specified events and synchronously raises all events (including previously queued ones) for
11096 * locations related to the specified change path (i.e. all ancestors and descendants).
11097 *
11098 * It is assumed that the new events are all related (ancestor or descendant) to the specified path.
11099 *
11100 * @param changedPath - The path to raise events for.
11101 * @param eventDataList - The events to raise
11102 */
11103function eventQueueRaiseEventsForChangedPath(eventQueue, changedPath, eventDataList) {
11104 eventQueueQueueEvents(eventQueue, eventDataList);
11105 eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, function (eventPath) {
11106 return pathContains(eventPath, changedPath) ||
11107 pathContains(changedPath, eventPath);
11108 });
11109}
11110function eventQueueRaiseQueuedEventsMatchingPredicate(eventQueue, predicate) {
11111 eventQueue.recursionDepth_++;
11112 var sentAll = true;
11113 for (var i = 0; i < eventQueue.eventLists_.length; i++) {
11114 var eventList = eventQueue.eventLists_[i];
11115 if (eventList) {
11116 var eventPath = eventList.path;
11117 if (predicate(eventPath)) {
11118 eventListRaise(eventQueue.eventLists_[i]);
11119 eventQueue.eventLists_[i] = null;
11120 }
11121 else {
11122 sentAll = false;
11123 }
11124 }
11125 }
11126 if (sentAll) {
11127 eventQueue.eventLists_ = [];
11128 }
11129 eventQueue.recursionDepth_--;
11130}
11131/**
11132 * Iterates through the list and raises each event
11133 */
11134function eventListRaise(eventList) {
11135 for (var i = 0; i < eventList.events.length; i++) {
11136 var eventData = eventList.events[i];
11137 if (eventData !== null) {
11138 eventList.events[i] = null;
11139 var eventFn = eventData.getEventRunner();
11140 if (logger) {
11141 log('event: ' + eventData.toString());
11142 }
11143 exceptionGuard(eventFn);
11144 }
11145 }
11146}
11147
11148/**
11149 * @license
11150 * Copyright 2017 Google LLC
11151 *
11152 * Licensed under the Apache License, Version 2.0 (the "License");
11153 * you may not use this file except in compliance with the License.
11154 * You may obtain a copy of the License at
11155 *
11156 * http://www.apache.org/licenses/LICENSE-2.0
11157 *
11158 * Unless required by applicable law or agreed to in writing, software
11159 * distributed under the License is distributed on an "AS IS" BASIS,
11160 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11161 * See the License for the specific language governing permissions and
11162 * limitations under the License.
11163 */
11164var INTERRUPT_REASON = 'repo_interrupt';
11165/**
11166 * If a transaction does not succeed after 25 retries, we abort it. Among other
11167 * things this ensure that if there's ever a bug causing a mismatch between
11168 * client / server hashes for some data, we won't retry indefinitely.
11169 */
11170var MAX_TRANSACTION_RETRIES = 25;
11171/**
11172 * A connection to a single data repository.
11173 */
11174var Repo = /** @class */ (function () {
11175 function Repo(repoInfo_, forceRestClient_, authTokenProvider_, appCheckProvider_) {
11176 this.repoInfo_ = repoInfo_;
11177 this.forceRestClient_ = forceRestClient_;
11178 this.authTokenProvider_ = authTokenProvider_;
11179 this.appCheckProvider_ = appCheckProvider_;
11180 this.dataUpdateCount = 0;
11181 this.statsListener_ = null;
11182 this.eventQueue_ = new EventQueue();
11183 this.nextWriteId_ = 1;
11184 this.interceptServerDataCallback_ = null;
11185 /** A list of data pieces and paths to be set when this client disconnects. */
11186 this.onDisconnect_ = newSparseSnapshotTree();
11187 /** Stores queues of outstanding transactions for Firebase locations. */
11188 this.transactionQueueTree_ = new Tree();
11189 // TODO: This should be @private but it's used by test_access.js and internal.js
11190 this.persistentConnection_ = null;
11191 // This key is intentionally not updated if RepoInfo is later changed or replaced
11192 this.key = this.repoInfo_.toURLString();
11193 }
11194 /**
11195 * @returns The URL corresponding to the root of this Firebase.
11196 */
11197 Repo.prototype.toString = function () {
11198 return ((this.repoInfo_.secure ? 'https://' : 'http://') + this.repoInfo_.host);
11199 };
11200 return Repo;
11201}());
11202function repoStart(repo, appId, authOverride) {
11203 repo.stats_ = statsManagerGetCollection(repo.repoInfo_);
11204 if (repo.forceRestClient_ || beingCrawled()) {
11205 repo.server_ = new ReadonlyRestClient(repo.repoInfo_, function (pathString, data, isMerge, tag) {
11206 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11207 }, repo.authTokenProvider_, repo.appCheckProvider_);
11208 // Minor hack: Fire onConnect immediately, since there's no actual connection.
11209 setTimeout(function () { return repoOnConnectStatus(repo, /* connectStatus= */ true); }, 0);
11210 }
11211 else {
11212 // Validate authOverride
11213 if (typeof authOverride !== 'undefined' && authOverride !== null) {
11214 if (typeof authOverride !== 'object') {
11215 throw new Error('Only objects are supported for option databaseAuthVariableOverride');
11216 }
11217 try {
11218 util.stringify(authOverride);
11219 }
11220 catch (e) {
11221 throw new Error('Invalid authOverride provided: ' + e);
11222 }
11223 }
11224 repo.persistentConnection_ = new PersistentConnection(repo.repoInfo_, appId, function (pathString, data, isMerge, tag) {
11225 repoOnDataUpdate(repo, pathString, data, isMerge, tag);
11226 }, function (connectStatus) {
11227 repoOnConnectStatus(repo, connectStatus);
11228 }, function (updates) {
11229 repoOnServerInfoUpdate(repo, updates);
11230 }, repo.authTokenProvider_, repo.appCheckProvider_, authOverride);
11231 repo.server_ = repo.persistentConnection_;
11232 }
11233 repo.authTokenProvider_.addTokenChangeListener(function (token) {
11234 repo.server_.refreshAuthToken(token);
11235 });
11236 repo.appCheckProvider_.addTokenChangeListener(function (result) {
11237 repo.server_.refreshAppCheckToken(result.token);
11238 });
11239 // In the case of multiple Repos for the same repoInfo (i.e. there are multiple Firebase.Contexts being used),
11240 // we only want to create one StatsReporter. As such, we'll report stats over the first Repo created.
11241 repo.statsReporter_ = statsManagerGetOrCreateReporter(repo.repoInfo_, function () { return new StatsReporter(repo.stats_, repo.server_); });
11242 // Used for .info.
11243 repo.infoData_ = new SnapshotHolder();
11244 repo.infoSyncTree_ = new SyncTree({
11245 startListening: function (query, tag, currentHashFn, onComplete) {
11246 var infoEvents = [];
11247 var node = repo.infoData_.getNode(query._path);
11248 // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
11249 // on initial data...
11250 if (!node.isEmpty()) {
11251 infoEvents = syncTreeApplyServerOverwrite(repo.infoSyncTree_, query._path, node);
11252 setTimeout(function () {
11253 onComplete('ok');
11254 }, 0);
11255 }
11256 return infoEvents;
11257 },
11258 stopListening: function () { }
11259 });
11260 repoUpdateInfo(repo, 'connected', false);
11261 repo.serverSyncTree_ = new SyncTree({
11262 startListening: function (query, tag, currentHashFn, onComplete) {
11263 repo.server_.listen(query, currentHashFn, tag, function (status, data) {
11264 var events = onComplete(status, data);
11265 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11266 });
11267 // No synchronous events for network-backed sync trees
11268 return [];
11269 },
11270 stopListening: function (query, tag) {
11271 repo.server_.unlisten(query, tag);
11272 }
11273 });
11274}
11275/**
11276 * @returns The time in milliseconds, taking the server offset into account if we have one.
11277 */
11278function repoServerTime(repo) {
11279 var offsetNode = repo.infoData_.getNode(new Path('.info/serverTimeOffset'));
11280 var offset = offsetNode.val() || 0;
11281 return new Date().getTime() + offset;
11282}
11283/**
11284 * Generate ServerValues using some variables from the repo object.
11285 */
11286function repoGenerateServerValues(repo) {
11287 return generateWithValues({
11288 timestamp: repoServerTime(repo)
11289 });
11290}
11291/**
11292 * Called by realtime when we get new messages from the server.
11293 */
11294function repoOnDataUpdate(repo, pathString, data, isMerge, tag) {
11295 // For testing.
11296 repo.dataUpdateCount++;
11297 var path = new Path(pathString);
11298 data = repo.interceptServerDataCallback_
11299 ? repo.interceptServerDataCallback_(pathString, data)
11300 : data;
11301 var events = [];
11302 if (tag) {
11303 if (isMerge) {
11304 var taggedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11305 events = syncTreeApplyTaggedQueryMerge(repo.serverSyncTree_, path, taggedChildren, tag);
11306 }
11307 else {
11308 var taggedSnap = nodeFromJSON(data);
11309 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, path, taggedSnap, tag);
11310 }
11311 }
11312 else if (isMerge) {
11313 var changedChildren = util.map(data, function (raw) { return nodeFromJSON(raw); });
11314 events = syncTreeApplyServerMerge(repo.serverSyncTree_, path, changedChildren);
11315 }
11316 else {
11317 var snap = nodeFromJSON(data);
11318 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap);
11319 }
11320 var affectedPath = path;
11321 if (events.length > 0) {
11322 // Since we have a listener outstanding for each transaction, receiving any events
11323 // is a proxy for some change having occurred.
11324 affectedPath = repoRerunTransactions(repo, path);
11325 }
11326 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, events);
11327}
11328function repoOnConnectStatus(repo, connectStatus) {
11329 repoUpdateInfo(repo, 'connected', connectStatus);
11330 if (connectStatus === false) {
11331 repoRunOnDisconnectEvents(repo);
11332 }
11333}
11334function repoOnServerInfoUpdate(repo, updates) {
11335 each(updates, function (key, value) {
11336 repoUpdateInfo(repo, key, value);
11337 });
11338}
11339function repoUpdateInfo(repo, pathString, value) {
11340 var path = new Path('/.info/' + pathString);
11341 var newNode = nodeFromJSON(value);
11342 repo.infoData_.updateSnapshot(path, newNode);
11343 var events = syncTreeApplyServerOverwrite(repo.infoSyncTree_, path, newNode);
11344 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11345}
11346function repoGetNextWriteId(repo) {
11347 return repo.nextWriteId_++;
11348}
11349/**
11350 * The purpose of `getValue` is to return the latest known value
11351 * satisfying `query`.
11352 *
11353 * This method will first check for in-memory cached values
11354 * belonging to active listeners. If they are found, such values
11355 * are considered to be the most up-to-date.
11356 *
11357 * If the client is not connected, this method will wait until the
11358 * repo has established a connection and then request the value for `query`.
11359 * If the client is not able to retrieve the query result for another reason,
11360 * it reports an error.
11361 *
11362 * @param query - The query to surface a value for.
11363 */
11364function repoGetValue(repo, query, eventRegistration) {
11365 // Only active queries are cached. There is no persisted cache.
11366 var cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11367 if (cached != null) {
11368 return Promise.resolve(cached);
11369 }
11370 return repo.server_.get(query).then(function (payload) {
11371 var node = nodeFromJSON(payload).withIndex(query._queryParams.getIndex());
11372 /**
11373 * Below we simulate the actions of an `onlyOnce` `onValue()` event where:
11374 * Add an event registration,
11375 * Update data at the path,
11376 * Raise any events,
11377 * Cleanup the SyncTree
11378 */
11379 syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration, true);
11380 var events;
11381 if (query._queryParams.loadsAllData()) {
11382 events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11383 }
11384 else {
11385 var tag = syncTreeTagForQuery(repo.serverSyncTree_, query);
11386 events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, query._path, node, tag);
11387 }
11388 /*
11389 * We need to raise events in the scenario where `get()` is called at a parent path, and
11390 * while the `get()` is pending, `onValue` is called at a child location. While get() is waiting
11391 * for the data, `onValue` will register a new event. Then, get() will come back, and update the syncTree
11392 * and its corresponding serverCache, including the child location where `onValue` is called. Then,
11393 * `onValue` will receive the event from the server, but look at the syncTree and see that the data received
11394 * from the server is already at the SyncPoint, and so the `onValue` callback will never get fired.
11395 * Calling `eventQueueRaiseEventsForChangedPath()` is the correct way to propagate the events and
11396 * ensure the corresponding child events will get fired.
11397 */
11398 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11399 syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration, null, true);
11400 return node;
11401 }, function (err) {
11402 repoLog(repo, 'get for query ' + util.stringify(query) + ' failed: ' + err);
11403 return Promise.reject(new Error(err));
11404 });
11405}
11406function repoSetWithPriority(repo, path, newVal, newPriority, onComplete) {
11407 repoLog(repo, 'set', {
11408 path: path.toString(),
11409 value: newVal,
11410 priority: newPriority
11411 });
11412 // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
11413 // (b) store unresolved paths on JSON parse
11414 var serverValues = repoGenerateServerValues(repo);
11415 var newNodeUnresolved = nodeFromJSON(newVal, newPriority);
11416 var existing = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path);
11417 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, existing, serverValues);
11418 var writeId = repoGetNextWriteId(repo);
11419 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, writeId, true);
11420 eventQueueQueueEvents(repo.eventQueue_, events);
11421 repo.server_.put(path.toString(), newNodeUnresolved.val(/*export=*/ true), function (status, errorReason) {
11422 var success = status === 'ok';
11423 if (!success) {
11424 warn('set at ' + path + ' failed: ' + status);
11425 }
11426 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId, !success);
11427 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, clearEvents);
11428 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11429 });
11430 var affectedPath = repoAbortTransactions(repo, path);
11431 repoRerunTransactions(repo, affectedPath);
11432 // We queued the events above, so just flush the queue here
11433 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, []);
11434}
11435function repoUpdate(repo, path, childrenToMerge, onComplete) {
11436 repoLog(repo, 'update', { path: path.toString(), value: childrenToMerge });
11437 // Start with our existing data and merge each child into it.
11438 var empty = true;
11439 var serverValues = repoGenerateServerValues(repo);
11440 var changedChildren = {};
11441 each(childrenToMerge, function (changedKey, changedValue) {
11442 empty = false;
11443 changedChildren[changedKey] = resolveDeferredValueTree(pathChild(path, changedKey), nodeFromJSON(changedValue), repo.serverSyncTree_, serverValues);
11444 });
11445 if (!empty) {
11446 var writeId_1 = repoGetNextWriteId(repo);
11447 var events = syncTreeApplyUserMerge(repo.serverSyncTree_, path, changedChildren, writeId_1);
11448 eventQueueQueueEvents(repo.eventQueue_, events);
11449 repo.server_.merge(path.toString(), childrenToMerge, function (status, errorReason) {
11450 var success = status === 'ok';
11451 if (!success) {
11452 warn('update at ' + path + ' failed: ' + status);
11453 }
11454 var clearEvents = syncTreeAckUserWrite(repo.serverSyncTree_, writeId_1, !success);
11455 var affectedPath = clearEvents.length > 0 ? repoRerunTransactions(repo, path) : path;
11456 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, affectedPath, clearEvents);
11457 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11458 });
11459 each(childrenToMerge, function (changedPath) {
11460 var affectedPath = repoAbortTransactions(repo, pathChild(path, changedPath));
11461 repoRerunTransactions(repo, affectedPath);
11462 });
11463 // We queued the events above, so just flush the queue here
11464 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, []);
11465 }
11466 else {
11467 log("update() called with empty data. Don't do anything.");
11468 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11469 }
11470}
11471/**
11472 * Applies all of the changes stored up in the onDisconnect_ tree.
11473 */
11474function repoRunOnDisconnectEvents(repo) {
11475 repoLog(repo, 'onDisconnectEvents');
11476 var serverValues = repoGenerateServerValues(repo);
11477 var resolvedOnDisconnectTree = newSparseSnapshotTree();
11478 sparseSnapshotTreeForEachTree(repo.onDisconnect_, newEmptyPath(), function (path, node) {
11479 var resolved = resolveDeferredValueTree(path, node, repo.serverSyncTree_, serverValues);
11480 sparseSnapshotTreeRemember(resolvedOnDisconnectTree, path, resolved);
11481 });
11482 var events = [];
11483 sparseSnapshotTreeForEachTree(resolvedOnDisconnectTree, newEmptyPath(), function (path, snap) {
11484 events = events.concat(syncTreeApplyServerOverwrite(repo.serverSyncTree_, path, snap));
11485 var affectedPath = repoAbortTransactions(repo, path);
11486 repoRerunTransactions(repo, affectedPath);
11487 });
11488 repo.onDisconnect_ = newSparseSnapshotTree();
11489 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, newEmptyPath(), events);
11490}
11491function repoOnDisconnectCancel(repo, path, onComplete) {
11492 repo.server_.onDisconnectCancel(path.toString(), function (status, errorReason) {
11493 if (status === 'ok') {
11494 sparseSnapshotTreeForget(repo.onDisconnect_, path);
11495 }
11496 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11497 });
11498}
11499function repoOnDisconnectSet(repo, path, value, onComplete) {
11500 var newNode = nodeFromJSON(value);
11501 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11502 if (status === 'ok') {
11503 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11504 }
11505 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11506 });
11507}
11508function repoOnDisconnectSetWithPriority(repo, path, value, priority, onComplete) {
11509 var newNode = nodeFromJSON(value, priority);
11510 repo.server_.onDisconnectPut(path.toString(), newNode.val(/*export=*/ true), function (status, errorReason) {
11511 if (status === 'ok') {
11512 sparseSnapshotTreeRemember(repo.onDisconnect_, path, newNode);
11513 }
11514 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11515 });
11516}
11517function repoOnDisconnectUpdate(repo, path, childrenToMerge, onComplete) {
11518 if (util.isEmpty(childrenToMerge)) {
11519 log("onDisconnect().update() called with empty data. Don't do anything.");
11520 repoCallOnCompleteCallback(repo, onComplete, 'ok', undefined);
11521 return;
11522 }
11523 repo.server_.onDisconnectMerge(path.toString(), childrenToMerge, function (status, errorReason) {
11524 if (status === 'ok') {
11525 each(childrenToMerge, function (childName, childNode) {
11526 var newChildNode = nodeFromJSON(childNode);
11527 sparseSnapshotTreeRemember(repo.onDisconnect_, pathChild(path, childName), newChildNode);
11528 });
11529 }
11530 repoCallOnCompleteCallback(repo, onComplete, status, errorReason);
11531 });
11532}
11533function repoAddEventCallbackForQuery(repo, query, eventRegistration) {
11534 var events;
11535 if (pathGetFront(query._path) === '.info') {
11536 events = syncTreeAddEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11537 }
11538 else {
11539 events = syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11540 }
11541 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11542}
11543function repoRemoveEventCallbackForQuery(repo, query, eventRegistration) {
11544 // These are guaranteed not to raise events, since we're not passing in a cancelError. However, we can future-proof
11545 // a little bit by handling the return values anyways.
11546 var events;
11547 if (pathGetFront(query._path) === '.info') {
11548 events = syncTreeRemoveEventRegistration(repo.infoSyncTree_, query, eventRegistration);
11549 }
11550 else {
11551 events = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration);
11552 }
11553 eventQueueRaiseEventsAtPath(repo.eventQueue_, query._path, events);
11554}
11555function repoInterrupt(repo) {
11556 if (repo.persistentConnection_) {
11557 repo.persistentConnection_.interrupt(INTERRUPT_REASON);
11558 }
11559}
11560function repoResume(repo) {
11561 if (repo.persistentConnection_) {
11562 repo.persistentConnection_.resume(INTERRUPT_REASON);
11563 }
11564}
11565function repoLog(repo) {
11566 var varArgs = [];
11567 for (var _i = 1; _i < arguments.length; _i++) {
11568 varArgs[_i - 1] = arguments[_i];
11569 }
11570 var prefix = '';
11571 if (repo.persistentConnection_) {
11572 prefix = repo.persistentConnection_.id + ':';
11573 }
11574 log.apply(void 0, tslib.__spreadArray([prefix], tslib.__read(varArgs), false));
11575}
11576function repoCallOnCompleteCallback(repo, callback, status, errorReason) {
11577 if (callback) {
11578 exceptionGuard(function () {
11579 if (status === 'ok') {
11580 callback(null);
11581 }
11582 else {
11583 var code = (status || 'error').toUpperCase();
11584 var message = code;
11585 if (errorReason) {
11586 message += ': ' + errorReason;
11587 }
11588 var error = new Error(message);
11589 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11590 error.code = code;
11591 callback(error);
11592 }
11593 });
11594 }
11595}
11596/**
11597 * Creates a new transaction, adds it to the transactions we're tracking, and
11598 * sends it to the server if possible.
11599 *
11600 * @param path - Path at which to do transaction.
11601 * @param transactionUpdate - Update callback.
11602 * @param onComplete - Completion callback.
11603 * @param unwatcher - Function that will be called when the transaction no longer
11604 * need data updates for `path`.
11605 * @param applyLocally - Whether or not to make intermediate results visible
11606 */
11607function repoStartTransaction(repo, path, transactionUpdate, onComplete, unwatcher, applyLocally) {
11608 repoLog(repo, 'transaction on ' + path);
11609 // Initialize transaction.
11610 var transaction = {
11611 path: path,
11612 update: transactionUpdate,
11613 onComplete: onComplete,
11614 // One of TransactionStatus enums.
11615 status: null,
11616 // Used when combining transactions at different locations to figure out
11617 // which one goes first.
11618 order: LUIDGenerator(),
11619 // Whether to raise local events for this transaction.
11620 applyLocally: applyLocally,
11621 // Count of how many times we've retried the transaction.
11622 retryCount: 0,
11623 // Function to call to clean up our .on() listener.
11624 unwatcher: unwatcher,
11625 // Stores why a transaction was aborted.
11626 abortReason: null,
11627 currentWriteId: null,
11628 currentInputSnapshot: null,
11629 currentOutputSnapshotRaw: null,
11630 currentOutputSnapshotResolved: null
11631 };
11632 // Run transaction initially.
11633 var currentState = repoGetLatestState(repo, path, undefined);
11634 transaction.currentInputSnapshot = currentState;
11635 var newVal = transaction.update(currentState.val());
11636 if (newVal === undefined) {
11637 // Abort transaction.
11638 transaction.unwatcher();
11639 transaction.currentOutputSnapshotRaw = null;
11640 transaction.currentOutputSnapshotResolved = null;
11641 if (transaction.onComplete) {
11642 transaction.onComplete(null, false, transaction.currentInputSnapshot);
11643 }
11644 }
11645 else {
11646 validateFirebaseData('transaction failed: Data returned ', newVal, transaction.path);
11647 // Mark as run and add to our queue.
11648 transaction.status = 0 /* TransactionStatus.RUN */;
11649 var queueNode = treeSubTree(repo.transactionQueueTree_, path);
11650 var nodeQueue = treeGetValue(queueNode) || [];
11651 nodeQueue.push(transaction);
11652 treeSetValue(queueNode, nodeQueue);
11653 // Update visibleData and raise events
11654 // Note: We intentionally raise events after updating all of our
11655 // transaction state, since the user could start new transactions from the
11656 // event callbacks.
11657 var priorityForNode = void 0;
11658 if (typeof newVal === 'object' &&
11659 newVal !== null &&
11660 util.contains(newVal, '.priority')) {
11661 // eslint-disable-next-line @typescript-eslint/no-explicit-any
11662 priorityForNode = util.safeGet(newVal, '.priority');
11663 util.assert(isValidPriority(priorityForNode), 'Invalid priority returned by transaction. ' +
11664 'Priority must be a valid string, finite number, server value, or null.');
11665 }
11666 else {
11667 var currentNode = syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path) ||
11668 ChildrenNode.EMPTY_NODE;
11669 priorityForNode = currentNode.getPriority().val();
11670 }
11671 var serverValues = repoGenerateServerValues(repo);
11672 var newNodeUnresolved = nodeFromJSON(newVal, priorityForNode);
11673 var newNode = resolveDeferredValueSnapshot(newNodeUnresolved, currentState, serverValues);
11674 transaction.currentOutputSnapshotRaw = newNodeUnresolved;
11675 transaction.currentOutputSnapshotResolved = newNode;
11676 transaction.currentWriteId = repoGetNextWriteId(repo);
11677 var events = syncTreeApplyUserOverwrite(repo.serverSyncTree_, path, newNode, transaction.currentWriteId, transaction.applyLocally);
11678 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11679 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11680 }
11681}
11682/**
11683 * @param excludeSets - A specific set to exclude
11684 */
11685function repoGetLatestState(repo, path, excludeSets) {
11686 return (syncTreeCalcCompleteEventCache(repo.serverSyncTree_, path, excludeSets) ||
11687 ChildrenNode.EMPTY_NODE);
11688}
11689/**
11690 * Sends any already-run transactions that aren't waiting for outstanding
11691 * transactions to complete.
11692 *
11693 * Externally it's called with no arguments, but it calls itself recursively
11694 * with a particular transactionQueueTree node to recurse through the tree.
11695 *
11696 * @param node - transactionQueueTree node to start at.
11697 */
11698function repoSendReadyTransactions(repo, node) {
11699 if (node === void 0) { node = repo.transactionQueueTree_; }
11700 // Before recursing, make sure any completed transactions are removed.
11701 if (!node) {
11702 repoPruneCompletedTransactionsBelowNode(repo, node);
11703 }
11704 if (treeGetValue(node)) {
11705 var queue = repoBuildTransactionQueue(repo, node);
11706 util.assert(queue.length > 0, 'Sending zero length transaction queue');
11707 var allRun = queue.every(function (transaction) { return transaction.status === 0 /* TransactionStatus.RUN */; });
11708 // If they're all run (and not sent), we can send them. Else, we must wait.
11709 if (allRun) {
11710 repoSendTransactionQueue(repo, treeGetPath(node), queue);
11711 }
11712 }
11713 else if (treeHasChildren(node)) {
11714 treeForEachChild(node, function (childNode) {
11715 repoSendReadyTransactions(repo, childNode);
11716 });
11717 }
11718}
11719/**
11720 * Given a list of run transactions, send them to the server and then handle
11721 * the result (success or failure).
11722 *
11723 * @param path - The location of the queue.
11724 * @param queue - Queue of transactions under the specified location.
11725 */
11726function repoSendTransactionQueue(repo, path, queue) {
11727 // Mark transactions as sent and increment retry count!
11728 var setsToIgnore = queue.map(function (txn) {
11729 return txn.currentWriteId;
11730 });
11731 var latestState = repoGetLatestState(repo, path, setsToIgnore);
11732 var snapToSend = latestState;
11733 var latestHash = latestState.hash();
11734 for (var i = 0; i < queue.length; i++) {
11735 var txn = queue[i];
11736 util.assert(txn.status === 0 /* TransactionStatus.RUN */, 'tryToSendTransactionQueue_: items in queue should all be run.');
11737 txn.status = 1 /* TransactionStatus.SENT */;
11738 txn.retryCount++;
11739 var relativePath = newRelativePath(path, txn.path);
11740 // If we've gotten to this point, the output snapshot must be defined.
11741 snapToSend = snapToSend.updateChild(relativePath /** @type {!Node} */, txn.currentOutputSnapshotRaw);
11742 }
11743 var dataToSend = snapToSend.val(true);
11744 var pathToSend = path;
11745 // Send the put.
11746 repo.server_.put(pathToSend.toString(), dataToSend, function (status) {
11747 repoLog(repo, 'transaction put response', {
11748 path: pathToSend.toString(),
11749 status: status
11750 });
11751 var events = [];
11752 if (status === 'ok') {
11753 // Queue up the callbacks and fire them after cleaning up all of our
11754 // transaction state, since the callback could trigger more
11755 // transactions or sets.
11756 var callbacks = [];
11757 var _loop_1 = function (i) {
11758 queue[i].status = 2 /* TransactionStatus.COMPLETED */;
11759 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId));
11760 if (queue[i].onComplete) {
11761 // We never unset the output snapshot, and given that this
11762 // transaction is complete, it should be set
11763 callbacks.push(function () {
11764 return queue[i].onComplete(null, true, queue[i].currentOutputSnapshotResolved);
11765 });
11766 }
11767 queue[i].unwatcher();
11768 };
11769 for (var i = 0; i < queue.length; i++) {
11770 _loop_1(i);
11771 }
11772 // Now remove the completed transactions.
11773 repoPruneCompletedTransactionsBelowNode(repo, treeSubTree(repo.transactionQueueTree_, path));
11774 // There may be pending transactions that we can now send.
11775 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11776 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11777 // Finally, trigger onComplete callbacks.
11778 for (var i = 0; i < callbacks.length; i++) {
11779 exceptionGuard(callbacks[i]);
11780 }
11781 }
11782 else {
11783 // transactions are no longer sent. Update their status appropriately.
11784 if (status === 'datastale') {
11785 for (var i = 0; i < queue.length; i++) {
11786 if (queue[i].status === 3 /* TransactionStatus.SENT_NEEDS_ABORT */) {
11787 queue[i].status = 4 /* TransactionStatus.NEEDS_ABORT */;
11788 }
11789 else {
11790 queue[i].status = 0 /* TransactionStatus.RUN */;
11791 }
11792 }
11793 }
11794 else {
11795 warn('transaction at ' + pathToSend.toString() + ' failed: ' + status);
11796 for (var i = 0; i < queue.length; i++) {
11797 queue[i].status = 4 /* TransactionStatus.NEEDS_ABORT */;
11798 queue[i].abortReason = status;
11799 }
11800 }
11801 repoRerunTransactions(repo, path);
11802 }
11803 }, latestHash);
11804}
11805/**
11806 * Finds all transactions dependent on the data at changedPath and reruns them.
11807 *
11808 * Should be called any time cached data changes.
11809 *
11810 * Return the highest path that was affected by rerunning transactions. This
11811 * is the path at which events need to be raised for.
11812 *
11813 * @param changedPath - The path in mergedData that changed.
11814 * @returns The rootmost path that was affected by rerunning transactions.
11815 */
11816function repoRerunTransactions(repo, changedPath) {
11817 var rootMostTransactionNode = repoGetAncestorTransactionNode(repo, changedPath);
11818 var path = treeGetPath(rootMostTransactionNode);
11819 var queue = repoBuildTransactionQueue(repo, rootMostTransactionNode);
11820 repoRerunTransactionQueue(repo, queue, path);
11821 return path;
11822}
11823/**
11824 * Does all the work of rerunning transactions (as well as cleans up aborted
11825 * transactions and whatnot).
11826 *
11827 * @param queue - The queue of transactions to run.
11828 * @param path - The path the queue is for.
11829 */
11830function repoRerunTransactionQueue(repo, queue, path) {
11831 if (queue.length === 0) {
11832 return; // Nothing to do!
11833 }
11834 // Queue up the callbacks and fire them after cleaning up all of our
11835 // transaction state, since the callback could trigger more transactions or
11836 // sets.
11837 var callbacks = [];
11838 var events = [];
11839 // Ignore all of the sets we're going to re-run.
11840 var txnsToRerun = queue.filter(function (q) {
11841 return q.status === 0 /* TransactionStatus.RUN */;
11842 });
11843 var setsToIgnore = txnsToRerun.map(function (q) {
11844 return q.currentWriteId;
11845 });
11846 var _loop_2 = function (i) {
11847 var transaction = queue[i];
11848 var relativePath = newRelativePath(path, transaction.path);
11849 var abortTransaction = false, abortReason;
11850 util.assert(relativePath !== null, 'rerunTransactionsUnderNode_: relativePath should not be null.');
11851 if (transaction.status === 4 /* TransactionStatus.NEEDS_ABORT */) {
11852 abortTransaction = true;
11853 abortReason = transaction.abortReason;
11854 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11855 }
11856 else if (transaction.status === 0 /* TransactionStatus.RUN */) {
11857 if (transaction.retryCount >= MAX_TRANSACTION_RETRIES) {
11858 abortTransaction = true;
11859 abortReason = 'maxretry';
11860 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11861 }
11862 else {
11863 // This code reruns a transaction
11864 var currentNode = repoGetLatestState(repo, transaction.path, setsToIgnore);
11865 transaction.currentInputSnapshot = currentNode;
11866 var newData = queue[i].update(currentNode.val());
11867 if (newData !== undefined) {
11868 validateFirebaseData('transaction failed: Data returned ', newData, transaction.path);
11869 var newDataNode = nodeFromJSON(newData);
11870 var hasExplicitPriority = typeof newData === 'object' &&
11871 newData != null &&
11872 util.contains(newData, '.priority');
11873 if (!hasExplicitPriority) {
11874 // Keep the old priority if there wasn't a priority explicitly specified.
11875 newDataNode = newDataNode.updatePriority(currentNode.getPriority());
11876 }
11877 var oldWriteId = transaction.currentWriteId;
11878 var serverValues = repoGenerateServerValues(repo);
11879 var newNodeResolved = resolveDeferredValueSnapshot(newDataNode, currentNode, serverValues);
11880 transaction.currentOutputSnapshotRaw = newDataNode;
11881 transaction.currentOutputSnapshotResolved = newNodeResolved;
11882 transaction.currentWriteId = repoGetNextWriteId(repo);
11883 // Mutates setsToIgnore in place
11884 setsToIgnore.splice(setsToIgnore.indexOf(oldWriteId), 1);
11885 events = events.concat(syncTreeApplyUserOverwrite(repo.serverSyncTree_, transaction.path, newNodeResolved, transaction.currentWriteId, transaction.applyLocally));
11886 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, oldWriteId, true));
11887 }
11888 else {
11889 abortTransaction = true;
11890 abortReason = 'nodata';
11891 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, transaction.currentWriteId, true));
11892 }
11893 }
11894 }
11895 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, path, events);
11896 events = [];
11897 if (abortTransaction) {
11898 // Abort.
11899 queue[i].status = 2 /* TransactionStatus.COMPLETED */;
11900 // Removing a listener can trigger pruning which can muck with
11901 // mergedData/visibleData (as it prunes data). So defer the unwatcher
11902 // until we're done.
11903 (function (unwatcher) {
11904 setTimeout(unwatcher, Math.floor(0));
11905 })(queue[i].unwatcher);
11906 if (queue[i].onComplete) {
11907 if (abortReason === 'nodata') {
11908 callbacks.push(function () {
11909 return queue[i].onComplete(null, false, queue[i].currentInputSnapshot);
11910 });
11911 }
11912 else {
11913 callbacks.push(function () {
11914 return queue[i].onComplete(new Error(abortReason), false, null);
11915 });
11916 }
11917 }
11918 }
11919 };
11920 for (var i = 0; i < queue.length; i++) {
11921 _loop_2(i);
11922 }
11923 // Clean up completed transactions.
11924 repoPruneCompletedTransactionsBelowNode(repo, repo.transactionQueueTree_);
11925 // Now fire callbacks, now that we're in a good, known state.
11926 for (var i = 0; i < callbacks.length; i++) {
11927 exceptionGuard(callbacks[i]);
11928 }
11929 // Try to send the transaction result to the server.
11930 repoSendReadyTransactions(repo, repo.transactionQueueTree_);
11931}
11932/**
11933 * Returns the rootmost ancestor node of the specified path that has a pending
11934 * transaction on it, or just returns the node for the given path if there are
11935 * no pending transactions on any ancestor.
11936 *
11937 * @param path - The location to start at.
11938 * @returns The rootmost node with a transaction.
11939 */
11940function repoGetAncestorTransactionNode(repo, path) {
11941 var front;
11942 // Start at the root and walk deeper into the tree towards path until we
11943 // find a node with pending transactions.
11944 var transactionNode = repo.transactionQueueTree_;
11945 front = pathGetFront(path);
11946 while (front !== null && treeGetValue(transactionNode) === undefined) {
11947 transactionNode = treeSubTree(transactionNode, front);
11948 path = pathPopFront(path);
11949 front = pathGetFront(path);
11950 }
11951 return transactionNode;
11952}
11953/**
11954 * Builds the queue of all transactions at or below the specified
11955 * transactionNode.
11956 *
11957 * @param transactionNode
11958 * @returns The generated queue.
11959 */
11960function repoBuildTransactionQueue(repo, transactionNode) {
11961 // Walk any child transaction queues and aggregate them into a single queue.
11962 var transactionQueue = [];
11963 repoAggregateTransactionQueuesForNode(repo, transactionNode, transactionQueue);
11964 // Sort them by the order the transactions were created.
11965 transactionQueue.sort(function (a, b) { return a.order - b.order; });
11966 return transactionQueue;
11967}
11968function repoAggregateTransactionQueuesForNode(repo, node, queue) {
11969 var nodeQueue = treeGetValue(node);
11970 if (nodeQueue) {
11971 for (var i = 0; i < nodeQueue.length; i++) {
11972 queue.push(nodeQueue[i]);
11973 }
11974 }
11975 treeForEachChild(node, function (child) {
11976 repoAggregateTransactionQueuesForNode(repo, child, queue);
11977 });
11978}
11979/**
11980 * Remove COMPLETED transactions at or below this node in the transactionQueueTree_.
11981 */
11982function repoPruneCompletedTransactionsBelowNode(repo, node) {
11983 var queue = treeGetValue(node);
11984 if (queue) {
11985 var to = 0;
11986 for (var from = 0; from < queue.length; from++) {
11987 if (queue[from].status !== 2 /* TransactionStatus.COMPLETED */) {
11988 queue[to] = queue[from];
11989 to++;
11990 }
11991 }
11992 queue.length = to;
11993 treeSetValue(node, queue.length > 0 ? queue : undefined);
11994 }
11995 treeForEachChild(node, function (childNode) {
11996 repoPruneCompletedTransactionsBelowNode(repo, childNode);
11997 });
11998}
11999/**
12000 * Aborts all transactions on ancestors or descendants of the specified path.
12001 * Called when doing a set() or update() since we consider them incompatible
12002 * with transactions.
12003 *
12004 * @param path - Path for which we want to abort related transactions.
12005 */
12006function repoAbortTransactions(repo, path) {
12007 var affectedPath = treeGetPath(repoGetAncestorTransactionNode(repo, path));
12008 var transactionNode = treeSubTree(repo.transactionQueueTree_, path);
12009 treeForEachAncestor(transactionNode, function (node) {
12010 repoAbortTransactionsOnNode(repo, node);
12011 });
12012 repoAbortTransactionsOnNode(repo, transactionNode);
12013 treeForEachDescendant(transactionNode, function (node) {
12014 repoAbortTransactionsOnNode(repo, node);
12015 });
12016 return affectedPath;
12017}
12018/**
12019 * Abort transactions stored in this transaction queue node.
12020 *
12021 * @param node - Node to abort transactions for.
12022 */
12023function repoAbortTransactionsOnNode(repo, node) {
12024 var queue = treeGetValue(node);
12025 if (queue) {
12026 // Queue up the callbacks and fire them after cleaning up all of our
12027 // transaction state, since the callback could trigger more transactions
12028 // or sets.
12029 var callbacks = [];
12030 // Go through queue. Any already-sent transactions must be marked for
12031 // abort, while the unsent ones can be immediately aborted and removed.
12032 var events = [];
12033 var lastSent = -1;
12034 for (var i = 0; i < queue.length; i++) {
12035 if (queue[i].status === 3 /* TransactionStatus.SENT_NEEDS_ABORT */) ;
12036 else if (queue[i].status === 1 /* TransactionStatus.SENT */) {
12037 util.assert(lastSent === i - 1, 'All SENT items should be at beginning of queue.');
12038 lastSent = i;
12039 // Mark transaction for abort when it comes back.
12040 queue[i].status = 3 /* TransactionStatus.SENT_NEEDS_ABORT */;
12041 queue[i].abortReason = 'set';
12042 }
12043 else {
12044 util.assert(queue[i].status === 0 /* TransactionStatus.RUN */, 'Unexpected transaction status in abort');
12045 // We can abort it immediately.
12046 queue[i].unwatcher();
12047 events = events.concat(syncTreeAckUserWrite(repo.serverSyncTree_, queue[i].currentWriteId, true));
12048 if (queue[i].onComplete) {
12049 callbacks.push(queue[i].onComplete.bind(null, new Error('set'), false, null));
12050 }
12051 }
12052 }
12053 if (lastSent === -1) {
12054 // We're not waiting for any sent transactions. We can clear the queue.
12055 treeSetValue(node, undefined);
12056 }
12057 else {
12058 // Remove the transactions we aborted.
12059 queue.length = lastSent + 1;
12060 }
12061 // Now fire the callbacks.
12062 eventQueueRaiseEventsForChangedPath(repo.eventQueue_, treeGetPath(node), events);
12063 for (var i = 0; i < callbacks.length; i++) {
12064 exceptionGuard(callbacks[i]);
12065 }
12066 }
12067}
12068
12069/**
12070 * @license
12071 * Copyright 2017 Google LLC
12072 *
12073 * Licensed under the Apache License, Version 2.0 (the "License");
12074 * you may not use this file except in compliance with the License.
12075 * You may obtain a copy of the License at
12076 *
12077 * http://www.apache.org/licenses/LICENSE-2.0
12078 *
12079 * Unless required by applicable law or agreed to in writing, software
12080 * distributed under the License is distributed on an "AS IS" BASIS,
12081 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12082 * See the License for the specific language governing permissions and
12083 * limitations under the License.
12084 */
12085function decodePath(pathString) {
12086 var pathStringDecoded = '';
12087 var pieces = pathString.split('/');
12088 for (var i = 0; i < pieces.length; i++) {
12089 if (pieces[i].length > 0) {
12090 var piece = pieces[i];
12091 try {
12092 piece = decodeURIComponent(piece.replace(/\+/g, ' '));
12093 }
12094 catch (e) { }
12095 pathStringDecoded += '/' + piece;
12096 }
12097 }
12098 return pathStringDecoded;
12099}
12100/**
12101 * @returns key value hash
12102 */
12103function decodeQuery(queryString) {
12104 var e_1, _a;
12105 var results = {};
12106 if (queryString.charAt(0) === '?') {
12107 queryString = queryString.substring(1);
12108 }
12109 try {
12110 for (var _b = tslib.__values(queryString.split('&')), _c = _b.next(); !_c.done; _c = _b.next()) {
12111 var segment = _c.value;
12112 if (segment.length === 0) {
12113 continue;
12114 }
12115 var kv = segment.split('=');
12116 if (kv.length === 2) {
12117 results[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
12118 }
12119 else {
12120 warn("Invalid query segment '".concat(segment, "' in query '").concat(queryString, "'"));
12121 }
12122 }
12123 }
12124 catch (e_1_1) { e_1 = { error: e_1_1 }; }
12125 finally {
12126 try {
12127 if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
12128 }
12129 finally { if (e_1) throw e_1.error; }
12130 }
12131 return results;
12132}
12133var parseRepoInfo = function (dataURL, nodeAdmin) {
12134 var parsedUrl = parseDatabaseURL(dataURL), namespace = parsedUrl.namespace;
12135 if (parsedUrl.domain === 'firebase.com') {
12136 fatal(parsedUrl.host +
12137 ' is no longer supported. ' +
12138 'Please use <YOUR FIREBASE>.firebaseio.com instead');
12139 }
12140 // Catch common error of uninitialized namespace value.
12141 if ((!namespace || namespace === 'undefined') &&
12142 parsedUrl.domain !== 'localhost') {
12143 fatal('Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com');
12144 }
12145 if (!parsedUrl.secure) {
12146 warnIfPageIsSecure();
12147 }
12148 var webSocketOnly = parsedUrl.scheme === 'ws' || parsedUrl.scheme === 'wss';
12149 return {
12150 repoInfo: new RepoInfo(parsedUrl.host, parsedUrl.secure, namespace, webSocketOnly, nodeAdmin,
12151 /*persistenceKey=*/ '',
12152 /*includeNamespaceInQueryParams=*/ namespace !== parsedUrl.subdomain),
12153 path: new Path(parsedUrl.pathString)
12154 };
12155};
12156var parseDatabaseURL = function (dataURL) {
12157 // Default to empty strings in the event of a malformed string.
12158 var host = '', domain = '', subdomain = '', pathString = '', namespace = '';
12159 // Always default to SSL, unless otherwise specified.
12160 var secure = true, scheme = 'https', port = 443;
12161 // Don't do any validation here. The caller is responsible for validating the result of parsing.
12162 if (typeof dataURL === 'string') {
12163 // Parse scheme.
12164 var colonInd = dataURL.indexOf('//');
12165 if (colonInd >= 0) {
12166 scheme = dataURL.substring(0, colonInd - 1);
12167 dataURL = dataURL.substring(colonInd + 2);
12168 }
12169 // Parse host, path, and query string.
12170 var slashInd = dataURL.indexOf('/');
12171 if (slashInd === -1) {
12172 slashInd = dataURL.length;
12173 }
12174 var questionMarkInd = dataURL.indexOf('?');
12175 if (questionMarkInd === -1) {
12176 questionMarkInd = dataURL.length;
12177 }
12178 host = dataURL.substring(0, Math.min(slashInd, questionMarkInd));
12179 if (slashInd < questionMarkInd) {
12180 // For pathString, questionMarkInd will always come after slashInd
12181 pathString = decodePath(dataURL.substring(slashInd, questionMarkInd));
12182 }
12183 var queryParams = decodeQuery(dataURL.substring(Math.min(dataURL.length, questionMarkInd)));
12184 // If we have a port, use scheme for determining if it's secure.
12185 colonInd = host.indexOf(':');
12186 if (colonInd >= 0) {
12187 secure = scheme === 'https' || scheme === 'wss';
12188 port = parseInt(host.substring(colonInd + 1), 10);
12189 }
12190 else {
12191 colonInd = host.length;
12192 }
12193 var hostWithoutPort = host.slice(0, colonInd);
12194 if (hostWithoutPort.toLowerCase() === 'localhost') {
12195 domain = 'localhost';
12196 }
12197 else if (hostWithoutPort.split('.').length <= 2) {
12198 domain = hostWithoutPort;
12199 }
12200 else {
12201 // Interpret the subdomain of a 3 or more component URL as the namespace name.
12202 var dotInd = host.indexOf('.');
12203 subdomain = host.substring(0, dotInd).toLowerCase();
12204 domain = host.substring(dotInd + 1);
12205 // Normalize namespaces to lowercase to share storage / connection.
12206 namespace = subdomain;
12207 }
12208 // Always treat the value of the `ns` as the namespace name if it is present.
12209 if ('ns' in queryParams) {
12210 namespace = queryParams['ns'];
12211 }
12212 }
12213 return {
12214 host: host,
12215 port: port,
12216 domain: domain,
12217 subdomain: subdomain,
12218 secure: secure,
12219 scheme: scheme,
12220 pathString: pathString,
12221 namespace: namespace
12222 };
12223};
12224
12225/**
12226 * @license
12227 * Copyright 2017 Google LLC
12228 *
12229 * Licensed under the Apache License, Version 2.0 (the "License");
12230 * you may not use this file except in compliance with the License.
12231 * You may obtain a copy of the License at
12232 *
12233 * http://www.apache.org/licenses/LICENSE-2.0
12234 *
12235 * Unless required by applicable law or agreed to in writing, software
12236 * distributed under the License is distributed on an "AS IS" BASIS,
12237 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12238 * See the License for the specific language governing permissions and
12239 * limitations under the License.
12240 */
12241// Modeled after base64 web-safe chars, but ordered by ASCII.
12242var PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
12243/**
12244 * Fancy ID generator that creates 20-character string identifiers with the
12245 * following properties:
12246 *
12247 * 1. They're based on timestamp so that they sort *after* any existing ids.
12248 * 2. They contain 72-bits of random data after the timestamp so that IDs won't
12249 * collide with other clients' IDs.
12250 * 3. They sort *lexicographically* (so the timestamp is converted to characters
12251 * that will sort properly).
12252 * 4. They're monotonically increasing. Even if you generate more than one in
12253 * the same timestamp, the latter ones will sort after the former ones. We do
12254 * this by using the previous random bits but "incrementing" them by 1 (only
12255 * in the case of a timestamp collision).
12256 */
12257var nextPushId = (function () {
12258 // Timestamp of last push, used to prevent local collisions if you push twice
12259 // in one ms.
12260 var lastPushTime = 0;
12261 // We generate 72-bits of randomness which get turned into 12 characters and
12262 // appended to the timestamp to prevent collisions with other clients. We
12263 // store the last characters we generated because in the event of a collision,
12264 // we'll use those same characters except "incremented" by one.
12265 var lastRandChars = [];
12266 return function (now) {
12267 var duplicateTime = now === lastPushTime;
12268 lastPushTime = now;
12269 var i;
12270 var timeStampChars = new Array(8);
12271 for (i = 7; i >= 0; i--) {
12272 timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
12273 // NOTE: Can't use << here because javascript will convert to int and lose
12274 // the upper bits.
12275 now = Math.floor(now / 64);
12276 }
12277 util.assert(now === 0, 'Cannot push at time == 0');
12278 var id = timeStampChars.join('');
12279 if (!duplicateTime) {
12280 for (i = 0; i < 12; i++) {
12281 lastRandChars[i] = Math.floor(Math.random() * 64);
12282 }
12283 }
12284 else {
12285 // If the timestamp hasn't changed since last push, use the same random
12286 // number, except incremented by 1.
12287 for (i = 11; i >= 0 && lastRandChars[i] === 63; i--) {
12288 lastRandChars[i] = 0;
12289 }
12290 lastRandChars[i]++;
12291 }
12292 for (i = 0; i < 12; i++) {
12293 id += PUSH_CHARS.charAt(lastRandChars[i]);
12294 }
12295 util.assert(id.length === 20, 'nextPushId: Length should be 20.');
12296 return id;
12297 };
12298})();
12299
12300/**
12301 * @license
12302 * Copyright 2017 Google LLC
12303 *
12304 * Licensed under the Apache License, Version 2.0 (the "License");
12305 * you may not use this file except in compliance with the License.
12306 * You may obtain a copy of the License at
12307 *
12308 * http://www.apache.org/licenses/LICENSE-2.0
12309 *
12310 * Unless required by applicable law or agreed to in writing, software
12311 * distributed under the License is distributed on an "AS IS" BASIS,
12312 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12313 * See the License for the specific language governing permissions and
12314 * limitations under the License.
12315 */
12316/**
12317 * Encapsulates the data needed to raise an event
12318 */
12319var DataEvent = /** @class */ (function () {
12320 /**
12321 * @param eventType - One of: value, child_added, child_changed, child_moved, child_removed
12322 * @param eventRegistration - The function to call to with the event data. User provided
12323 * @param snapshot - The data backing the event
12324 * @param prevName - Optional, the name of the previous child for child_* events.
12325 */
12326 function DataEvent(eventType, eventRegistration, snapshot, prevName) {
12327 this.eventType = eventType;
12328 this.eventRegistration = eventRegistration;
12329 this.snapshot = snapshot;
12330 this.prevName = prevName;
12331 }
12332 DataEvent.prototype.getPath = function () {
12333 var ref = this.snapshot.ref;
12334 if (this.eventType === 'value') {
12335 return ref._path;
12336 }
12337 else {
12338 return ref.parent._path;
12339 }
12340 };
12341 DataEvent.prototype.getEventType = function () {
12342 return this.eventType;
12343 };
12344 DataEvent.prototype.getEventRunner = function () {
12345 return this.eventRegistration.getEventRunner(this);
12346 };
12347 DataEvent.prototype.toString = function () {
12348 return (this.getPath().toString() +
12349 ':' +
12350 this.eventType +
12351 ':' +
12352 util.stringify(this.snapshot.exportVal()));
12353 };
12354 return DataEvent;
12355}());
12356var CancelEvent = /** @class */ (function () {
12357 function CancelEvent(eventRegistration, error, path) {
12358 this.eventRegistration = eventRegistration;
12359 this.error = error;
12360 this.path = path;
12361 }
12362 CancelEvent.prototype.getPath = function () {
12363 return this.path;
12364 };
12365 CancelEvent.prototype.getEventType = function () {
12366 return 'cancel';
12367 };
12368 CancelEvent.prototype.getEventRunner = function () {
12369 return this.eventRegistration.getEventRunner(this);
12370 };
12371 CancelEvent.prototype.toString = function () {
12372 return this.path.toString() + ':cancel';
12373 };
12374 return CancelEvent;
12375}());
12376
12377/**
12378 * @license
12379 * Copyright 2017 Google LLC
12380 *
12381 * Licensed under the Apache License, Version 2.0 (the "License");
12382 * you may not use this file except in compliance with the License.
12383 * You may obtain a copy of the License at
12384 *
12385 * http://www.apache.org/licenses/LICENSE-2.0
12386 *
12387 * Unless required by applicable law or agreed to in writing, software
12388 * distributed under the License is distributed on an "AS IS" BASIS,
12389 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12390 * See the License for the specific language governing permissions and
12391 * limitations under the License.
12392 */
12393/**
12394 * A wrapper class that converts events from the database@exp SDK to the legacy
12395 * Database SDK. Events are not converted directly as event registration relies
12396 * on reference comparison of the original user callback (see `matches()`) and
12397 * relies on equality of the legacy SDK's `context` object.
12398 */
12399var CallbackContext = /** @class */ (function () {
12400 function CallbackContext(snapshotCallback, cancelCallback) {
12401 this.snapshotCallback = snapshotCallback;
12402 this.cancelCallback = cancelCallback;
12403 }
12404 CallbackContext.prototype.onValue = function (expDataSnapshot, previousChildName) {
12405 this.snapshotCallback.call(null, expDataSnapshot, previousChildName);
12406 };
12407 CallbackContext.prototype.onCancel = function (error) {
12408 util.assert(this.hasCancelCallback, 'Raising a cancel event on a listener with no cancel callback');
12409 return this.cancelCallback.call(null, error);
12410 };
12411 Object.defineProperty(CallbackContext.prototype, "hasCancelCallback", {
12412 get: function () {
12413 return !!this.cancelCallback;
12414 },
12415 enumerable: false,
12416 configurable: true
12417 });
12418 CallbackContext.prototype.matches = function (other) {
12419 return (this.snapshotCallback === other.snapshotCallback ||
12420 (this.snapshotCallback.userCallback !== undefined &&
12421 this.snapshotCallback.userCallback ===
12422 other.snapshotCallback.userCallback &&
12423 this.snapshotCallback.context === other.snapshotCallback.context));
12424 };
12425 return CallbackContext;
12426}());
12427
12428/**
12429 * @license
12430 * Copyright 2021 Google LLC
12431 *
12432 * Licensed under the Apache License, Version 2.0 (the "License");
12433 * you may not use this file except in compliance with the License.
12434 * You may obtain a copy of the License at
12435 *
12436 * http://www.apache.org/licenses/LICENSE-2.0
12437 *
12438 * Unless required by applicable law or agreed to in writing, software
12439 * distributed under the License is distributed on an "AS IS" BASIS,
12440 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12441 * See the License for the specific language governing permissions and
12442 * limitations under the License.
12443 */
12444/**
12445 * The `onDisconnect` class allows you to write or clear data when your client
12446 * disconnects from the Database server. These updates occur whether your
12447 * client disconnects cleanly or not, so you can rely on them to clean up data
12448 * even if a connection is dropped or a client crashes.
12449 *
12450 * The `onDisconnect` class is most commonly used to manage presence in
12451 * applications where it is useful to detect how many clients are connected and
12452 * when other clients disconnect. See
12453 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12454 * for more information.
12455 *
12456 * To avoid problems when a connection is dropped before the requests can be
12457 * transferred to the Database server, these functions should be called before
12458 * writing any data.
12459 *
12460 * Note that `onDisconnect` operations are only triggered once. If you want an
12461 * operation to occur each time a disconnect occurs, you'll need to re-establish
12462 * the `onDisconnect` operations each time you reconnect.
12463 */
12464var OnDisconnect = /** @class */ (function () {
12465 /** @hideconstructor */
12466 function OnDisconnect(_repo, _path) {
12467 this._repo = _repo;
12468 this._path = _path;
12469 }
12470 /**
12471 * Cancels all previously queued `onDisconnect()` set or update events for this
12472 * location and all children.
12473 *
12474 * If a write has been queued for this location via a `set()` or `update()` at a
12475 * parent location, the write at this location will be canceled, though writes
12476 * to sibling locations will still occur.
12477 *
12478 * @returns Resolves when synchronization to the server is complete.
12479 */
12480 OnDisconnect.prototype.cancel = function () {
12481 var deferred = new util.Deferred();
12482 repoOnDisconnectCancel(this._repo, this._path, deferred.wrapCallback(function () { }));
12483 return deferred.promise;
12484 };
12485 /**
12486 * Ensures the data at this location is deleted when the client is disconnected
12487 * (due to closing the browser, navigating to a new page, or network issues).
12488 *
12489 * @returns Resolves when synchronization to the server is complete.
12490 */
12491 OnDisconnect.prototype.remove = function () {
12492 validateWritablePath('OnDisconnect.remove', this._path);
12493 var deferred = new util.Deferred();
12494 repoOnDisconnectSet(this._repo, this._path, null, deferred.wrapCallback(function () { }));
12495 return deferred.promise;
12496 };
12497 /**
12498 * Ensures the data at this location is set to the specified value when the
12499 * client is disconnected (due to closing the browser, navigating to a new page,
12500 * or network issues).
12501 *
12502 * `set()` is especially useful for implementing "presence" systems, where a
12503 * value should be changed or cleared when a user disconnects so that they
12504 * appear "offline" to other users. See
12505 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
12506 * for more information.
12507 *
12508 * Note that `onDisconnect` operations are only triggered once. If you want an
12509 * operation to occur each time a disconnect occurs, you'll need to re-establish
12510 * the `onDisconnect` operations each time.
12511 *
12512 * @param value - The value to be written to this location on disconnect (can
12513 * be an object, array, string, number, boolean, or null).
12514 * @returns Resolves when synchronization to the Database is complete.
12515 */
12516 OnDisconnect.prototype.set = function (value) {
12517 validateWritablePath('OnDisconnect.set', this._path);
12518 validateFirebaseDataArg('OnDisconnect.set', value, this._path, false);
12519 var deferred = new util.Deferred();
12520 repoOnDisconnectSet(this._repo, this._path, value, deferred.wrapCallback(function () { }));
12521 return deferred.promise;
12522 };
12523 /**
12524 * Ensures the data at this location is set to the specified value and priority
12525 * when the client is disconnected (due to closing the browser, navigating to a
12526 * new page, or network issues).
12527 *
12528 * @param value - The value to be written to this location on disconnect (can
12529 * be an object, array, string, number, boolean, or null).
12530 * @param priority - The priority to be written (string, number, or null).
12531 * @returns Resolves when synchronization to the Database is complete.
12532 */
12533 OnDisconnect.prototype.setWithPriority = function (value, priority) {
12534 validateWritablePath('OnDisconnect.setWithPriority', this._path);
12535 validateFirebaseDataArg('OnDisconnect.setWithPriority', value, this._path, false);
12536 validatePriority('OnDisconnect.setWithPriority', priority, false);
12537 var deferred = new util.Deferred();
12538 repoOnDisconnectSetWithPriority(this._repo, this._path, value, priority, deferred.wrapCallback(function () { }));
12539 return deferred.promise;
12540 };
12541 /**
12542 * Writes multiple values at this location when the client is disconnected (due
12543 * to closing the browser, navigating to a new page, or network issues).
12544 *
12545 * The `values` argument contains multiple property-value pairs that will be
12546 * written to the Database together. Each child property can either be a simple
12547 * property (for example, "name") or a relative path (for example, "name/first")
12548 * from the current location to the data to update.
12549 *
12550 * As opposed to the `set()` method, `update()` can be use to selectively update
12551 * only the referenced properties at the current location (instead of replacing
12552 * all the child properties at the current location).
12553 *
12554 * @param values - Object containing multiple values.
12555 * @returns Resolves when synchronization to the Database is complete.
12556 */
12557 OnDisconnect.prototype.update = function (values) {
12558 validateWritablePath('OnDisconnect.update', this._path);
12559 validateFirebaseMergeDataArg('OnDisconnect.update', values, this._path, false);
12560 var deferred = new util.Deferred();
12561 repoOnDisconnectUpdate(this._repo, this._path, values, deferred.wrapCallback(function () { }));
12562 return deferred.promise;
12563 };
12564 return OnDisconnect;
12565}());
12566
12567/**
12568 * @license
12569 * Copyright 2020 Google LLC
12570 *
12571 * Licensed under the Apache License, Version 2.0 (the "License");
12572 * you may not use this file except in compliance with the License.
12573 * You may obtain a copy of the License at
12574 *
12575 * http://www.apache.org/licenses/LICENSE-2.0
12576 *
12577 * Unless required by applicable law or agreed to in writing, software
12578 * distributed under the License is distributed on an "AS IS" BASIS,
12579 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12580 * See the License for the specific language governing permissions and
12581 * limitations under the License.
12582 */
12583/**
12584 * @internal
12585 */
12586var QueryImpl = /** @class */ (function () {
12587 /**
12588 * @hideconstructor
12589 */
12590 function QueryImpl(_repo, _path, _queryParams, _orderByCalled) {
12591 this._repo = _repo;
12592 this._path = _path;
12593 this._queryParams = _queryParams;
12594 this._orderByCalled = _orderByCalled;
12595 }
12596 Object.defineProperty(QueryImpl.prototype, "key", {
12597 get: function () {
12598 if (pathIsEmpty(this._path)) {
12599 return null;
12600 }
12601 else {
12602 return pathGetBack(this._path);
12603 }
12604 },
12605 enumerable: false,
12606 configurable: true
12607 });
12608 Object.defineProperty(QueryImpl.prototype, "ref", {
12609 get: function () {
12610 return new ReferenceImpl(this._repo, this._path);
12611 },
12612 enumerable: false,
12613 configurable: true
12614 });
12615 Object.defineProperty(QueryImpl.prototype, "_queryIdentifier", {
12616 get: function () {
12617 var obj = queryParamsGetQueryObject(this._queryParams);
12618 var id = ObjectToUniqueKey(obj);
12619 return id === '{}' ? 'default' : id;
12620 },
12621 enumerable: false,
12622 configurable: true
12623 });
12624 Object.defineProperty(QueryImpl.prototype, "_queryObject", {
12625 /**
12626 * An object representation of the query parameters used by this Query.
12627 */
12628 get: function () {
12629 return queryParamsGetQueryObject(this._queryParams);
12630 },
12631 enumerable: false,
12632 configurable: true
12633 });
12634 QueryImpl.prototype.isEqual = function (other) {
12635 other = util.getModularInstance(other);
12636 if (!(other instanceof QueryImpl)) {
12637 return false;
12638 }
12639 var sameRepo = this._repo === other._repo;
12640 var samePath = pathEquals(this._path, other._path);
12641 var sameQueryIdentifier = this._queryIdentifier === other._queryIdentifier;
12642 return sameRepo && samePath && sameQueryIdentifier;
12643 };
12644 QueryImpl.prototype.toJSON = function () {
12645 return this.toString();
12646 };
12647 QueryImpl.prototype.toString = function () {
12648 return this._repo.toString() + pathToUrlEncodedString(this._path);
12649 };
12650 return QueryImpl;
12651}());
12652/**
12653 * Validates that no other order by call has been made
12654 */
12655function validateNoPreviousOrderByCall(query, fnName) {
12656 if (query._orderByCalled === true) {
12657 throw new Error(fnName + ": You can't combine multiple orderBy calls.");
12658 }
12659}
12660/**
12661 * Validates start/end values for queries.
12662 */
12663function validateQueryEndpoints(params) {
12664 var startNode = null;
12665 var endNode = null;
12666 if (params.hasStart()) {
12667 startNode = params.getIndexStartValue();
12668 }
12669 if (params.hasEnd()) {
12670 endNode = params.getIndexEndValue();
12671 }
12672 if (params.getIndex() === KEY_INDEX) {
12673 var tooManyArgsError = 'Query: When ordering by key, you may only pass one argument to ' +
12674 'startAt(), endAt(), or equalTo().';
12675 var wrongArgTypeError = 'Query: When ordering by key, the argument passed to startAt(), startAfter(), ' +
12676 'endAt(), endBefore(), or equalTo() must be a string.';
12677 if (params.hasStart()) {
12678 var startName = params.getIndexStartName();
12679 if (startName !== MIN_NAME) {
12680 throw new Error(tooManyArgsError);
12681 }
12682 else if (typeof startNode !== 'string') {
12683 throw new Error(wrongArgTypeError);
12684 }
12685 }
12686 if (params.hasEnd()) {
12687 var endName = params.getIndexEndName();
12688 if (endName !== MAX_NAME) {
12689 throw new Error(tooManyArgsError);
12690 }
12691 else if (typeof endNode !== 'string') {
12692 throw new Error(wrongArgTypeError);
12693 }
12694 }
12695 }
12696 else if (params.getIndex() === PRIORITY_INDEX) {
12697 if ((startNode != null && !isValidPriority(startNode)) ||
12698 (endNode != null && !isValidPriority(endNode))) {
12699 throw new Error('Query: When ordering by priority, the first argument passed to startAt(), ' +
12700 'startAfter() endAt(), endBefore(), or equalTo() must be a valid priority value ' +
12701 '(null, a number, or a string).');
12702 }
12703 }
12704 else {
12705 util.assert(params.getIndex() instanceof PathIndex ||
12706 params.getIndex() === VALUE_INDEX, 'unknown index type.');
12707 if ((startNode != null && typeof startNode === 'object') ||
12708 (endNode != null && typeof endNode === 'object')) {
12709 throw new Error('Query: First argument passed to startAt(), startAfter(), endAt(), endBefore(), or ' +
12710 'equalTo() cannot be an object.');
12711 }
12712 }
12713}
12714/**
12715 * Validates that limit* has been called with the correct combination of parameters
12716 */
12717function validateLimit(params) {
12718 if (params.hasStart() &&
12719 params.hasEnd() &&
12720 params.hasLimit() &&
12721 !params.hasAnchoredLimit()) {
12722 throw new Error("Query: Can't combine startAt(), startAfter(), endAt(), endBefore(), and limit(). Use " +
12723 'limitToFirst() or limitToLast() instead.');
12724 }
12725}
12726/**
12727 * @internal
12728 */
12729var ReferenceImpl = /** @class */ (function (_super) {
12730 tslib.__extends(ReferenceImpl, _super);
12731 /** @hideconstructor */
12732 function ReferenceImpl(repo, path) {
12733 return _super.call(this, repo, path, new QueryParams(), false) || this;
12734 }
12735 Object.defineProperty(ReferenceImpl.prototype, "parent", {
12736 get: function () {
12737 var parentPath = pathParent(this._path);
12738 return parentPath === null
12739 ? null
12740 : new ReferenceImpl(this._repo, parentPath);
12741 },
12742 enumerable: false,
12743 configurable: true
12744 });
12745 Object.defineProperty(ReferenceImpl.prototype, "root", {
12746 get: function () {
12747 var ref = this;
12748 while (ref.parent !== null) {
12749 ref = ref.parent;
12750 }
12751 return ref;
12752 },
12753 enumerable: false,
12754 configurable: true
12755 });
12756 return ReferenceImpl;
12757}(QueryImpl));
12758/**
12759 * A `DataSnapshot` contains data from a Database location.
12760 *
12761 * Any time you read data from the Database, you receive the data as a
12762 * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach
12763 * with `on()` or `once()`. You can extract the contents of the snapshot as a
12764 * JavaScript object by calling the `val()` method. Alternatively, you can
12765 * traverse into the snapshot by calling `child()` to return child snapshots
12766 * (which you could then call `val()` on).
12767 *
12768 * A `DataSnapshot` is an efficiently generated, immutable copy of the data at
12769 * a Database location. It cannot be modified and will never change (to modify
12770 * data, you always call the `set()` method on a `Reference` directly).
12771 */
12772var DataSnapshot = /** @class */ (function () {
12773 /**
12774 * @param _node - A SnapshotNode to wrap.
12775 * @param ref - The location this snapshot came from.
12776 * @param _index - The iteration order for this snapshot
12777 * @hideconstructor
12778 */
12779 function DataSnapshot(_node,
12780 /**
12781 * The location of this DataSnapshot.
12782 */
12783 ref, _index) {
12784 this._node = _node;
12785 this.ref = ref;
12786 this._index = _index;
12787 }
12788 Object.defineProperty(DataSnapshot.prototype, "priority", {
12789 /**
12790 * Gets the priority value of the data in this `DataSnapshot`.
12791 *
12792 * Applications need not use priority but can order collections by
12793 * ordinary properties (see
12794 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data |Sorting and filtering data}
12795 * ).
12796 */
12797 get: function () {
12798 // typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
12799 return this._node.getPriority().val();
12800 },
12801 enumerable: false,
12802 configurable: true
12803 });
12804 Object.defineProperty(DataSnapshot.prototype, "key", {
12805 /**
12806 * The key (last part of the path) of the location of this `DataSnapshot`.
12807 *
12808 * The last token in a Database location is considered its key. For example,
12809 * "ada" is the key for the /users/ada/ node. Accessing the key on any
12810 * `DataSnapshot` will return the key for the location that generated it.
12811 * However, accessing the key on the root URL of a Database will return
12812 * `null`.
12813 */
12814 get: function () {
12815 return this.ref.key;
12816 },
12817 enumerable: false,
12818 configurable: true
12819 });
12820 Object.defineProperty(DataSnapshot.prototype, "size", {
12821 /** Returns the number of child properties of this `DataSnapshot`. */
12822 get: function () {
12823 return this._node.numChildren();
12824 },
12825 enumerable: false,
12826 configurable: true
12827 });
12828 /**
12829 * Gets another `DataSnapshot` for the location at the specified relative path.
12830 *
12831 * Passing a relative path to the `child()` method of a DataSnapshot returns
12832 * another `DataSnapshot` for the location at the specified relative path. The
12833 * relative path can either be a simple child name (for example, "ada") or a
12834 * deeper, slash-separated path (for example, "ada/name/first"). If the child
12835 * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot`
12836 * whose value is `null`) is returned.
12837 *
12838 * @param path - A relative path to the location of child data.
12839 */
12840 DataSnapshot.prototype.child = function (path) {
12841 var childPath = new Path(path);
12842 var childRef = child(this.ref, path);
12843 return new DataSnapshot(this._node.getChild(childPath), childRef, PRIORITY_INDEX);
12844 };
12845 /**
12846 * Returns true if this `DataSnapshot` contains any data. It is slightly more
12847 * efficient than using `snapshot.val() !== null`.
12848 */
12849 DataSnapshot.prototype.exists = function () {
12850 return !this._node.isEmpty();
12851 };
12852 /**
12853 * Exports the entire contents of the DataSnapshot as a JavaScript object.
12854 *
12855 * The `exportVal()` method is similar to `val()`, except priority information
12856 * is included (if available), making it suitable for backing up your data.
12857 *
12858 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12859 * Array, string, number, boolean, or `null`).
12860 */
12861 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12862 DataSnapshot.prototype.exportVal = function () {
12863 return this._node.val(true);
12864 };
12865 /**
12866 * Enumerates the top-level children in the `DataSnapshot`.
12867 *
12868 * Because of the way JavaScript objects work, the ordering of data in the
12869 * JavaScript object returned by `val()` is not guaranteed to match the
12870 * ordering on the server nor the ordering of `onChildAdded()` events. That is
12871 * where `forEach()` comes in handy. It guarantees the children of a
12872 * `DataSnapshot` will be iterated in their query order.
12873 *
12874 * If no explicit `orderBy*()` method is used, results are returned
12875 * ordered by key (unless priorities are used, in which case, results are
12876 * returned by priority).
12877 *
12878 * @param action - A function that will be called for each child DataSnapshot.
12879 * The callback can return true to cancel further enumeration.
12880 * @returns true if enumeration was canceled due to your callback returning
12881 * true.
12882 */
12883 DataSnapshot.prototype.forEach = function (action) {
12884 var _this = this;
12885 if (this._node.isLeafNode()) {
12886 return false;
12887 }
12888 var childrenNode = this._node;
12889 // Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
12890 return !!childrenNode.forEachChild(this._index, function (key, node) {
12891 return action(new DataSnapshot(node, child(_this.ref, key), PRIORITY_INDEX));
12892 });
12893 };
12894 /**
12895 * Returns true if the specified child path has (non-null) data.
12896 *
12897 * @param path - A relative path to the location of a potential child.
12898 * @returns `true` if data exists at the specified child path; else
12899 * `false`.
12900 */
12901 DataSnapshot.prototype.hasChild = function (path) {
12902 var childPath = new Path(path);
12903 return !this._node.getChild(childPath).isEmpty();
12904 };
12905 /**
12906 * Returns whether or not the `DataSnapshot` has any non-`null` child
12907 * properties.
12908 *
12909 * You can use `hasChildren()` to determine if a `DataSnapshot` has any
12910 * children. If it does, you can enumerate them using `forEach()`. If it
12911 * doesn't, then either this snapshot contains a primitive value (which can be
12912 * retrieved with `val()`) or it is empty (in which case, `val()` will return
12913 * `null`).
12914 *
12915 * @returns true if this snapshot has any children; else false.
12916 */
12917 DataSnapshot.prototype.hasChildren = function () {
12918 if (this._node.isLeafNode()) {
12919 return false;
12920 }
12921 else {
12922 return !this._node.isEmpty();
12923 }
12924 };
12925 /**
12926 * Returns a JSON-serializable representation of this object.
12927 */
12928 DataSnapshot.prototype.toJSON = function () {
12929 return this.exportVal();
12930 };
12931 /**
12932 * Extracts a JavaScript value from a `DataSnapshot`.
12933 *
12934 * Depending on the data in a `DataSnapshot`, the `val()` method may return a
12935 * scalar type (string, number, or boolean), an array, or an object. It may
12936 * also return null, indicating that the `DataSnapshot` is empty (contains no
12937 * data).
12938 *
12939 * @returns The DataSnapshot's contents as a JavaScript value (Object,
12940 * Array, string, number, boolean, or `null`).
12941 */
12942 // eslint-disable-next-line @typescript-eslint/no-explicit-any
12943 DataSnapshot.prototype.val = function () {
12944 return this._node.val();
12945 };
12946 return DataSnapshot;
12947}());
12948/**
12949 *
12950 * Returns a `Reference` representing the location in the Database
12951 * corresponding to the provided path. If no path is provided, the `Reference`
12952 * will point to the root of the Database.
12953 *
12954 * @param db - The database instance to obtain a reference for.
12955 * @param path - Optional path representing the location the returned
12956 * `Reference` will point. If not provided, the returned `Reference` will
12957 * point to the root of the Database.
12958 * @returns If a path is provided, a `Reference`
12959 * pointing to the provided path. Otherwise, a `Reference` pointing to the
12960 * root of the Database.
12961 */
12962function ref(db, path) {
12963 db = util.getModularInstance(db);
12964 db._checkNotDeleted('ref');
12965 return path !== undefined ? child(db._root, path) : db._root;
12966}
12967/**
12968 * Returns a `Reference` representing the location in the Database
12969 * corresponding to the provided Firebase URL.
12970 *
12971 * An exception is thrown if the URL is not a valid Firebase Database URL or it
12972 * has a different domain than the current `Database` instance.
12973 *
12974 * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored
12975 * and are not applied to the returned `Reference`.
12976 *
12977 * @param db - The database instance to obtain a reference for.
12978 * @param url - The Firebase URL at which the returned `Reference` will
12979 * point.
12980 * @returns A `Reference` pointing to the provided
12981 * Firebase URL.
12982 */
12983function refFromURL(db, url) {
12984 db = util.getModularInstance(db);
12985 db._checkNotDeleted('refFromURL');
12986 var parsedURL = parseRepoInfo(url, db._repo.repoInfo_.nodeAdmin);
12987 validateUrl('refFromURL', parsedURL);
12988 var repoInfo = parsedURL.repoInfo;
12989 if (!db._repo.repoInfo_.isCustomHost() &&
12990 repoInfo.host !== db._repo.repoInfo_.host) {
12991 fatal('refFromURL' +
12992 ': Host name does not match the current database: ' +
12993 '(found ' +
12994 repoInfo.host +
12995 ' but expected ' +
12996 db._repo.repoInfo_.host +
12997 ')');
12998 }
12999 return ref(db, parsedURL.path.toString());
13000}
13001/**
13002 * Gets a `Reference` for the location at the specified relative path.
13003 *
13004 * The relative path can either be a simple child name (for example, "ada") or
13005 * a deeper slash-separated path (for example, "ada/name/first").
13006 *
13007 * @param parent - The parent location.
13008 * @param path - A relative path from this location to the desired child
13009 * location.
13010 * @returns The specified child location.
13011 */
13012function child(parent, path) {
13013 parent = util.getModularInstance(parent);
13014 if (pathGetFront(parent._path) === null) {
13015 validateRootPathString('child', 'path', path, false);
13016 }
13017 else {
13018 validatePathString('child', 'path', path, false);
13019 }
13020 return new ReferenceImpl(parent._repo, pathChild(parent._path, path));
13021}
13022/**
13023 * Returns an `OnDisconnect` object - see
13024 * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript}
13025 * for more information on how to use it.
13026 *
13027 * @param ref - The reference to add OnDisconnect triggers for.
13028 */
13029function onDisconnect(ref) {
13030 ref = util.getModularInstance(ref);
13031 return new OnDisconnect(ref._repo, ref._path);
13032}
13033/**
13034 * Generates a new child location using a unique key and returns its
13035 * `Reference`.
13036 *
13037 * This is the most common pattern for adding data to a collection of items.
13038 *
13039 * If you provide a value to `push()`, the value is written to the
13040 * generated location. If you don't pass a value, nothing is written to the
13041 * database and the child remains empty (but you can use the `Reference`
13042 * elsewhere).
13043 *
13044 * The unique keys generated by `push()` are ordered by the current time, so the
13045 * resulting list of items is chronologically sorted. The keys are also
13046 * designed to be unguessable (they contain 72 random bits of entropy).
13047 *
13048 * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}.
13049 * See {@link https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html | The 2^120 Ways to Ensure Unique Identifiers}.
13050 *
13051 * @param parent - The parent location.
13052 * @param value - Optional value to be written at the generated location.
13053 * @returns Combined `Promise` and `Reference`; resolves when write is complete,
13054 * but can be used immediately as the `Reference` to the child location.
13055 */
13056function push(parent, value) {
13057 parent = util.getModularInstance(parent);
13058 validateWritablePath('push', parent._path);
13059 validateFirebaseDataArg('push', value, parent._path, true);
13060 var now = repoServerTime(parent._repo);
13061 var name = nextPushId(now);
13062 // push() returns a ThennableReference whose promise is fulfilled with a
13063 // regular Reference. We use child() to create handles to two different
13064 // references. The first is turned into a ThennableReference below by adding
13065 // then() and catch() methods and is used as the return value of push(). The
13066 // second remains a regular Reference and is used as the fulfilled value of
13067 // the first ThennableReference.
13068 var thennablePushRef = child(parent, name);
13069 var pushRef = child(parent, name);
13070 var promise;
13071 if (value != null) {
13072 promise = set(pushRef, value).then(function () { return pushRef; });
13073 }
13074 else {
13075 promise = Promise.resolve(pushRef);
13076 }
13077 thennablePushRef.then = promise.then.bind(promise);
13078 thennablePushRef.catch = promise.then.bind(promise, undefined);
13079 return thennablePushRef;
13080}
13081/**
13082 * Removes the data at this Database location.
13083 *
13084 * Any data at child locations will also be deleted.
13085 *
13086 * The effect of the remove will be visible immediately and the corresponding
13087 * event 'value' will be triggered. Synchronization of the remove to the
13088 * Firebase servers will also be started, and the returned Promise will resolve
13089 * when complete. If provided, the onComplete callback will be called
13090 * asynchronously after synchronization has finished.
13091 *
13092 * @param ref - The location to remove.
13093 * @returns Resolves when remove on server is complete.
13094 */
13095function remove(ref) {
13096 validateWritablePath('remove', ref._path);
13097 return set(ref, null);
13098}
13099/**
13100 * Writes data to this Database location.
13101 *
13102 * This will overwrite any data at this location and all child locations.
13103 *
13104 * The effect of the write will be visible immediately, and the corresponding
13105 * events ("value", "child_added", etc.) will be triggered. Synchronization of
13106 * the data to the Firebase servers will also be started, and the returned
13107 * Promise will resolve when complete. If provided, the `onComplete` callback
13108 * will be called asynchronously after synchronization has finished.
13109 *
13110 * Passing `null` for the new value is equivalent to calling `remove()`; namely,
13111 * all data at this location and all child locations will be deleted.
13112 *
13113 * `set()` will remove any priority stored at this location, so if priority is
13114 * meant to be preserved, you need to use `setWithPriority()` instead.
13115 *
13116 * Note that modifying data with `set()` will cancel any pending transactions
13117 * at that location, so extreme care should be taken if mixing `set()` and
13118 * `transaction()` to modify the same data.
13119 *
13120 * A single `set()` will generate a single "value" event at the location where
13121 * the `set()` was performed.
13122 *
13123 * @param ref - The location to write to.
13124 * @param value - The value to be written (string, number, boolean, object,
13125 * array, or null).
13126 * @returns Resolves when write to server is complete.
13127 */
13128function set(ref, value) {
13129 ref = util.getModularInstance(ref);
13130 validateWritablePath('set', ref._path);
13131 validateFirebaseDataArg('set', value, ref._path, false);
13132 var deferred = new util.Deferred();
13133 repoSetWithPriority(ref._repo, ref._path, value,
13134 /*priority=*/ null, deferred.wrapCallback(function () { }));
13135 return deferred.promise;
13136}
13137/**
13138 * Sets a priority for the data at this Database location.
13139 *
13140 * Applications need not use priority but can order collections by
13141 * ordinary properties (see
13142 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13143 * ).
13144 *
13145 * @param ref - The location to write to.
13146 * @param priority - The priority to be written (string, number, or null).
13147 * @returns Resolves when write to server is complete.
13148 */
13149function setPriority(ref, priority) {
13150 ref = util.getModularInstance(ref);
13151 validateWritablePath('setPriority', ref._path);
13152 validatePriority('setPriority', priority, false);
13153 var deferred = new util.Deferred();
13154 repoSetWithPriority(ref._repo, pathChild(ref._path, '.priority'), priority, null, deferred.wrapCallback(function () { }));
13155 return deferred.promise;
13156}
13157/**
13158 * Writes data the Database location. Like `set()` but also specifies the
13159 * priority for that data.
13160 *
13161 * Applications need not use priority but can order collections by
13162 * ordinary properties (see
13163 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data | Sorting and filtering data}
13164 * ).
13165 *
13166 * @param ref - The location to write to.
13167 * @param value - The value to be written (string, number, boolean, object,
13168 * array, or null).
13169 * @param priority - The priority to be written (string, number, or null).
13170 * @returns Resolves when write to server is complete.
13171 */
13172function setWithPriority(ref, value, priority) {
13173 validateWritablePath('setWithPriority', ref._path);
13174 validateFirebaseDataArg('setWithPriority', value, ref._path, false);
13175 validatePriority('setWithPriority', priority, false);
13176 if (ref.key === '.length' || ref.key === '.keys') {
13177 throw 'setWithPriority failed: ' + ref.key + ' is a read-only object.';
13178 }
13179 var deferred = new util.Deferred();
13180 repoSetWithPriority(ref._repo, ref._path, value, priority, deferred.wrapCallback(function () { }));
13181 return deferred.promise;
13182}
13183/**
13184 * Writes multiple values to the Database at once.
13185 *
13186 * The `values` argument contains multiple property-value pairs that will be
13187 * written to the Database together. Each child property can either be a simple
13188 * property (for example, "name") or a relative path (for example,
13189 * "name/first") from the current location to the data to update.
13190 *
13191 * As opposed to the `set()` method, `update()` can be use to selectively update
13192 * only the referenced properties at the current location (instead of replacing
13193 * all the child properties at the current location).
13194 *
13195 * The effect of the write will be visible immediately, and the corresponding
13196 * events ('value', 'child_added', etc.) will be triggered. Synchronization of
13197 * the data to the Firebase servers will also be started, and the returned
13198 * Promise will resolve when complete. If provided, the `onComplete` callback
13199 * will be called asynchronously after synchronization has finished.
13200 *
13201 * A single `update()` will generate a single "value" event at the location
13202 * where the `update()` was performed, regardless of how many children were
13203 * modified.
13204 *
13205 * Note that modifying data with `update()` will cancel any pending
13206 * transactions at that location, so extreme care should be taken if mixing
13207 * `update()` and `transaction()` to modify the same data.
13208 *
13209 * Passing `null` to `update()` will remove the data at this location.
13210 *
13211 * See
13212 * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}.
13213 *
13214 * @param ref - The location to write to.
13215 * @param values - Object containing multiple values.
13216 * @returns Resolves when update on server is complete.
13217 */
13218function update(ref, values) {
13219 validateFirebaseMergeDataArg('update', values, ref._path, false);
13220 var deferred = new util.Deferred();
13221 repoUpdate(ref._repo, ref._path, values, deferred.wrapCallback(function () { }));
13222 return deferred.promise;
13223}
13224/**
13225 * Gets the most up-to-date result for this query.
13226 *
13227 * @param query - The query to run.
13228 * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is
13229 * available, or rejects if the client is unable to return a value (e.g., if the
13230 * server is unreachable and there is nothing cached).
13231 */
13232function get(query) {
13233 query = util.getModularInstance(query);
13234 var callbackContext = new CallbackContext(function () { });
13235 var container = new ValueEventRegistration(callbackContext);
13236 return repoGetValue(query._repo, query, container).then(function (node) {
13237 return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
13238 });
13239}
13240/**
13241 * Represents registration for 'value' events.
13242 */
13243var ValueEventRegistration = /** @class */ (function () {
13244 function ValueEventRegistration(callbackContext) {
13245 this.callbackContext = callbackContext;
13246 }
13247 ValueEventRegistration.prototype.respondsTo = function (eventType) {
13248 return eventType === 'value';
13249 };
13250 ValueEventRegistration.prototype.createEvent = function (change, query) {
13251 var index = query._queryParams.getIndex();
13252 return new DataEvent('value', this, new DataSnapshot(change.snapshotNode, new ReferenceImpl(query._repo, query._path), index));
13253 };
13254 ValueEventRegistration.prototype.getEventRunner = function (eventData) {
13255 var _this = this;
13256 if (eventData.getEventType() === 'cancel') {
13257 return function () {
13258 return _this.callbackContext.onCancel(eventData.error);
13259 };
13260 }
13261 else {
13262 return function () {
13263 return _this.callbackContext.onValue(eventData.snapshot, null);
13264 };
13265 }
13266 };
13267 ValueEventRegistration.prototype.createCancelEvent = function (error, path) {
13268 if (this.callbackContext.hasCancelCallback) {
13269 return new CancelEvent(this, error, path);
13270 }
13271 else {
13272 return null;
13273 }
13274 };
13275 ValueEventRegistration.prototype.matches = function (other) {
13276 if (!(other instanceof ValueEventRegistration)) {
13277 return false;
13278 }
13279 else if (!other.callbackContext || !this.callbackContext) {
13280 // If no callback specified, we consider it to match any callback.
13281 return true;
13282 }
13283 else {
13284 return other.callbackContext.matches(this.callbackContext);
13285 }
13286 };
13287 ValueEventRegistration.prototype.hasAnyCallback = function () {
13288 return this.callbackContext !== null;
13289 };
13290 return ValueEventRegistration;
13291}());
13292/**
13293 * Represents the registration of a child_x event.
13294 */
13295var ChildEventRegistration = /** @class */ (function () {
13296 function ChildEventRegistration(eventType, callbackContext) {
13297 this.eventType = eventType;
13298 this.callbackContext = callbackContext;
13299 }
13300 ChildEventRegistration.prototype.respondsTo = function (eventType) {
13301 var eventToCheck = eventType === 'children_added' ? 'child_added' : eventType;
13302 eventToCheck =
13303 eventToCheck === 'children_removed' ? 'child_removed' : eventToCheck;
13304 return this.eventType === eventToCheck;
13305 };
13306 ChildEventRegistration.prototype.createCancelEvent = function (error, path) {
13307 if (this.callbackContext.hasCancelCallback) {
13308 return new CancelEvent(this, error, path);
13309 }
13310 else {
13311 return null;
13312 }
13313 };
13314 ChildEventRegistration.prototype.createEvent = function (change, query) {
13315 util.assert(change.childName != null, 'Child events should have a childName.');
13316 var childRef = child(new ReferenceImpl(query._repo, query._path), change.childName);
13317 var index = query._queryParams.getIndex();
13318 return new DataEvent(change.type, this, new DataSnapshot(change.snapshotNode, childRef, index), change.prevName);
13319 };
13320 ChildEventRegistration.prototype.getEventRunner = function (eventData) {
13321 var _this = this;
13322 if (eventData.getEventType() === 'cancel') {
13323 return function () {
13324 return _this.callbackContext.onCancel(eventData.error);
13325 };
13326 }
13327 else {
13328 return function () {
13329 return _this.callbackContext.onValue(eventData.snapshot, eventData.prevName);
13330 };
13331 }
13332 };
13333 ChildEventRegistration.prototype.matches = function (other) {
13334 if (other instanceof ChildEventRegistration) {
13335 return (this.eventType === other.eventType &&
13336 (!this.callbackContext ||
13337 !other.callbackContext ||
13338 this.callbackContext.matches(other.callbackContext)));
13339 }
13340 return false;
13341 };
13342 ChildEventRegistration.prototype.hasAnyCallback = function () {
13343 return !!this.callbackContext;
13344 };
13345 return ChildEventRegistration;
13346}());
13347function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) {
13348 var cancelCallback;
13349 if (typeof cancelCallbackOrListenOptions === 'object') {
13350 cancelCallback = undefined;
13351 options = cancelCallbackOrListenOptions;
13352 }
13353 if (typeof cancelCallbackOrListenOptions === 'function') {
13354 cancelCallback = cancelCallbackOrListenOptions;
13355 }
13356 if (options && options.onlyOnce) {
13357 var userCallback_1 = callback;
13358 var onceCallback = function (dataSnapshot, previousChildName) {
13359 repoRemoveEventCallbackForQuery(query._repo, query, container);
13360 userCallback_1(dataSnapshot, previousChildName);
13361 };
13362 onceCallback.userCallback = callback.userCallback;
13363 onceCallback.context = callback.context;
13364 callback = onceCallback;
13365 }
13366 var callbackContext = new CallbackContext(callback, cancelCallback || undefined);
13367 var container = eventType === 'value'
13368 ? new ValueEventRegistration(callbackContext)
13369 : new ChildEventRegistration(eventType, callbackContext);
13370 repoAddEventCallbackForQuery(query._repo, query, container);
13371 return function () { return repoRemoveEventCallbackForQuery(query._repo, query, container); };
13372}
13373function onValue(query, callback, cancelCallbackOrListenOptions, options) {
13374 return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options);
13375}
13376function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) {
13377 return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options);
13378}
13379function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) {
13380 return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options);
13381}
13382function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) {
13383 return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options);
13384}
13385function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) {
13386 return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options);
13387}
13388/**
13389 * Detaches a callback previously attached with the corresponding `on*()` (`onValue`, `onChildAdded`) listener.
13390 * Note: This is not the recommended way to remove a listener. Instead, please use the returned callback function from
13391 * the respective `on*` callbacks.
13392 *
13393 * Detach a callback previously attached with `on*()`. Calling `off()` on a parent listener
13394 * will not automatically remove listeners registered on child nodes, `off()`
13395 * must also be called on any child listeners to remove the callback.
13396 *
13397 * If a callback is not specified, all callbacks for the specified eventType
13398 * will be removed. Similarly, if no eventType is specified, all callbacks
13399 * for the `Reference` will be removed.
13400 *
13401 * Individual listeners can also be removed by invoking their unsubscribe
13402 * callbacks.
13403 *
13404 * @param query - The query that the listener was registered with.
13405 * @param eventType - One of the following strings: "value", "child_added",
13406 * "child_changed", "child_removed", or "child_moved." If omitted, all callbacks
13407 * for the `Reference` will be removed.
13408 * @param callback - The callback function that was passed to `on()` or
13409 * `undefined` to remove all callbacks.
13410 */
13411function off(query, eventType, callback) {
13412 var container = null;
13413 var expCallback = callback ? new CallbackContext(callback) : null;
13414 if (eventType === 'value') {
13415 container = new ValueEventRegistration(expCallback);
13416 }
13417 else if (eventType) {
13418 container = new ChildEventRegistration(eventType, expCallback);
13419 }
13420 repoRemoveEventCallbackForQuery(query._repo, query, container);
13421}
13422/**
13423 * A `QueryConstraint` is used to narrow the set of documents returned by a
13424 * Database query. `QueryConstraint`s are created by invoking {@link endAt},
13425 * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link
13426 * limitToFirst}, {@link limitToLast}, {@link orderByChild},
13427 * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} ,
13428 * {@link orderByValue} or {@link equalTo} and
13429 * can then be passed to {@link query} to create a new query instance that
13430 * also contains this `QueryConstraint`.
13431 */
13432var QueryConstraint = /** @class */ (function () {
13433 function QueryConstraint() {
13434 }
13435 return QueryConstraint;
13436}());
13437var QueryEndAtConstraint = /** @class */ (function (_super) {
13438 tslib.__extends(QueryEndAtConstraint, _super);
13439 function QueryEndAtConstraint(_value, _key) {
13440 var _this = _super.call(this) || this;
13441 _this._value = _value;
13442 _this._key = _key;
13443 return _this;
13444 }
13445 QueryEndAtConstraint.prototype._apply = function (query) {
13446 validateFirebaseDataArg('endAt', this._value, query._path, true);
13447 var newParams = queryParamsEndAt(query._queryParams, this._value, this._key);
13448 validateLimit(newParams);
13449 validateQueryEndpoints(newParams);
13450 if (query._queryParams.hasEnd()) {
13451 throw new Error('endAt: Starting point was already set (by another call to endAt, ' +
13452 'endBefore or equalTo).');
13453 }
13454 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13455 };
13456 return QueryEndAtConstraint;
13457}(QueryConstraint));
13458/**
13459 * Creates a `QueryConstraint` with the specified ending point.
13460 *
13461 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13462 * allows you to choose arbitrary starting and ending points for your queries.
13463 *
13464 * The ending point is inclusive, so children with exactly the specified value
13465 * will be included in the query. The optional key argument can be used to
13466 * further limit the range of the query. If it is specified, then children that
13467 * have exactly the specified value must also have a key name less than or equal
13468 * to the specified key.
13469 *
13470 * You can read more about `endAt()` in
13471 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13472 *
13473 * @param value - The value to end at. The argument type depends on which
13474 * `orderBy*()` function was used in this query. Specify a value that matches
13475 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13476 * value must be a string.
13477 * @param key - The child key to end at, among the children with the previously
13478 * specified priority. This argument is only allowed if ordering by child,
13479 * value, or priority.
13480 */
13481function endAt(value, key) {
13482 validateKey('endAt', 'key', key, true);
13483 return new QueryEndAtConstraint(value, key);
13484}
13485var QueryEndBeforeConstraint = /** @class */ (function (_super) {
13486 tslib.__extends(QueryEndBeforeConstraint, _super);
13487 function QueryEndBeforeConstraint(_value, _key) {
13488 var _this = _super.call(this) || this;
13489 _this._value = _value;
13490 _this._key = _key;
13491 return _this;
13492 }
13493 QueryEndBeforeConstraint.prototype._apply = function (query) {
13494 validateFirebaseDataArg('endBefore', this._value, query._path, false);
13495 var newParams = queryParamsEndBefore(query._queryParams, this._value, this._key);
13496 validateLimit(newParams);
13497 validateQueryEndpoints(newParams);
13498 if (query._queryParams.hasEnd()) {
13499 throw new Error('endBefore: Starting point was already set (by another call to endAt, ' +
13500 'endBefore or equalTo).');
13501 }
13502 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13503 };
13504 return QueryEndBeforeConstraint;
13505}(QueryConstraint));
13506/**
13507 * Creates a `QueryConstraint` with the specified ending point (exclusive).
13508 *
13509 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13510 * allows you to choose arbitrary starting and ending points for your queries.
13511 *
13512 * The ending point is exclusive. If only a value is provided, children
13513 * with a value less than the specified value will be included in the query.
13514 * If a key is specified, then children must have a value less than or equal
13515 * to the specified value and a key name less than the specified key.
13516 *
13517 * @param value - The value to end before. The argument type depends on which
13518 * `orderBy*()` function was used in this query. Specify a value that matches
13519 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13520 * value must be a string.
13521 * @param key - The child key to end before, among the children with the
13522 * previously specified priority. This argument is only allowed if ordering by
13523 * child, value, or priority.
13524 */
13525function endBefore(value, key) {
13526 validateKey('endBefore', 'key', key, true);
13527 return new QueryEndBeforeConstraint(value, key);
13528}
13529var QueryStartAtConstraint = /** @class */ (function (_super) {
13530 tslib.__extends(QueryStartAtConstraint, _super);
13531 function QueryStartAtConstraint(_value, _key) {
13532 var _this = _super.call(this) || this;
13533 _this._value = _value;
13534 _this._key = _key;
13535 return _this;
13536 }
13537 QueryStartAtConstraint.prototype._apply = function (query) {
13538 validateFirebaseDataArg('startAt', this._value, query._path, true);
13539 var newParams = queryParamsStartAt(query._queryParams, this._value, this._key);
13540 validateLimit(newParams);
13541 validateQueryEndpoints(newParams);
13542 if (query._queryParams.hasStart()) {
13543 throw new Error('startAt: Starting point was already set (by another call to startAt, ' +
13544 'startBefore or equalTo).');
13545 }
13546 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13547 };
13548 return QueryStartAtConstraint;
13549}(QueryConstraint));
13550/**
13551 * Creates a `QueryConstraint` with the specified starting point.
13552 *
13553 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13554 * allows you to choose arbitrary starting and ending points for your queries.
13555 *
13556 * The starting point is inclusive, so children with exactly the specified value
13557 * will be included in the query. The optional key argument can be used to
13558 * further limit the range of the query. If it is specified, then children that
13559 * have exactly the specified value must also have a key name greater than or
13560 * equal to the specified key.
13561 *
13562 * You can read more about `startAt()` in
13563 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13564 *
13565 * @param value - The value to start at. The argument type depends on which
13566 * `orderBy*()` function was used in this query. Specify a value that matches
13567 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13568 * value must be a string.
13569 * @param key - The child key to start at. This argument is only allowed if
13570 * ordering by child, value, or priority.
13571 */
13572function startAt(value, key) {
13573 if (value === void 0) { value = null; }
13574 validateKey('startAt', 'key', key, true);
13575 return new QueryStartAtConstraint(value, key);
13576}
13577var QueryStartAfterConstraint = /** @class */ (function (_super) {
13578 tslib.__extends(QueryStartAfterConstraint, _super);
13579 function QueryStartAfterConstraint(_value, _key) {
13580 var _this = _super.call(this) || this;
13581 _this._value = _value;
13582 _this._key = _key;
13583 return _this;
13584 }
13585 QueryStartAfterConstraint.prototype._apply = function (query) {
13586 validateFirebaseDataArg('startAfter', this._value, query._path, false);
13587 var newParams = queryParamsStartAfter(query._queryParams, this._value, this._key);
13588 validateLimit(newParams);
13589 validateQueryEndpoints(newParams);
13590 if (query._queryParams.hasStart()) {
13591 throw new Error('startAfter: Starting point was already set (by another call to startAt, ' +
13592 'startAfter, or equalTo).');
13593 }
13594 return new QueryImpl(query._repo, query._path, newParams, query._orderByCalled);
13595 };
13596 return QueryStartAfterConstraint;
13597}(QueryConstraint));
13598/**
13599 * Creates a `QueryConstraint` with the specified starting point (exclusive).
13600 *
13601 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13602 * allows you to choose arbitrary starting and ending points for your queries.
13603 *
13604 * The starting point is exclusive. If only a value is provided, children
13605 * with a value greater than the specified value will be included in the query.
13606 * If a key is specified, then children must have a value greater than or equal
13607 * to the specified value and a a key name greater than the specified key.
13608 *
13609 * @param value - The value to start after. The argument type depends on which
13610 * `orderBy*()` function was used in this query. Specify a value that matches
13611 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13612 * value must be a string.
13613 * @param key - The child key to start after. This argument is only allowed if
13614 * ordering by child, value, or priority.
13615 */
13616function startAfter(value, key) {
13617 validateKey('startAfter', 'key', key, true);
13618 return new QueryStartAfterConstraint(value, key);
13619}
13620var QueryLimitToFirstConstraint = /** @class */ (function (_super) {
13621 tslib.__extends(QueryLimitToFirstConstraint, _super);
13622 function QueryLimitToFirstConstraint(_limit) {
13623 var _this = _super.call(this) || this;
13624 _this._limit = _limit;
13625 return _this;
13626 }
13627 QueryLimitToFirstConstraint.prototype._apply = function (query) {
13628 if (query._queryParams.hasLimit()) {
13629 throw new Error('limitToFirst: Limit was already set (by another call to limitToFirst ' +
13630 'or limitToLast).');
13631 }
13632 return new QueryImpl(query._repo, query._path, queryParamsLimitToFirst(query._queryParams, this._limit), query._orderByCalled);
13633 };
13634 return QueryLimitToFirstConstraint;
13635}(QueryConstraint));
13636/**
13637 * Creates a new `QueryConstraint` that if limited to the first specific number
13638 * of children.
13639 *
13640 * The `limitToFirst()` method is used to set a maximum number of children to be
13641 * synced for a given callback. If we set a limit of 100, we will initially only
13642 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13643 * stored in our Database, a `child_added` event will fire for each message.
13644 * However, if we have over 100 messages, we will only receive a `child_added`
13645 * event for the first 100 ordered messages. As items change, we will receive
13646 * `child_removed` events for each item that drops out of the active list so
13647 * that the total number stays at 100.
13648 *
13649 * You can read more about `limitToFirst()` in
13650 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13651 *
13652 * @param limit - The maximum number of nodes to include in this query.
13653 */
13654function limitToFirst(limit) {
13655 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13656 throw new Error('limitToFirst: First argument must be a positive integer.');
13657 }
13658 return new QueryLimitToFirstConstraint(limit);
13659}
13660var QueryLimitToLastConstraint = /** @class */ (function (_super) {
13661 tslib.__extends(QueryLimitToLastConstraint, _super);
13662 function QueryLimitToLastConstraint(_limit) {
13663 var _this = _super.call(this) || this;
13664 _this._limit = _limit;
13665 return _this;
13666 }
13667 QueryLimitToLastConstraint.prototype._apply = function (query) {
13668 if (query._queryParams.hasLimit()) {
13669 throw new Error('limitToLast: Limit was already set (by another call to limitToFirst ' +
13670 'or limitToLast).');
13671 }
13672 return new QueryImpl(query._repo, query._path, queryParamsLimitToLast(query._queryParams, this._limit), query._orderByCalled);
13673 };
13674 return QueryLimitToLastConstraint;
13675}(QueryConstraint));
13676/**
13677 * Creates a new `QueryConstraint` that is limited to return only the last
13678 * specified number of children.
13679 *
13680 * The `limitToLast()` method is used to set a maximum number of children to be
13681 * synced for a given callback. If we set a limit of 100, we will initially only
13682 * receive up to 100 `child_added` events. If we have fewer than 100 messages
13683 * stored in our Database, a `child_added` event will fire for each message.
13684 * However, if we have over 100 messages, we will only receive a `child_added`
13685 * event for the last 100 ordered messages. As items change, we will receive
13686 * `child_removed` events for each item that drops out of the active list so
13687 * that the total number stays at 100.
13688 *
13689 * You can read more about `limitToLast()` in
13690 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13691 *
13692 * @param limit - The maximum number of nodes to include in this query.
13693 */
13694function limitToLast(limit) {
13695 if (typeof limit !== 'number' || Math.floor(limit) !== limit || limit <= 0) {
13696 throw new Error('limitToLast: First argument must be a positive integer.');
13697 }
13698 return new QueryLimitToLastConstraint(limit);
13699}
13700var QueryOrderByChildConstraint = /** @class */ (function (_super) {
13701 tslib.__extends(QueryOrderByChildConstraint, _super);
13702 function QueryOrderByChildConstraint(_path) {
13703 var _this = _super.call(this) || this;
13704 _this._path = _path;
13705 return _this;
13706 }
13707 QueryOrderByChildConstraint.prototype._apply = function (query) {
13708 validateNoPreviousOrderByCall(query, 'orderByChild');
13709 var parsedPath = new Path(this._path);
13710 if (pathIsEmpty(parsedPath)) {
13711 throw new Error('orderByChild: cannot pass in empty path. Use orderByValue() instead.');
13712 }
13713 var index = new PathIndex(parsedPath);
13714 var newParams = queryParamsOrderBy(query._queryParams, index);
13715 validateQueryEndpoints(newParams);
13716 return new QueryImpl(query._repo, query._path, newParams,
13717 /*orderByCalled=*/ true);
13718 };
13719 return QueryOrderByChildConstraint;
13720}(QueryConstraint));
13721/**
13722 * Creates a new `QueryConstraint` that orders by the specified child key.
13723 *
13724 * Queries can only order by one key at a time. Calling `orderByChild()`
13725 * multiple times on the same query is an error.
13726 *
13727 * Firebase queries allow you to order your data by any child key on the fly.
13728 * However, if you know in advance what your indexes will be, you can define
13729 * them via the .indexOn rule in your Security Rules for better performance. See
13730 * the{@link https://firebase.google.com/docs/database/security/indexing-data}
13731 * rule for more information.
13732 *
13733 * You can read more about `orderByChild()` in
13734 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13735 *
13736 * @param path - The path to order by.
13737 */
13738function orderByChild(path) {
13739 if (path === '$key') {
13740 throw new Error('orderByChild: "$key" is invalid. Use orderByKey() instead.');
13741 }
13742 else if (path === '$priority') {
13743 throw new Error('orderByChild: "$priority" is invalid. Use orderByPriority() instead.');
13744 }
13745 else if (path === '$value') {
13746 throw new Error('orderByChild: "$value" is invalid. Use orderByValue() instead.');
13747 }
13748 validatePathString('orderByChild', 'path', path, false);
13749 return new QueryOrderByChildConstraint(path);
13750}
13751var QueryOrderByKeyConstraint = /** @class */ (function (_super) {
13752 tslib.__extends(QueryOrderByKeyConstraint, _super);
13753 function QueryOrderByKeyConstraint() {
13754 return _super !== null && _super.apply(this, arguments) || this;
13755 }
13756 QueryOrderByKeyConstraint.prototype._apply = function (query) {
13757 validateNoPreviousOrderByCall(query, 'orderByKey');
13758 var newParams = queryParamsOrderBy(query._queryParams, KEY_INDEX);
13759 validateQueryEndpoints(newParams);
13760 return new QueryImpl(query._repo, query._path, newParams,
13761 /*orderByCalled=*/ true);
13762 };
13763 return QueryOrderByKeyConstraint;
13764}(QueryConstraint));
13765/**
13766 * Creates a new `QueryConstraint` that orders by the key.
13767 *
13768 * Sorts the results of a query by their (ascending) key values.
13769 *
13770 * You can read more about `orderByKey()` in
13771 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13772 */
13773function orderByKey() {
13774 return new QueryOrderByKeyConstraint();
13775}
13776var QueryOrderByPriorityConstraint = /** @class */ (function (_super) {
13777 tslib.__extends(QueryOrderByPriorityConstraint, _super);
13778 function QueryOrderByPriorityConstraint() {
13779 return _super !== null && _super.apply(this, arguments) || this;
13780 }
13781 QueryOrderByPriorityConstraint.prototype._apply = function (query) {
13782 validateNoPreviousOrderByCall(query, 'orderByPriority');
13783 var newParams = queryParamsOrderBy(query._queryParams, PRIORITY_INDEX);
13784 validateQueryEndpoints(newParams);
13785 return new QueryImpl(query._repo, query._path, newParams,
13786 /*orderByCalled=*/ true);
13787 };
13788 return QueryOrderByPriorityConstraint;
13789}(QueryConstraint));
13790/**
13791 * Creates a new `QueryConstraint` that orders by priority.
13792 *
13793 * Applications need not use priority but can order collections by
13794 * ordinary properties (see
13795 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}
13796 * for alternatives to priority.
13797 */
13798function orderByPriority() {
13799 return new QueryOrderByPriorityConstraint();
13800}
13801var QueryOrderByValueConstraint = /** @class */ (function (_super) {
13802 tslib.__extends(QueryOrderByValueConstraint, _super);
13803 function QueryOrderByValueConstraint() {
13804 return _super !== null && _super.apply(this, arguments) || this;
13805 }
13806 QueryOrderByValueConstraint.prototype._apply = function (query) {
13807 validateNoPreviousOrderByCall(query, 'orderByValue');
13808 var newParams = queryParamsOrderBy(query._queryParams, VALUE_INDEX);
13809 validateQueryEndpoints(newParams);
13810 return new QueryImpl(query._repo, query._path, newParams,
13811 /*orderByCalled=*/ true);
13812 };
13813 return QueryOrderByValueConstraint;
13814}(QueryConstraint));
13815/**
13816 * Creates a new `QueryConstraint` that orders by value.
13817 *
13818 * If the children of a query are all scalar values (string, number, or
13819 * boolean), you can order the results by their (ascending) values.
13820 *
13821 * You can read more about `orderByValue()` in
13822 * {@link https://firebase.google.com/docs/database/web/lists-of-data#sort_data | Sort data}.
13823 */
13824function orderByValue() {
13825 return new QueryOrderByValueConstraint();
13826}
13827var QueryEqualToValueConstraint = /** @class */ (function (_super) {
13828 tslib.__extends(QueryEqualToValueConstraint, _super);
13829 function QueryEqualToValueConstraint(_value, _key) {
13830 var _this = _super.call(this) || this;
13831 _this._value = _value;
13832 _this._key = _key;
13833 return _this;
13834 }
13835 QueryEqualToValueConstraint.prototype._apply = function (query) {
13836 validateFirebaseDataArg('equalTo', this._value, query._path, false);
13837 if (query._queryParams.hasStart()) {
13838 throw new Error('equalTo: Starting point was already set (by another call to startAt/startAfter or ' +
13839 'equalTo).');
13840 }
13841 if (query._queryParams.hasEnd()) {
13842 throw new Error('equalTo: Ending point was already set (by another call to endAt/endBefore or ' +
13843 'equalTo).');
13844 }
13845 return new QueryEndAtConstraint(this._value, this._key)._apply(new QueryStartAtConstraint(this._value, this._key)._apply(query));
13846 };
13847 return QueryEqualToValueConstraint;
13848}(QueryConstraint));
13849/**
13850 * Creates a `QueryConstraint` that includes children that match the specified
13851 * value.
13852 *
13853 * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()`
13854 * allows you to choose arbitrary starting and ending points for your queries.
13855 *
13856 * The optional key argument can be used to further limit the range of the
13857 * query. If it is specified, then children that have exactly the specified
13858 * value must also have exactly the specified key as their key name. This can be
13859 * used to filter result sets with many matches for the same value.
13860 *
13861 * You can read more about `equalTo()` in
13862 * {@link https://firebase.google.com/docs/database/web/lists-of-data#filtering_data | Filtering data}.
13863 *
13864 * @param value - The value to match for. The argument type depends on which
13865 * `orderBy*()` function was used in this query. Specify a value that matches
13866 * the `orderBy*()` type. When used in combination with `orderByKey()`, the
13867 * value must be a string.
13868 * @param key - The child key to start at, among the children with the
13869 * previously specified priority. This argument is only allowed if ordering by
13870 * child, value, or priority.
13871 */
13872function equalTo(value, key) {
13873 validateKey('equalTo', 'key', key, true);
13874 return new QueryEqualToValueConstraint(value, key);
13875}
13876/**
13877 * Creates a new immutable instance of `Query` that is extended to also include
13878 * additional query constraints.
13879 *
13880 * @param query - The Query instance to use as a base for the new constraints.
13881 * @param queryConstraints - The list of `QueryConstraint`s to apply.
13882 * @throws if any of the provided query constraints cannot be combined with the
13883 * existing or new constraints.
13884 */
13885function query(query) {
13886 var e_1, _a;
13887 var queryConstraints = [];
13888 for (var _i = 1; _i < arguments.length; _i++) {
13889 queryConstraints[_i - 1] = arguments[_i];
13890 }
13891 var queryImpl = util.getModularInstance(query);
13892 try {
13893 for (var queryConstraints_1 = tslib.__values(queryConstraints), queryConstraints_1_1 = queryConstraints_1.next(); !queryConstraints_1_1.done; queryConstraints_1_1 = queryConstraints_1.next()) {
13894 var constraint = queryConstraints_1_1.value;
13895 queryImpl = constraint._apply(queryImpl);
13896 }
13897 }
13898 catch (e_1_1) { e_1 = { error: e_1_1 }; }
13899 finally {
13900 try {
13901 if (queryConstraints_1_1 && !queryConstraints_1_1.done && (_a = queryConstraints_1.return)) _a.call(queryConstraints_1);
13902 }
13903 finally { if (e_1) throw e_1.error; }
13904 }
13905 return queryImpl;
13906}
13907/**
13908 * Define reference constructor in various modules
13909 *
13910 * We are doing this here to avoid several circular
13911 * dependency issues
13912 */
13913syncPointSetReferenceConstructor(ReferenceImpl);
13914syncTreeSetReferenceConstructor(ReferenceImpl);
13915
13916/**
13917 * This variable is also defined in the firebase Node.js Admin SDK. Before
13918 * modifying this definition, consult the definition in:
13919 *
13920 * https://github.com/firebase/firebase-admin-node
13921 *
13922 * and make sure the two are consistent.
13923 */
13924var FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST';
13925/**
13926 * Creates and caches `Repo` instances.
13927 */
13928var repos = {};
13929/**
13930 * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes).
13931 */
13932var useRestClient = false;
13933/**
13934 * Update an existing `Repo` in place to point to a new host/port.
13935 */
13936function repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider) {
13937 repo.repoInfo_ = new RepoInfo("".concat(host, ":").concat(port),
13938 /* secure= */ false, repo.repoInfo_.namespace, repo.repoInfo_.webSocketOnly, repo.repoInfo_.nodeAdmin, repo.repoInfo_.persistenceKey, repo.repoInfo_.includeNamespaceInQueryParams,
13939 /*isUsingEmulator=*/ true);
13940 if (tokenProvider) {
13941 repo.authTokenProvider_ = tokenProvider;
13942 }
13943}
13944/**
13945 * This function should only ever be called to CREATE a new database instance.
13946 * @internal
13947 */
13948function repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url, nodeAdmin) {
13949 var dbUrl = url || app.options.databaseURL;
13950 if (dbUrl === undefined) {
13951 if (!app.options.projectId) {
13952 fatal("Can't determine Firebase Database URL. Be sure to include " +
13953 ' a Project ID when calling firebase.initializeApp().');
13954 }
13955 log('Using default host for project ', app.options.projectId);
13956 dbUrl = "".concat(app.options.projectId, "-default-rtdb.firebaseio.com");
13957 }
13958 var parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
13959 var repoInfo = parsedUrl.repoInfo;
13960 var isEmulator;
13961 var dbEmulatorHost = undefined;
13962 if (typeof process !== 'undefined' && process.env) {
13963 dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR];
13964 }
13965 if (dbEmulatorHost) {
13966 isEmulator = true;
13967 dbUrl = "http://".concat(dbEmulatorHost, "?ns=").concat(repoInfo.namespace);
13968 parsedUrl = parseRepoInfo(dbUrl, nodeAdmin);
13969 repoInfo = parsedUrl.repoInfo;
13970 }
13971 else {
13972 isEmulator = !parsedUrl.repoInfo.secure;
13973 }
13974 var authTokenProvider = nodeAdmin && isEmulator
13975 ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER)
13976 : new FirebaseAuthTokenProvider(app.name, app.options, authProvider);
13977 validateUrl('Invalid Firebase Database URL', parsedUrl);
13978 if (!pathIsEmpty(parsedUrl.path)) {
13979 fatal('Database URL must point to the root of a Firebase Database ' +
13980 '(not including a child path).');
13981 }
13982 var repo = repoManagerCreateRepo(repoInfo, app, authTokenProvider, new AppCheckTokenProvider(app.name, appCheckProvider));
13983 return new Database(repo, app);
13984}
13985/**
13986 * Remove the repo and make sure it is disconnected.
13987 *
13988 */
13989function repoManagerDeleteRepo(repo, appName) {
13990 var appRepos = repos[appName];
13991 // This should never happen...
13992 if (!appRepos || appRepos[repo.key] !== repo) {
13993 fatal("Database ".concat(appName, "(").concat(repo.repoInfo_, ") has already been deleted."));
13994 }
13995 repoInterrupt(repo);
13996 delete appRepos[repo.key];
13997}
13998/**
13999 * Ensures a repo doesn't already exist and then creates one using the
14000 * provided app.
14001 *
14002 * @param repoInfo - The metadata about the Repo
14003 * @returns The Repo object for the specified server / repoName.
14004 */
14005function repoManagerCreateRepo(repoInfo, app, authTokenProvider, appCheckProvider) {
14006 var appRepos = repos[app.name];
14007 if (!appRepos) {
14008 appRepos = {};
14009 repos[app.name] = appRepos;
14010 }
14011 var repo = appRepos[repoInfo.toURLString()];
14012 if (repo) {
14013 fatal('Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.');
14014 }
14015 repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider);
14016 appRepos[repoInfo.toURLString()] = repo;
14017 return repo;
14018}
14019/**
14020 * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos.
14021 */
14022function repoManagerForceRestClient(forceRestClient) {
14023 useRestClient = forceRestClient;
14024}
14025/**
14026 * Class representing a Firebase Realtime Database.
14027 */
14028var Database = /** @class */ (function () {
14029 /** @hideconstructor */
14030 function Database(_repoInternal,
14031 /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */
14032 app) {
14033 this._repoInternal = _repoInternal;
14034 this.app = app;
14035 /** Represents a `Database` instance. */
14036 this['type'] = 'database';
14037 /** Track if the instance has been used (root or repo accessed) */
14038 this._instanceStarted = false;
14039 }
14040 Object.defineProperty(Database.prototype, "_repo", {
14041 get: function () {
14042 if (!this._instanceStarted) {
14043 repoStart(this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride']);
14044 this._instanceStarted = true;
14045 }
14046 return this._repoInternal;
14047 },
14048 enumerable: false,
14049 configurable: true
14050 });
14051 Object.defineProperty(Database.prototype, "_root", {
14052 get: function () {
14053 if (!this._rootInternal) {
14054 this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath());
14055 }
14056 return this._rootInternal;
14057 },
14058 enumerable: false,
14059 configurable: true
14060 });
14061 Database.prototype._delete = function () {
14062 if (this._rootInternal !== null) {
14063 repoManagerDeleteRepo(this._repo, this.app.name);
14064 this._repoInternal = null;
14065 this._rootInternal = null;
14066 }
14067 return Promise.resolve();
14068 };
14069 Database.prototype._checkNotDeleted = function (apiName) {
14070 if (this._rootInternal === null) {
14071 fatal('Cannot call ' + apiName + ' on a deleted database.');
14072 }
14073 };
14074 return Database;
14075}());
14076function checkTransportInit() {
14077 if (TransportManager.IS_TRANSPORT_INITIALIZED) {
14078 warn('Transport has already been initialized. Please call this function before calling ref or setting up a listener');
14079 }
14080}
14081/**
14082 * Force the use of websockets instead of longPolling.
14083 */
14084function forceWebSockets() {
14085 checkTransportInit();
14086 BrowserPollConnection.forceDisallow();
14087}
14088/**
14089 * Force the use of longPolling instead of websockets. This will be ignored if websocket protocol is used in databaseURL.
14090 */
14091function forceLongPolling() {
14092 checkTransportInit();
14093 WebSocketConnection.forceDisallow();
14094 BrowserPollConnection.forceAllow();
14095}
14096/**
14097 * Returns the instance of the Realtime Database SDK that is associated
14098 * with the provided {@link @firebase/app#FirebaseApp}. Initializes a new instance with
14099 * with default settings if no instance exists or if the existing instance uses
14100 * a custom database URL.
14101 *
14102 * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned Realtime
14103 * Database instance is associated with.
14104 * @param url - The URL of the Realtime Database instance to connect to. If not
14105 * provided, the SDK connects to the default instance of the Firebase App.
14106 * @returns The `Database` instance of the provided app.
14107 */
14108function getDatabase(app$1, url) {
14109 if (app$1 === void 0) { app$1 = app.getApp(); }
14110 var db = app._getProvider(app$1, 'database').getImmediate({
14111 identifier: url
14112 });
14113 if (!db._instanceStarted) {
14114 var emulator = util.getDefaultEmulatorHostnameAndPort('database');
14115 if (emulator) {
14116 connectDatabaseEmulator.apply(void 0, tslib.__spreadArray([db], tslib.__read(emulator), false));
14117 }
14118 }
14119 return db;
14120}
14121/**
14122 * Modify the provided instance to communicate with the Realtime Database
14123 * emulator.
14124 *
14125 * <p>Note: This method must be called before performing any other operation.
14126 *
14127 * @param db - The instance to modify.
14128 * @param host - The emulator host (ex: localhost)
14129 * @param port - The emulator port (ex: 8080)
14130 * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules
14131 */
14132function connectDatabaseEmulator(db, host, port, options) {
14133 if (options === void 0) { options = {}; }
14134 db = util.getModularInstance(db);
14135 db._checkNotDeleted('useEmulator');
14136 if (db._instanceStarted) {
14137 fatal('Cannot call useEmulator() after instance has already been initialized.');
14138 }
14139 var repo = db._repoInternal;
14140 var tokenProvider = undefined;
14141 if (repo.repoInfo_.nodeAdmin) {
14142 if (options.mockUserToken) {
14143 fatal('mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".');
14144 }
14145 tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
14146 }
14147 else if (options.mockUserToken) {
14148 var token = typeof options.mockUserToken === 'string'
14149 ? options.mockUserToken
14150 : util.createMockUserToken(options.mockUserToken, db.app.options.projectId);
14151 tokenProvider = new EmulatorTokenProvider(token);
14152 }
14153 // Modify the repo to apply emulator settings
14154 repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider);
14155}
14156/**
14157 * Disconnects from the server (all Database operations will be completed
14158 * offline).
14159 *
14160 * The client automatically maintains a persistent connection to the Database
14161 * server, which will remain active indefinitely and reconnect when
14162 * disconnected. However, the `goOffline()` and `goOnline()` methods may be used
14163 * to control the client connection in cases where a persistent connection is
14164 * undesirable.
14165 *
14166 * While offline, the client will no longer receive data updates from the
14167 * Database. However, all Database operations performed locally will continue to
14168 * immediately fire events, allowing your application to continue behaving
14169 * normally. Additionally, each operation performed locally will automatically
14170 * be queued and retried upon reconnection to the Database server.
14171 *
14172 * To reconnect to the Database and begin receiving remote events, see
14173 * `goOnline()`.
14174 *
14175 * @param db - The instance to disconnect.
14176 */
14177function goOffline(db) {
14178 db = util.getModularInstance(db);
14179 db._checkNotDeleted('goOffline');
14180 repoInterrupt(db._repo);
14181}
14182/**
14183 * Reconnects to the server and synchronizes the offline Database state
14184 * with the server state.
14185 *
14186 * This method should be used after disabling the active connection with
14187 * `goOffline()`. Once reconnected, the client will transmit the proper data
14188 * and fire the appropriate events so that your client "catches up"
14189 * automatically.
14190 *
14191 * @param db - The instance to reconnect.
14192 */
14193function goOnline(db) {
14194 db = util.getModularInstance(db);
14195 db._checkNotDeleted('goOnline');
14196 repoResume(db._repo);
14197}
14198function enableLogging(logger, persistent) {
14199 enableLogging$1(logger, persistent);
14200}
14201
14202/**
14203 * @license
14204 * Copyright 2021 Google LLC
14205 *
14206 * Licensed under the Apache License, Version 2.0 (the "License");
14207 * you may not use this file except in compliance with the License.
14208 * You may obtain a copy of the License at
14209 *
14210 * http://www.apache.org/licenses/LICENSE-2.0
14211 *
14212 * Unless required by applicable law or agreed to in writing, software
14213 * distributed under the License is distributed on an "AS IS" BASIS,
14214 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14215 * See the License for the specific language governing permissions and
14216 * limitations under the License.
14217 */
14218function registerDatabase(variant) {
14219 setSDKVersion(app.SDK_VERSION);
14220 app._registerComponent(new component.Component('database', function (container, _a) {
14221 var url = _a.instanceIdentifier;
14222 var app = container.getProvider('app').getImmediate();
14223 var authProvider = container.getProvider('auth-internal');
14224 var appCheckProvider = container.getProvider('app-check-internal');
14225 return repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url);
14226 }, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
14227 app.registerVersion(name, version, variant);
14228 // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
14229 app.registerVersion(name, version, 'cjs5');
14230}
14231
14232/**
14233 * @license
14234 * Copyright 2020 Google LLC
14235 *
14236 * Licensed under the Apache License, Version 2.0 (the "License");
14237 * you may not use this file except in compliance with the License.
14238 * You may obtain a copy of the License at
14239 *
14240 * http://www.apache.org/licenses/LICENSE-2.0
14241 *
14242 * Unless required by applicable law or agreed to in writing, software
14243 * distributed under the License is distributed on an "AS IS" BASIS,
14244 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14245 * See the License for the specific language governing permissions and
14246 * limitations under the License.
14247 */
14248var SERVER_TIMESTAMP = {
14249 '.sv': 'timestamp'
14250};
14251/**
14252 * Returns a placeholder value for auto-populating the current timestamp (time
14253 * since the Unix epoch, in milliseconds) as determined by the Firebase
14254 * servers.
14255 */
14256function serverTimestamp() {
14257 return SERVER_TIMESTAMP;
14258}
14259/**
14260 * Returns a placeholder value that can be used to atomically increment the
14261 * current database value by the provided delta.
14262 *
14263 * @param delta - the amount to modify the current value atomically.
14264 * @returns A placeholder value for modifying data atomically server-side.
14265 */
14266function increment(delta) {
14267 return {
14268 '.sv': {
14269 'increment': delta
14270 }
14271 };
14272}
14273
14274/**
14275 * @license
14276 * Copyright 2020 Google LLC
14277 *
14278 * Licensed under the Apache License, Version 2.0 (the "License");
14279 * you may not use this file except in compliance with the License.
14280 * You may obtain a copy of the License at
14281 *
14282 * http://www.apache.org/licenses/LICENSE-2.0
14283 *
14284 * Unless required by applicable law or agreed to in writing, software
14285 * distributed under the License is distributed on an "AS IS" BASIS,
14286 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14287 * See the License for the specific language governing permissions and
14288 * limitations under the License.
14289 */
14290/**
14291 * A type for the resolve value of {@link runTransaction}.
14292 */
14293var TransactionResult = /** @class */ (function () {
14294 /** @hideconstructor */
14295 function TransactionResult(
14296 /** Whether the transaction was successfully committed. */
14297 committed,
14298 /** The resulting data snapshot. */
14299 snapshot) {
14300 this.committed = committed;
14301 this.snapshot = snapshot;
14302 }
14303 /** Returns a JSON-serializable representation of this object. */
14304 TransactionResult.prototype.toJSON = function () {
14305 return { committed: this.committed, snapshot: this.snapshot.toJSON() };
14306 };
14307 return TransactionResult;
14308}());
14309/**
14310 * Atomically modifies the data at this location.
14311 *
14312 * Atomically modify the data at this location. Unlike a normal `set()`, which
14313 * just overwrites the data regardless of its previous value, `runTransaction()` is
14314 * used to modify the existing value to a new value, ensuring there are no
14315 * conflicts with other clients writing to the same location at the same time.
14316 *
14317 * To accomplish this, you pass `runTransaction()` an update function which is
14318 * used to transform the current value into a new value. If another client
14319 * writes to the location before your new value is successfully written, your
14320 * update function will be called again with the new current value, and the
14321 * write will be retried. This will happen repeatedly until your write succeeds
14322 * without conflict or you abort the transaction by not returning a value from
14323 * your update function.
14324 *
14325 * Note: Modifying data with `set()` will cancel any pending transactions at
14326 * that location, so extreme care should be taken if mixing `set()` and
14327 * `runTransaction()` to update the same data.
14328 *
14329 * Note: When using transactions with Security and Firebase Rules in place, be
14330 * aware that a client needs `.read` access in addition to `.write` access in
14331 * order to perform a transaction. This is because the client-side nature of
14332 * transactions requires the client to read the data in order to transactionally
14333 * update it.
14334 *
14335 * @param ref - The location to atomically modify.
14336 * @param transactionUpdate - A developer-supplied function which will be passed
14337 * the current data stored at this location (as a JavaScript object). The
14338 * function should return the new value it would like written (as a JavaScript
14339 * object). If `undefined` is returned (i.e. you return with no arguments) the
14340 * transaction will be aborted and the data at this location will not be
14341 * modified.
14342 * @param options - An options object to configure transactions.
14343 * @returns A `Promise` that can optionally be used instead of the `onComplete`
14344 * callback to handle success and failure.
14345 */
14346function runTransaction(ref,
14347// eslint-disable-next-line @typescript-eslint/no-explicit-any
14348transactionUpdate, options) {
14349 var _a;
14350 ref = util.getModularInstance(ref);
14351 validateWritablePath('Reference.transaction', ref._path);
14352 if (ref.key === '.length' || ref.key === '.keys') {
14353 throw ('Reference.transaction failed: ' + ref.key + ' is a read-only object.');
14354 }
14355 var applyLocally = (_a = options === null || options === void 0 ? void 0 : options.applyLocally) !== null && _a !== void 0 ? _a : true;
14356 var deferred = new util.Deferred();
14357 var promiseComplete = function (error, committed, node) {
14358 var dataSnapshot = null;
14359 if (error) {
14360 deferred.reject(error);
14361 }
14362 else {
14363 dataSnapshot = new DataSnapshot(node, new ReferenceImpl(ref._repo, ref._path), PRIORITY_INDEX);
14364 deferred.resolve(new TransactionResult(committed, dataSnapshot));
14365 }
14366 };
14367 // Add a watch to make sure we get server updates.
14368 var unwatcher = onValue(ref, function () { });
14369 repoStartTransaction(ref._repo, ref._path, transactionUpdate, promiseComplete, unwatcher, applyLocally);
14370 return deferred.promise;
14371}
14372
14373/**
14374 * @license
14375 * Copyright 2017 Google LLC
14376 *
14377 * Licensed under the Apache License, Version 2.0 (the "License");
14378 * you may not use this file except in compliance with the License.
14379 * You may obtain a copy of the License at
14380 *
14381 * http://www.apache.org/licenses/LICENSE-2.0
14382 *
14383 * Unless required by applicable law or agreed to in writing, software
14384 * distributed under the License is distributed on an "AS IS" BASIS,
14385 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14386 * See the License for the specific language governing permissions and
14387 * limitations under the License.
14388 */
14389// eslint-disable-next-line @typescript-eslint/no-explicit-any
14390PersistentConnection.prototype.simpleListen = function (pathString, onComplete) {
14391 this.sendRequest('q', { p: pathString }, onComplete);
14392};
14393// eslint-disable-next-line @typescript-eslint/no-explicit-any
14394PersistentConnection.prototype.echo = function (data, onEcho) {
14395 this.sendRequest('echo', { d: data }, onEcho);
14396};
14397/**
14398 * @internal
14399 */
14400var hijackHash = function (newHash) {
14401 var oldPut = PersistentConnection.prototype.put;
14402 PersistentConnection.prototype.put = function (pathString, data, onComplete, hash) {
14403 if (hash !== undefined) {
14404 hash = newHash();
14405 }
14406 oldPut.call(this, pathString, data, onComplete, hash);
14407 };
14408 return function () {
14409 PersistentConnection.prototype.put = oldPut;
14410 };
14411};
14412/**
14413 * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection.
14414 * @internal
14415 */
14416var forceRestClient = function (forceRestClient) {
14417 repoManagerForceRestClient(forceRestClient);
14418};
14419
14420/**
14421 * @license
14422 * Copyright 2021 Google LLC
14423 *
14424 * Licensed under the Apache License, Version 2.0 (the "License");
14425 * you may not use this file except in compliance with the License.
14426 * You may obtain a copy of the License at
14427 *
14428 * http://www.apache.org/licenses/LICENSE-2.0
14429 *
14430 * Unless required by applicable law or agreed to in writing, software
14431 * distributed under the License is distributed on an "AS IS" BASIS,
14432 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14433 * See the License for the specific language governing permissions and
14434 * limitations under the License.
14435 */
14436setWebSocketImpl(Websocket__default["default"].Client);
14437registerDatabase('node');
14438
14439exports.DataSnapshot = DataSnapshot;
14440exports.Database = Database;
14441exports.OnDisconnect = OnDisconnect;
14442exports.QueryConstraint = QueryConstraint;
14443exports.TransactionResult = TransactionResult;
14444exports._QueryImpl = QueryImpl;
14445exports._QueryParams = QueryParams;
14446exports._ReferenceImpl = ReferenceImpl;
14447exports._TEST_ACCESS_forceRestClient = forceRestClient;
14448exports._TEST_ACCESS_hijackHash = hijackHash;
14449exports._repoManagerDatabaseFromApp = repoManagerDatabaseFromApp;
14450exports._setSDKVersion = setSDKVersion;
14451exports._validatePathString = validatePathString;
14452exports._validateWritablePath = validateWritablePath;
14453exports.child = child;
14454exports.connectDatabaseEmulator = connectDatabaseEmulator;
14455exports.enableLogging = enableLogging;
14456exports.endAt = endAt;
14457exports.endBefore = endBefore;
14458exports.equalTo = equalTo;
14459exports.forceLongPolling = forceLongPolling;
14460exports.forceWebSockets = forceWebSockets;
14461exports.get = get;
14462exports.getDatabase = getDatabase;
14463exports.goOffline = goOffline;
14464exports.goOnline = goOnline;
14465exports.increment = increment;
14466exports.limitToFirst = limitToFirst;
14467exports.limitToLast = limitToLast;
14468exports.off = off;
14469exports.onChildAdded = onChildAdded;
14470exports.onChildChanged = onChildChanged;
14471exports.onChildMoved = onChildMoved;
14472exports.onChildRemoved = onChildRemoved;
14473exports.onDisconnect = onDisconnect;
14474exports.onValue = onValue;
14475exports.orderByChild = orderByChild;
14476exports.orderByKey = orderByKey;
14477exports.orderByPriority = orderByPriority;
14478exports.orderByValue = orderByValue;
14479exports.push = push;
14480exports.query = query;
14481exports.ref = ref;
14482exports.refFromURL = refFromURL;
14483exports.remove = remove;
14484exports.runTransaction = runTransaction;
14485exports.serverTimestamp = serverTimestamp;
14486exports.set = set;
14487exports.setPriority = setPriority;
14488exports.setWithPriority = setWithPriority;
14489exports.startAfter = startAfter;
14490exports.startAt = startAt;
14491exports.update = update;
14492//# sourceMappingURL=index.node.cjs.js.map