UNPKG

matrix-react-sdk

Version:
700 lines (649 loc) 105 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.LIST_UPDATED_EVENT = exports.Algorithm = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _matrix = require("matrix-js-sdk/src/matrix"); var _types = require("matrix-js-sdk/src/types"); var _utils = require("matrix-js-sdk/src/utils"); var _events = require("events"); var _logger = require("matrix-js-sdk/src/logger"); var _DMRoomMap = _interopRequireDefault(require("../../../utils/DMRoomMap")); var _arrays = require("../../../utils/arrays"); var _models = require("../models"); var _membership = require("../../../utils/membership"); var _listOrdering = require("./list-ordering"); var _VisibilityProvider = require("../filters/VisibilityProvider"); var _CallStore = require("../../CallStore"); /* Copyright 2024 New Vector Ltd. Copyright 2020, 2021 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ /** * Fired when the Algorithm has determined a list has been updated. */ const LIST_UPDATED_EVENT = exports.LIST_UPDATED_EVENT = "list_updated_event"; // These are the causes which require a room to be known in order for us to handle them. If // a cause in this list is raised and we don't know about the room, we don't handle the update. // // Note: these typically happen when a new room is coming in, such as the user creating or // joining the room. For these cases, we need to know about the room prior to handling it otherwise // we'll make bad assumptions. const CAUSES_REQUIRING_ROOM = [_models.RoomUpdateCause.Timeline, _models.RoomUpdateCause.ReadReceipt]; /** * Represents a list ordering algorithm. This class will take care of tag * management (which rooms go in which tags) and ask the implementation to * deal with ordering mechanics. */ class Algorithm extends _events.EventEmitter { constructor(...args) { super(...args); (0, _defineProperty2.default)(this, "_cachedRooms", {}); (0, _defineProperty2.default)(this, "_cachedStickyRooms", {}); // a clone of the _cachedRooms, with the sticky room (0, _defineProperty2.default)(this, "_stickyRoom", null); (0, _defineProperty2.default)(this, "_lastStickyRoom", null); // only not-null when changing the sticky room (0, _defineProperty2.default)(this, "sortAlgorithms", null); (0, _defineProperty2.default)(this, "listAlgorithms", null); (0, _defineProperty2.default)(this, "algorithms", null); (0, _defineProperty2.default)(this, "rooms", []); (0, _defineProperty2.default)(this, "roomIdsToTags", {}); /** * Set to true to suspend emissions of algorithm updates. */ (0, _defineProperty2.default)(this, "updatesInhibited", false); (0, _defineProperty2.default)(this, "onConnectedCalls", () => { // In case we're unsticking a room, sort it back into natural order this.recalculateStickyRoom(); // Update the stickiness of rooms with calls this.recalculateActiveCallRooms(); if (this.updatesInhibited) return; // This isn't in response to any particular RoomListStore update, // so notify the store that it needs to force-update this.emit(LIST_UPDATED_EVENT, true); }); } start() { _CallStore.CallStore.instance.on(_CallStore.CallStoreEvent.ConnectedCalls, this.onConnectedCalls); } stop() { _CallStore.CallStore.instance.off(_CallStore.CallStoreEvent.ConnectedCalls, this.onConnectedCalls); } get stickyRoom() { return this._stickyRoom ? this._stickyRoom.room : null; } get hasTagSortingMap() { return !!this.sortAlgorithms; } set cachedRooms(val) { this._cachedRooms = val; this.recalculateStickyRoom(); this.recalculateActiveCallRooms(); } get cachedRooms() { // 🐉 Here be dragons. // Note: this is used by the underlying algorithm classes, so don't make it return // the sticky room cache. If it ends up returning the sticky room cache, we end up // corrupting our caches and confusing them. return this._cachedRooms; } /** * Awaitable version of the sticky room setter. * @param val The new room to sticky. */ setStickyRoom(val) { try { this.updateStickyRoom(val); } catch (e) { _logger.logger.warn("Failed to update sticky room", e); } } getTagSorting(tagId) { if (!this.sortAlgorithms) return null; return this.sortAlgorithms[tagId]; } setTagSorting(tagId, sort) { if (!tagId) throw new Error("Tag ID must be defined"); if (!sort) throw new Error("Algorithm must be defined"); if (!this.sortAlgorithms) throw new Error("this.sortAlgorithms must be defined before calling setTagSorting"); if (!this.algorithms) throw new Error("this.algorithms must be defined before calling setTagSorting"); this.sortAlgorithms[tagId] = sort; const algorithm = this.algorithms[tagId]; algorithm.setSortAlgorithm(sort); this._cachedRooms[tagId] = algorithm.orderedRooms; this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed this.recalculateActiveCallRooms(tagId); } getListOrdering(tagId) { if (!this.listAlgorithms) return null; return this.listAlgorithms[tagId]; } setListOrdering(tagId, order) { if (!tagId) throw new Error("Tag ID must be defined"); if (!order) throw new Error("Algorithm must be defined"); if (!this.sortAlgorithms) throw new Error("this.sortAlgorithms must be defined before calling setListOrdering"); if (!this.listAlgorithms) throw new Error("this.listAlgorithms must be defined before calling setListOrdering"); if (!this.algorithms) throw new Error("this.algorithms must be defined before calling setListOrdering"); this.listAlgorithms[tagId] = order; const algorithm = (0, _listOrdering.getListAlgorithmInstance)(order, tagId, this.sortAlgorithms[tagId]); this.algorithms[tagId] = algorithm; algorithm.setRooms(this._cachedRooms[tagId]); this._cachedRooms[tagId] = algorithm.orderedRooms; this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed this.recalculateActiveCallRooms(tagId); } updateStickyRoom(val) { this.doUpdateStickyRoom(val); this._lastStickyRoom = null; // clear to indicate we're done changing } doUpdateStickyRoom(val) { if (val?.isSpaceRoom() && val.getMyMembership() !== _types.KnownMembership.Invite) { // no-op sticky rooms for spaces - they're effectively virtual rooms val = null; } if (val && !_VisibilityProvider.VisibilityProvider.instance.isRoomVisible(val)) { val = null; // the room isn't visible - lie to the rest of this function } // Set the last sticky room to indicate that we're in a change. The code throughout the // class can safely handle a null room, so this should be safe to do as a backup. this._lastStickyRoom = this._stickyRoom || {}; // It's possible to have no selected room. In that case, clear the sticky room if (!val) { if (this._stickyRoom) { const stickyRoom = this._stickyRoom.room; this._stickyRoom = null; // clear before we go to update the algorithm // Lie to the algorithm and re-add the room to the algorithm this.handleRoomUpdate(stickyRoom, _models.RoomUpdateCause.NewRoom); return; } return; } // When we do have a room though, we expect to be able to find it let tag = this.roomIdsToTags[val.roomId]?.[0]; if (!tag) throw new Error(`${val.roomId} does not belong to a tag and cannot be sticky`); // We specifically do NOT use the ordered rooms set as it contains the sticky room, which // means we'll be off by 1 when the user is switching rooms. This leads to visual jumping // when the user is moving south in the list (not north, because of math). const tagList = this.getOrderedRoomsWithoutSticky()[tag] || []; // can be null if filtering let position = tagList.indexOf(val); // We do want to see if a tag change happened though - if this did happen then we'll want // to force the position to zero (top) to ensure we can properly handle it. const wasSticky = this._lastStickyRoom.room ? this._lastStickyRoom.room.roomId === val.roomId : false; if (this._lastStickyRoom.tag && tag !== this._lastStickyRoom.tag && wasSticky && position < 0) { _logger.logger.warn(`Sticky room ${val.roomId} changed tags during sticky room handling`); position = 0; } // Sanity check the position to make sure the room is qualified for being sticky if (position < 0) throw new Error(`${val.roomId} does not appear to be known and cannot be sticky`); // 🐉 Here be dragons. // Before we can go through with lying to the underlying algorithm about a room // we need to ensure that when we do we're ready for the inevitable sticky room // update we'll receive. To prepare for that, we first remove the sticky room and // recalculate the state ourselves so that when the underlying algorithm calls for // the same thing it no-ops. After we're done calling the algorithm, we'll issue // a new update for ourselves. const lastStickyRoom = this._stickyRoom; this._stickyRoom = null; // clear before we update the algorithm this.recalculateStickyRoom(); // When we do have the room, re-add the old room (if needed) to the algorithm // and remove the sticky room from the algorithm. This is so the underlying // algorithm doesn't try and confuse itself with the sticky room concept. // We don't add the new room if the sticky room isn't changing because that's // an easy way to cause duplication. We have to do room ID checks instead of // referential checks as the references can differ through the lifecycle. if (lastStickyRoom && lastStickyRoom.room && lastStickyRoom.room.roomId !== val.roomId) { // Lie to the algorithm and re-add the room to the algorithm this.handleRoomUpdate(lastStickyRoom.room, _models.RoomUpdateCause.NewRoom); } // Lie to the algorithm and remove the room from it's field of view this.handleRoomUpdate(val, _models.RoomUpdateCause.RoomRemoved); // handleRoomUpdate may have modified this._stickyRoom. Convince the // compiler of this fact. this._stickyRoom = this.stickyRoomMightBeModified(); // Check for tag & position changes while we're here. We also check the room to ensure // it is still the same room. if (this._stickyRoom) { if (this._stickyRoom.room !== val) { // Check the room IDs just in case if (this._stickyRoom.room.roomId === val.roomId) { _logger.logger.warn("Sticky room changed references"); } else { throw new Error("Sticky room changed while the sticky room was changing"); } } _logger.logger.warn(`Sticky room changed tag & position from ${tag} / ${position} ` + `to ${this._stickyRoom.tag} / ${this._stickyRoom.position}`); tag = this._stickyRoom.tag; position = this._stickyRoom.position; } // Now that we're done lying to the algorithm, we need to update our position // marker only if the user is moving further down the same list. If they're switching // lists, or moving upwards, the position marker will splice in just fine but if // they went downwards in the same list we'll be off by 1 due to the shifting rooms. if (lastStickyRoom && lastStickyRoom.tag === tag && lastStickyRoom.position <= position) { position++; } this._stickyRoom = { room: val, position: position, tag: tag }; // We update the filtered rooms just in case, as otherwise users will end up visiting // a room while filtering and it'll disappear. We don't update the filter earlier in // this function simply because we don't have to. this.recalculateStickyRoom(); this.recalculateActiveCallRooms(tag); if (lastStickyRoom && lastStickyRoom.tag !== tag) this.recalculateActiveCallRooms(lastStickyRoom.tag); // Finally, trigger an update if (this.updatesInhibited) return; this.emit(LIST_UPDATED_EVENT); } /** * Hack to prevent Typescript claiming this._stickyRoom is always null. */ stickyRoomMightBeModified() { return this._stickyRoom; } initCachedStickyRooms() { this._cachedStickyRooms = {}; for (const tagId of Object.keys(this.cachedRooms)) { this._cachedStickyRooms[tagId] = [...this.cachedRooms[tagId]]; // shallow clone } } /** * Recalculate the sticky room position. If this is being called in relation to * a specific tag being updated, it should be given to this function to optimize * the call. * @param updatedTag The tag that was updated, if possible. */ recalculateStickyRoom(updatedTag = null) { // 🐉 Here be dragons. // This function does far too much for what it should, and is called by many places. // Not only is this responsible for ensuring the sticky room is held in place at all // times, it is also responsible for ensuring our clone of the cachedRooms is up to // date. If either of these desyncs, we see weird behaviour like duplicated rooms, // outdated lists, and other nonsensical issues that aren't necessarily obvious. if (!this._stickyRoom) { // If there's no sticky room, just do nothing useful. if (!!this._cachedStickyRooms) { // Clear the cache if we won't be needing it this._cachedStickyRooms = null; if (this.updatesInhibited) return; this.emit(LIST_UPDATED_EVENT); } return; } if (!this._cachedStickyRooms || !updatedTag) { this.initCachedStickyRooms(); } if (updatedTag) { // Update the tag indicated by the caller, if possible. This is mostly to ensure // our cache is up to date. if (this._cachedStickyRooms) { this._cachedStickyRooms[updatedTag] = [...this.cachedRooms[updatedTag]]; // shallow clone } } // Now try to insert the sticky room, if we need to. // We need to if there's no updated tag (we regenned the whole cache) or if the tag // we might have updated from the cache is also our sticky room. const sticky = this._stickyRoom; if (sticky && (!updatedTag || updatedTag === sticky.tag) && this._cachedStickyRooms) { this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room); } // Finally, trigger an update if (this.updatesInhibited) return; this.emit(LIST_UPDATED_EVENT); } /** * Recalculate the position of any rooms with calls. If this is being called in * relation to a specific tag being updated, it should be given to this function to * optimize the call. * * This expects to be called *after* the sticky rooms are updated, and sticks the * room with the currently active call to the top of its tag. * * @param updatedTag The tag that was updated, if possible. */ recalculateActiveCallRooms(updatedTag = null) { if (!updatedTag) { // Assume all tags need updating // We're not modifying the map here, so can safely rely on the cached values // rather than the explicitly sticky map. for (const tagId of Object.keys(this.cachedRooms)) { if (!tagId) { throw new Error("Unexpected recursion: falsy tag"); } this.recalculateActiveCallRooms(tagId); } return; } if (_CallStore.CallStore.instance.connectedCalls.size) { // We operate on the sticky rooms map if (!this._cachedStickyRooms) this.initCachedStickyRooms(); const rooms = this._cachedStickyRooms[updatedTag]; const activeRoomIds = new Set([..._CallStore.CallStore.instance.connectedCalls].map(call => call.roomId)); const activeRooms = []; const inactiveRooms = []; for (const room of rooms) { (activeRoomIds.has(room.roomId) ? activeRooms : inactiveRooms).push(room); } // Stick rooms with active calls to the top this._cachedStickyRooms[updatedTag] = [...activeRooms, ...inactiveRooms]; } } /** * Asks the Algorithm to regenerate all lists, using the tags given * as reference for which lists to generate and which way to generate * them. * @param {ITagSortingMap} tagSortingMap The tags to generate. * @param {IListOrderingMap} listOrderingMap The ordering of those tags. */ populateTags(tagSortingMap, listOrderingMap) { if (!tagSortingMap) throw new Error(`Sorting map cannot be null or empty`); if (!listOrderingMap) throw new Error(`Ordering ma cannot be null or empty`); if ((0, _arrays.arrayHasDiff)(Object.keys(tagSortingMap), Object.keys(listOrderingMap))) { throw new Error(`Both maps must contain the exact same tags`); } this.sortAlgorithms = tagSortingMap; this.listAlgorithms = listOrderingMap; this.algorithms = {}; for (const tag of Object.keys(tagSortingMap)) { this.algorithms[tag] = (0, _listOrdering.getListAlgorithmInstance)(this.listAlgorithms[tag], tag, this.sortAlgorithms[tag]); } return this.setKnownRooms(this.rooms); } /** * Gets an ordered set of rooms for the all known tags. * @returns {ITagMap} The cached list of rooms, ordered, * for each tag. May be empty, but never null/undefined. */ getOrderedRooms() { return this._cachedStickyRooms || this.cachedRooms; } /** * This returns the same as getOrderedRooms(), but without the sticky room * map as it causes issues for sticky room handling (see sticky room handling * for more information). * @returns {ITagMap} The cached list of rooms, ordered, * for each tag. May be empty, but never null/undefined. */ getOrderedRoomsWithoutSticky() { return this.cachedRooms; } /** * Seeds the Algorithm with a set of rooms. The algorithm will discard all * previously known information and instead use these rooms instead. * @param {Room[]} rooms The rooms to force the algorithm to use. */ setKnownRooms(rooms) { if ((0, _utils.isNullOrUndefined)(rooms)) throw new Error(`Array of rooms cannot be null`); if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`); if (!this.updatesInhibited) { // We only log this if we're expecting to be publishing updates, which means that // this could be an unexpected invocation. If we're inhibited, then this is probably // an intentional invocation. _logger.logger.warn("Resetting known rooms, initiating regeneration"); } // Before we go any further we need to clear (but remember) the sticky room to // avoid accidentally duplicating it in the list. const oldStickyRoom = this._stickyRoom; if (oldStickyRoom) this.updateStickyRoom(null); this.rooms = rooms; const newTags = {}; for (const tagId in this.sortAlgorithms) { // noinspection JSUnfilteredForInLoop newTags[tagId] = []; } // If we can avoid doing work, do so. if (!rooms.length) { this.generateFreshTags(newTags); // just in case it wants to do something this.cachedRooms = newTags; return; } // Split out the easy rooms first (leave and invite) const memberships = (0, _membership.splitRoomsByMembership)(rooms); for (const room of memberships[_membership.EffectiveMembership.Invite]) { newTags[_models.DefaultTagID.Invite].push(room); } for (const room of memberships[_membership.EffectiveMembership.Leave]) { // We may not have had an archived section previously, so make sure its there. if (newTags[_models.DefaultTagID.Archived] === undefined) newTags[_models.DefaultTagID.Archived] = []; newTags[_models.DefaultTagID.Archived].push(room); } // Now process all the joined rooms. This is a bit more complicated for (const room of memberships[_membership.EffectiveMembership.Join]) { const tags = this.getTagsOfJoinedRoom(room); let inTag = false; if (tags.length > 0) { for (const tag of tags) { if (!(0, _utils.isNullOrUndefined)(newTags[tag])) { newTags[tag].push(room); inTag = true; } } } if (!inTag) { if (_DMRoomMap.default.shared().getUserIdForRoomId(room.roomId)) { newTags[_models.DefaultTagID.DM].push(room); } else { newTags[_models.DefaultTagID.Untagged].push(room); } } } this.generateFreshTags(newTags); this.cachedRooms = newTags; // this recalculates the filtered rooms for us this.updateTagsFromCache(); // Now that we've finished generation, we need to update the sticky room to what // it was. It's entirely possible that it changed lists though, so if it did then // we also have to update the position of it. if (oldStickyRoom && oldStickyRoom.room) { this.updateStickyRoom(oldStickyRoom.room); if (this._stickyRoom && this._stickyRoom.room) { // just in case the update doesn't go according to plan if (this._stickyRoom.tag !== oldStickyRoom.tag) { // We put the sticky room at the top of the list to treat it as an obvious tag change. this._stickyRoom.position = 0; this.recalculateStickyRoom(this._stickyRoom.tag); } } } } getTagsForRoom(room) { const tags = []; if (!(0, _membership.getEffectiveMembership)(room.getMyMembership())) return []; // peeked room has no tags const membership = (0, _membership.getEffectiveMembershipTag)(room); if (membership === _membership.EffectiveMembership.Invite) { tags.push(_models.DefaultTagID.Invite); } else if (membership === _membership.EffectiveMembership.Leave) { tags.push(_models.DefaultTagID.Archived); } else { tags.push(...this.getTagsOfJoinedRoom(room)); } if (!tags.length) tags.push(_models.DefaultTagID.Untagged); return tags; } getTagsOfJoinedRoom(room) { let tags = Object.keys(room.tags || {}); if (tags.length === 0) { // Check to see if it's a DM if it isn't anything else if (_DMRoomMap.default.shared().getUserIdForRoomId(room.roomId)) { tags = [_models.DefaultTagID.DM]; } } if (room.isCallRoom() && (room.getJoinRule() === _matrix.JoinRule.Public || room.getJoinRule() === _matrix.JoinRule.Knock)) { tags.push(_models.DefaultTagID.Conference); } return tags; } /** * Updates the roomsToTags map */ updateTagsFromCache() { const newMap = {}; const tags = Object.keys(this.cachedRooms); for (const tagId of tags) { const rooms = this.cachedRooms[tagId]; for (const room of rooms) { if (!newMap[room.roomId]) newMap[room.roomId] = []; newMap[room.roomId].push(tagId); } } this.roomIdsToTags = newMap; } /** * Called when the Algorithm believes a complete regeneration of the existing * lists is needed. * @param {ITagMap} updatedTagMap The tag map which needs populating. Each tag * will already have the rooms which belong to it - they just need ordering. Must * be mutated in place. */ generateFreshTags(updatedTagMap) { if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); for (const tag of Object.keys(updatedTagMap)) { const algorithm = this.algorithms[tag]; if (!algorithm) throw new Error(`No algorithm for ${tag}`); algorithm.setRooms(updatedTagMap[tag]); updatedTagMap[tag] = algorithm.orderedRooms; } } /** * Asks the Algorithm to update its knowledge of a room. For example, when * a user tags a room, joins/creates a room, or leaves a room the Algorithm * should be told that the room's info might have changed. The Algorithm * may no-op this request if no changes are required. * @param {Room} room The room which might have affected sorting. * @param {RoomUpdateCause} cause The reason for the update being triggered. * @returns {Promise<boolean>} A boolean of whether or not getOrderedRooms() * should be called after processing. */ handleRoomUpdate(room, cause) { if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); // Note: check the isSticky against the room ID just in case the reference is wrong const isSticky = this._stickyRoom?.room?.roomId === room.roomId; if (cause === _models.RoomUpdateCause.NewRoom) { const isForLastSticky = this._lastStickyRoom?.room === room; const roomTags = this.roomIdsToTags[room.roomId]; const hasTags = roomTags && roomTags.length > 0; // Don't change the cause if the last sticky room is being re-added. If we fail to // pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus // lose the room. if (hasTags && !isForLastSticky) { _logger.logger.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); cause = _models.RoomUpdateCause.PossibleTagChange; } // Check to see if the room is known first let knownRoomRef = this.rooms.includes(room); if (hasTags && !knownRoomRef) { _logger.logger.warn(`${room.roomId} might be a reference change - attempting to update reference`); this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r); knownRoomRef = this.rooms.includes(room); if (!knownRoomRef) { _logger.logger.warn(`${room.roomId} is still not referenced. It may be sticky.`); } } // If we have tags for a room and don't have the room referenced, something went horribly // wrong - the reference should have been updated above. if (hasTags && !knownRoomRef && !isSticky) { throw new Error(`${room.roomId} is missing from room array but is known - trying to find duplicate`); } // Like above, update the reference to the sticky room if we need to if (hasTags && isSticky && this._stickyRoom) { // Go directly in and set the sticky room's new reference, being careful not // to trigger a sticky room update ourselves. this._stickyRoom.room = room; } // If after all that we're still a NewRoom update, add the room if applicable. // We don't do this for the sticky room (because it causes duplication issues) // or if we know about the reference (as it should be replaced). if (cause === _models.RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) { this.rooms.push(room); } } let didTagChange = false; if (cause === _models.RoomUpdateCause.PossibleTagChange) { const oldTags = this.roomIdsToTags[room.roomId] || []; const newTags = this.getTagsForRoom(room); const diff = (0, _arrays.arrayDiff)(oldTags, newTags); if (diff.removed.length > 0 || diff.added.length > 0) { for (const rmTag of diff.removed) { const algorithm = this.algorithms[rmTag]; if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); algorithm.handleRoomUpdate(room, _models.RoomUpdateCause.RoomRemoved); this._cachedRooms[rmTag] = algorithm.orderedRooms; this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed this.recalculateActiveCallRooms(rmTag); } for (const addTag of diff.added) { const algorithm = this.algorithms[addTag]; if (!algorithm) throw new Error(`No algorithm for ${addTag}`); algorithm.handleRoomUpdate(room, _models.RoomUpdateCause.NewRoom); this._cachedRooms[addTag] = algorithm.orderedRooms; } // Update the tag map so we don't regen it in a moment this.roomIdsToTags[room.roomId] = newTags; cause = _models.RoomUpdateCause.Timeline; didTagChange = true; } else { // This is a tag change update and no tags were changed, nothing to do! return false; } if (didTagChange && isSticky) { // Manually update the tag for the sticky room without triggering a sticky room // update. The update will be handled implicitly by the sticky room handling and // requires no changes on our part, if we're in the middle of a sticky room change. if (this._lastStickyRoom) { this._stickyRoom = { room, tag: this.roomIdsToTags[room.roomId][0], position: 0 // right at the top as it changed tags }; } else { // We have to clear the lock as the sticky room change will trigger updates. this.setStickyRoom(room); } } } // If the update is for a room change which might be the sticky room, prevent it. We // need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though // as the sticky room relies on this. if (cause !== _models.RoomUpdateCause.NewRoom && cause !== _models.RoomUpdateCause.RoomRemoved) { if (this.stickyRoom === room) { return false; } } if (!this.roomIdsToTags[room.roomId]) { if (CAUSES_REQUIRING_ROOM.includes(cause)) { return false; } // Get the tags for the room and populate the cache const roomTags = this.getTagsForRoom(room).filter(t => !(0, _utils.isNullOrUndefined)(this.cachedRooms[t])); // "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(), // which means we should *always* have a tag to go off of. if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`); this.roomIdsToTags[room.roomId] = roomTags; } const tags = this.roomIdsToTags[room.roomId]; if (!tags) { _logger.logger.warn(`No tags known for "${room.name}" (${room.roomId})`); return false; } let changed = didTagChange; for (const tag of tags) { const algorithm = this.algorithms[tag]; if (!algorithm) throw new Error(`No algorithm for ${tag}`); algorithm.handleRoomUpdate(room, cause); this._cachedRooms[tag] = algorithm.orderedRooms; // Flag that we've done something this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed this.recalculateActiveCallRooms(tag); changed = true; } return changed; } } exports.Algorithm = Algorithm; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4IiwicmVxdWlyZSIsIl90eXBlcyIsIl91dGlscyIsIl9ldmVudHMiLCJfbG9nZ2VyIiwiX0RNUm9vbU1hcCIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfYXJyYXlzIiwiX21vZGVscyIsIl9tZW1iZXJzaGlwIiwiX2xpc3RPcmRlcmluZyIsIl9WaXNpYmlsaXR5UHJvdmlkZXIiLCJfQ2FsbFN0b3JlIiwiTElTVF9VUERBVEVEX0VWRU5UIiwiZXhwb3J0cyIsIkNBVVNFU19SRVFVSVJJTkdfUk9PTSIsIlJvb21VcGRhdGVDYXVzZSIsIlRpbWVsaW5lIiwiUmVhZFJlY2VpcHQiLCJBbGdvcml0aG0iLCJFdmVudEVtaXR0ZXIiLCJjb25zdHJ1Y3RvciIsImFyZ3MiLCJfZGVmaW5lUHJvcGVydHkyIiwiZGVmYXVsdCIsInJlY2FsY3VsYXRlU3RpY2t5Um9vbSIsInJlY2FsY3VsYXRlQWN0aXZlQ2FsbFJvb21zIiwidXBkYXRlc0luaGliaXRlZCIsImVtaXQiLCJzdGFydCIsIkNhbGxTdG9yZSIsImluc3RhbmNlIiwib24iLCJDYWxsU3RvcmVFdmVudCIsIkNvbm5lY3RlZENhbGxzIiwib25Db25uZWN0ZWRDYWxscyIsInN0b3AiLCJvZmYiLCJzdGlja3lSb29tIiwiX3N0aWNreVJvb20iLCJyb29tIiwiaGFzVGFnU29ydGluZ01hcCIsInNvcnRBbGdvcml0aG1zIiwiY2FjaGVkUm9vbXMiLCJ2YWwiLCJfY2FjaGVkUm9vbXMiLCJzZXRTdGlja3lSb29tIiwidXBkYXRlU3RpY2t5Um9vbSIsImUiLCJsb2dnZXIiLCJ3YXJuIiwiZ2V0VGFnU29ydGluZyIsInRhZ0lkIiwic2V0VGFnU29ydGluZyIsInNvcnQiLCJFcnJvciIsImFsZ29yaXRobXMiLCJhbGdvcml0aG0iLCJzZXRTb3J0QWxnb3JpdGhtIiwib3JkZXJlZFJvb21zIiwiZ2V0TGlzdE9yZGVyaW5nIiwibGlzdEFsZ29yaXRobXMiLCJzZXRMaXN0T3JkZXJpbmciLCJvcmRlciIsImdldExpc3RBbGdvcml0aG1JbnN0YW5jZSIsInNldFJvb21zIiwiZG9VcGRhdGVTdGlja3lSb29tIiwiX2xhc3RTdGlja3lSb29tIiwiaXNTcGFjZVJvb20iLCJnZXRNeU1lbWJlcnNoaXAiLCJLbm93bk1lbWJlcnNoaXAiLCJJbnZpdGUiLCJWaXNpYmlsaXR5UHJvdmlkZXIiLCJpc1Jvb21WaXNpYmxlIiwiaGFuZGxlUm9vbVVwZGF0ZSIsIk5ld1Jvb20iLCJ0YWciLCJyb29tSWRzVG9UYWdzIiwicm9vbUlkIiwidGFnTGlzdCIsImdldE9yZGVyZWRSb29tc1dpdGhvdXRTdGlja3kiLCJwb3NpdGlvbiIsImluZGV4T2YiLCJ3YXNTdGlja3kiLCJsYXN0U3RpY2t5Um9vbSIsIlJvb21SZW1vdmVkIiwic3RpY2t5Um9vbU1pZ2h0QmVNb2RpZmllZCIsImluaXRDYWNoZWRTdGlja3lSb29tcyIsIl9jYWNoZWRTdGlja3lSb29tcyIsIk9iamVjdCIsImtleXMiLCJ1cGRhdGVkVGFnIiwic3RpY2t5Iiwic3BsaWNlIiwiY29ubmVjdGVkQ2FsbHMiLCJzaXplIiwicm9vbXMiLCJhY3RpdmVSb29tSWRzIiwiU2V0IiwibWFwIiwiY2FsbCIsImFjdGl2ZVJvb21zIiwiaW5hY3RpdmVSb29tcyIsImhhcyIsInB1c2giLCJwb3B1bGF0ZVRhZ3MiLCJ0YWdTb3J0aW5nTWFwIiwibGlzdE9yZGVyaW5nTWFwIiwiYXJyYXlIYXNEaWZmIiwic2V0S25vd25Sb29tcyIsImdldE9yZGVyZWRSb29tcyIsImlzTnVsbE9yVW5kZWZpbmVkIiwib2xkU3RpY2t5Um9vbSIsIm5ld1RhZ3MiLCJsZW5ndGgiLCJnZW5lcmF0ZUZyZXNoVGFncyIsIm1lbWJlcnNoaXBzIiwic3BsaXRSb29tc0J5TWVtYmVyc2hpcCIsIkVmZmVjdGl2ZU1lbWJlcnNoaXAiLCJEZWZhdWx0VGFnSUQiLCJMZWF2ZSIsIkFyY2hpdmVkIiwidW5kZWZpbmVkIiwiSm9pbiIsInRhZ3MiLCJnZXRUYWdzT2ZKb2luZWRSb29tIiwiaW5UYWciLCJETVJvb21NYXAiLCJzaGFyZWQiLCJnZXRVc2VySWRGb3JSb29tSWQiLCJETSIsIlVudGFnZ2VkIiwidXBkYXRlVGFnc0Zyb21DYWNoZSIsImdldFRhZ3NGb3JSb29tIiwiZ2V0RWZmZWN0aXZlTWVtYmVyc2hpcCIsIm1lbWJlcnNoaXAiLCJnZXRFZmZlY3RpdmVNZW1iZXJzaGlwVGFnIiwiaXNDYWxsUm9vbSIsImdldEpvaW5SdWxlIiwiSm9pblJ1bGUiLCJQdWJsaWMiLCJLbm9jayIsIkNvbmZlcmVuY2UiLCJuZXdNYXAiLCJ1cGRhdGVkVGFnTWFwIiwiY2F1c2UiLCJpc1N0aWNreSIsImlzRm9yTGFzdFN0aWNreSIsInJvb21UYWdzIiwiaGFzVGFncyIsIlBvc3NpYmxlVGFnQ2hhbmdlIiwia25vd25Sb29tUmVmIiwiaW5jbHVkZXMiLCJyIiwiZGlkVGFnQ2hhbmdlIiwib2xkVGFncyIsImRpZmYiLCJhcnJheURpZmYiLCJyZW1vdmVkIiwiYWRkZWQiLCJybVRhZyIsImFkZFRhZyIsImZpbHRlciIsInQiLCJuYW1lIiwiY2hhbmdlZCJdLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zdG9yZXMvcm9vbS1saXN0L2FsZ29yaXRobXMvQWxnb3JpdGhtLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qXG5Db3B5cmlnaHQgMjAyNCBOZXcgVmVjdG9yIEx0ZC5cbkNvcHlyaWdodCAyMDIwLCAyMDIxIFRoZSBNYXRyaXgub3JnIEZvdW5kYXRpb24gQy5JLkMuXG5cblNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBR1BMLTMuMC1vbmx5IE9SIEdQTC0zLjAtb25seVxuUGxlYXNlIHNlZSBMSUNFTlNFIGZpbGVzIGluIHRoZSByZXBvc2l0b3J5IHJvb3QgZm9yIGZ1bGwgZGV0YWlscy5cbiovXG5cbmltcG9ydCB7IEpvaW5SdWxlLCBSb29tIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL21hdHJpeFwiO1xuaW1wb3J0IHsgS25vd25NZW1iZXJzaGlwIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3R5cGVzXCI7XG5pbXBvcnQgeyBpc051bGxPclVuZGVmaW5lZCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy91dGlsc1wiO1xuaW1wb3J0IHsgRXZlbnRFbWl0dGVyIH0gZnJvbSBcImV2ZW50c1wiO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL2xvZ2dlclwiO1xuXG5pbXBvcnQgRE1Sb29tTWFwIGZyb20gXCIuLi8uLi8uLi91dGlscy9ETVJvb21NYXBcIjtcbmltcG9ydCB7IGFycmF5RGlmZiwgYXJyYXlIYXNEaWZmIH0gZnJvbSBcIi4uLy4uLy4uL3V0aWxzL2FycmF5c1wiO1xuaW1wb3J0IHsgRGVmYXVsdFRhZ0lELCBSb29tVXBkYXRlQ2F1c2UsIFRhZ0lEIH0gZnJvbSBcIi4uL21vZGVsc1wiO1xuaW1wb3J0IHtcbiAgICBJTGlzdE9yZGVyaW5nTWFwLFxuICAgIElPcmRlcmluZ0FsZ29yaXRobU1hcCxcbiAgICBJVGFnTWFwLFxuICAgIElUYWdTb3J0aW5nTWFwLFxuICAgIExpc3RBbGdvcml0aG0sXG4gICAgU29ydEFsZ29yaXRobSxcbn0gZnJvbSBcIi4vbW9kZWxzXCI7XG5pbXBvcnQge1xuICAgIEVmZmVjdGl2ZU1lbWJlcnNoaXAsXG4gICAgZ2V0RWZmZWN0aXZlTWVtYmVyc2hpcCxcbiAgICBnZXRFZmZlY3RpdmVNZW1iZXJzaGlwVGFnLFxuICAgIHNwbGl0Um9vbXNCeU1lbWJlcnNoaXAsXG59IGZyb20gXCIuLi8uLi8uLi91dGlscy9tZW1iZXJzaGlwXCI7XG5pbXBvcnQgeyBPcmRlcmluZ0FsZ29yaXRobSB9IGZyb20gXCIuL2xpc3Qtb3JkZXJpbmcvT3JkZXJpbmdBbGdvcml0aG1cIjtcbmltcG9ydCB7IGdldExpc3RBbGdvcml0aG1JbnN0YW5jZSB9IGZyb20gXCIuL2xpc3Qtb3JkZXJpbmdcIjtcbmltcG9ydCB7IFZpc2liaWxpdHlQcm92aWRlciB9IGZyb20gXCIuLi9maWx0ZXJzL1Zpc2liaWxpdHlQcm92aWRlclwiO1xuaW1wb3J0IHsgQ2FsbFN0b3JlLCBDYWxsU3RvcmVFdmVudCB9IGZyb20gXCIuLi8uLi9DYWxsU3RvcmVcIjtcblxuLyoqXG4gKiBGaXJlZCB3aGVuIHRoZSBBbGdvcml0aG0gaGFzIGRldGVybWluZWQgYSBsaXN0IGhhcyBiZWVuIHVwZGF0ZWQuXG4gKi9cbmV4cG9ydCBjb25zdCBMSVNUX1VQREFURURfRVZFTlQgPSBcImxpc3RfdXBkYXRlZF9ldmVudFwiO1xuXG4vLyBUaGVzZSBhcmUgdGhlIGNhdXNlcyB3aGljaCByZXF1aXJlIGEgcm9vbSB0byBiZSBrbm93biBpbiBvcmRlciBmb3IgdXMgdG8gaGFuZGxlIHRoZW0uIElmXG4vLyBhIGNhdXNlIGluIHRoaXMgbGlzdCBpcyByYWlzZWQgYW5kIHdlIGRvbid0IGtub3cgYWJvdXQgdGhlIHJvb20sIHdlIGRvbid0IGhhbmRsZSB0aGUgdXBkYXRlLlxuLy9cbi8vIE5vdGU6IHRoZXNlIHR5cGljYWxseSBoYXBwZW4gd2hlbiBhIG5ldyByb29tIGlzIGNvbWluZyBpbiwgc3VjaCBhcyB0aGUgdXNlciBjcmVhdGluZyBvclxuLy8gam9pbmluZyB0aGUgcm9vbS4gRm9yIHRoZXNlIGNhc2VzLCB3ZSBuZWVkIHRvIGtub3cgYWJvdXQgdGhlIHJvb20gcHJpb3IgdG8gaGFuZGxpbmcgaXQgb3RoZXJ3aXNlXG4vLyB3ZSdsbCBtYWtlIGJhZCBhc3N1bXB0aW9ucy5cbmNvbnN0IENBVVNFU19SRVFVSVJJTkdfUk9PTSA9IFtSb29tVXBkYXRlQ2F1c2UuVGltZWxpbmUsIFJvb21VcGRhdGVDYXVzZS5SZWFkUmVjZWlwdF07XG5cbmludGVyZmFjZSBJU3RpY2t5Um9vbSB7XG4gICAgcm9vbTogUm9vbTtcbiAgICBwb3NpdGlvbjogbnVtYmVyO1xuICAgIHRhZzogVGFnSUQ7XG59XG5cbi8qKlxuICogUmVwcmVzZW50cyBhIGxpc3Qgb3JkZXJpbmcgYWxnb3JpdGhtLiBUaGlzIGNsYXNzIHdpbGwgdGFrZSBjYXJlIG9mIHRhZ1xuICogbWFuYWdlbWVudCAod2hpY2ggcm9vbXMgZ28gaW4gd2hpY2ggdGFncykgYW5kIGFzayB0aGUgaW1wbGVtZW50YXRpb24gdG9cbiAqIGRlYWwgd2l0aCBvcmRlcmluZyBtZWNoYW5pY3MuXG4gKi9cbmV4cG9ydCBjbGFzcyBBbGdvcml0aG0gZXh0ZW5kcyBFdmVudEVtaXR0ZXIge1xuICAgIHByaXZhdGUgX2NhY2hlZFJvb21zOiBJVGFnTWFwID0ge307XG4gICAgcHJpdmF0ZSBfY2FjaGVkU3RpY2t5Um9vbXM6IElUYWdNYXAgfCBudWxsID0ge307IC8vIGEgY2xvbmUgb2YgdGhlIF9jYWNoZWRSb29tcywgd2l0aCB0aGUgc3RpY2t5IHJvb21cbiAgICBwcml2YXRlIF9zdGlja3lSb29tOiBJU3RpY2t5Um9vbSB8IG51bGwgPSBudWxsO1xuICAgIHByaXZhdGUgX2xhc3RTdGlja3lSb29tOiBJU3RpY2t5Um9vbSB8IG51bGwgPSBudWxsOyAvLyBvbmx5IG5vdC1udWxsIHdoZW4gY2hhbmdpbmcgdGhlIHN0aWNreSByb29tXG4gICAgcHJpdmF0ZSBzb3J0QWxnb3JpdGhtczogSVRhZ1NvcnRpbmdNYXAgfCBudWxsID0gbnVsbDtcbiAgICBwcml2YXRlIGxpc3RBbGdvcml0aG1zOiBJTGlzdE9yZGVyaW5nTWFwIHwgbnVsbCA9IG51bGw7XG4gICAgcHJpdmF0ZSBhbGdvcml0aG1zOiBJT3JkZXJpbmdBbGdvcml0aG1NYXAgfCBudWxsID0gbnVsbDtcbiAgICBwcml2YXRlIHJvb21zOiBSb29tW10gPSBbXTtcbiAgICBwcml2YXRlIHJvb21JZHNUb1RhZ3M6IHtcbiAgICAgICAgW3Jvb21JZDogc3RyaW5nXTogVGFnSURbXTtcbiAgICB9ID0ge307XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdG8gdHJ1ZSB0byBzdXNwZW5kIGVtaXNzaW9ucyBvZiBhbGdvcml0aG0gdXBkYXRlcy5cbiAgICAgKi9cbiAgICBwdWJsaWMgdXBkYXRlc0luaGliaXRlZCA9IGZhbHNlO1xuXG4gICAgcHVibGljIHN0YXJ0KCk6IHZvaWQge1xuICAgICAgICBDYWxsU3RvcmUuaW5zdGFuY2Uub24oQ2FsbFN0b3JlRXZlbnQuQ29ubmVjdGVkQ2FsbHMsIHRoaXMub25Db25uZWN0ZWRDYWxscyk7XG4gICAgfVxuXG4gICAgcHVibGljIHN0b3AoKTogdm9pZCB7XG4gICAgICAgIENhbGxTdG9yZS5pbnN0YW5jZS5vZmYoQ2FsbFN0b3JlRXZlbnQuQ29ubmVjdGVkQ2FsbHMsIHRoaXMub25Db25uZWN0ZWRDYWxscyk7XG4gICAgfVxuXG4gICAgcHVibGljIGdldCBzdGlja3lSb29tKCk6IFJvb20gfCBudWxsIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3N0aWNreVJvb20gPyB0aGlzLl9zdGlja3lSb29tLnJvb20gOiBudWxsO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgaGFzVGFnU29ydGluZ01hcCgpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuICEhdGhpcy5zb3J0QWxnb3JpdGhtcztcbiAgICB9XG5cbiAgICBwcm90ZWN0ZWQgc2V0IGNhY2hlZFJvb21zKHZhbDogSVRhZ01hcCkge1xuICAgICAgICB0aGlzLl9jYWNoZWRSb29tcyA9IHZhbDtcbiAgICAgICAgdGhpcy5yZWNhbGN1bGF0ZVN0aWNreVJvb20oKTtcbiAgICAgICAgdGhpcy5yZWNhbGN1bGF0ZUFjdGl2ZUNhbGxSb29tcygpO1xuICAgIH1cblxuICAgIHByb3RlY3RlZCBnZXQgY2FjaGVkUm9vbXMoKTogSVRhZ01hcCB7XG4gICAgICAgIC8vIPCfkIkgSGVyZSBiZSBkcmFnb25zLlxuICAgICAgICAvLyBOb3RlOiB0aGlzIGlzIHVzZWQgYnkgdGhlIHVuZGVybHlpbmcgYWxnb3JpdGhtIGNsYXNzZXMsIHNvIGRvbid0IG1ha2UgaXQgcmV0dXJuXG4gICAgICAgIC8vIHRoZSBzdGlja3kgcm9vbSBjYWNoZS4gSWYgaXQgZW5kcyB1cCByZXR1cm5pbmcgdGhlIHN0aWNreSByb29tIGNhY2hlLCB3ZSBlbmQgdXBcbiAgICAgICAgLy8gY29ycnVwdGluZyBvdXIgY2FjaGVzIGFuZCBjb25mdXNpbmcgdGhlbS5cbiAgICAgICAgcmV0dXJuIHRoaXMuX2NhY2hlZFJvb21zO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEF3YWl0YWJsZSB2ZXJzaW9uIG9mIHRoZSBzdGlja3kgcm9vbSBzZXR0ZXIuXG4gICAgICogQHBhcmFtIHZhbCBUaGUgbmV3IHJvb20gdG8gc3RpY2t5LlxuICAgICAqL1xuICAgIHB1YmxpYyBzZXRTdGlja3lSb29tKHZhbDogUm9vbSB8IG51bGwpOiB2b2lkIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHRoaXMudXBkYXRlU3RpY2t5Um9vbSh2YWwpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihcIkZhaWxlZCB0byB1cGRhdGUgc3RpY2t5IHJvb21cIiwgZSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0VGFnU29ydGluZyh0YWdJZDogVGFnSUQpOiBTb3J0QWxnb3JpdGhtIHwgbnVsbCB7XG4gICAgICAgIGlmICghdGhpcy5zb3J0QWxnb3JpdGhtcykgcmV0dXJuIG51bGw7XG4gICAgICAgIHJldHVybiB0aGlzLnNvcnRBbGdvcml0aG1zW3RhZ0lkXTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc2V0VGFnU29ydGluZyh0YWdJZDogVGFnSUQsIHNvcnQ6IFNvcnRBbGdvcml0aG0pOiB2b2lkIHtcbiAgICAgICAgaWYgKCF0YWdJZCkgdGhyb3cgbmV3IEVycm9yKFwiVGFnIElEIG11c3QgYmUgZGVmaW5lZFwiKTtcbiAgICAgICAgaWYgKCFzb3J0KSB0aHJvdyBuZXcgRXJyb3IoXCJBbGdvcml0aG0gbXVzdCBiZSBkZWZpbmVkXCIpO1xuICAgICAgICBpZiAoIXRoaXMuc29ydEFsZ29yaXRobXMpIHRocm93IG5ldyBFcnJvcihcInRoaXMuc29ydEFsZ29yaXRobXMgbXVzdCBiZSBkZWZpbmVkIGJlZm9yZSBjYWxsaW5nIHNldFRhZ1NvcnRpbmdcIik7XG4gICAgICAgIGlmICghdGhpcy5hbGdvcml0aG1zKSB0aHJvdyBuZXcgRXJyb3IoXCJ0aGlzLmFsZ29yaXRobXMgbXVzdCBiZSBkZWZpbmVkIGJlZm9yZSBjYWxsaW5nIHNldFRhZ1NvcnRpbmdcIik7XG4gICAgICAgIHRoaXMuc29ydEFsZ29yaXRobXNbdGFnSWRdID0gc29ydDtcblxuICAgICAgICBjb25zdCBhbGdvcml0aG06IE9yZGVyaW5nQWxnb3JpdGhtID0gdGhpcy5hbGdvcml0aG1zW3RhZ0lkXTtcbiAgICAgICAgYWxnb3JpdGhtLnNldFNvcnRBbGdvcml0aG0oc29ydCk7XG4gICAgICAgIHRoaXMuX2NhY2hlZFJvb21zW3RhZ0lkXSA9IGFsZ29yaXRobS5vcmRlcmVkUm9vbXM7XG4gICAgICAgIHRoaXMucmVjYWxjdWxhdGVTdGlja3lSb29tKHRhZ0lkKTsgLy8gdXBkYXRlIHN0aWNreSByb29tIHRvIG1ha2Ugc3VyZSBpdCBhcHBlYXJzIGlmIG5lZWRlZFxuICAgICAgICB0aGlzLnJlY2FsY3VsYXRlQWN0aXZlQ2FsbFJvb21zKHRhZ0lkKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0TGlzdE9yZGVyaW5nKHRhZ0lkOiBUYWdJRCk6IExpc3RBbGdvcml0aG0gfCBudWxsIHtcbiAgICAgICAgaWYgKCF0aGlzLmxpc3RBbGdvcml0aG1zKSByZXR1cm4gbnVsbDtcbiAgICAgICAgcmV0dXJuIHRoaXMubGlzdEFsZ29yaXRobXNbdGFnSWRdO1xuICAgIH1cblxuICAgIHB1YmxpYyBzZXRMaXN0T3JkZXJpbmcodGFnSWQ6IFRhZ0lELCBvcmRlcjogTGlzdEFsZ29yaXRobSk6IHZvaWQge1xuICAgICAgICBpZiAoIXRhZ0lkKSB0aHJvdyBuZXcgRXJyb3IoXCJUYWcgSUQgbXVzdCBiZSBkZWZpbmVkXCIpO1xuICAgICAgICBpZiAoIW9yZGVyKSB0aHJvdyBuZXcgRXJyb3IoXCJBbGdvcml0aG0gbXVzdCBiZSBkZWZpbmVkXCIpO1xuICAgICAgICBpZiAoIXRoaXMuc29ydEFsZ29yaXRobXMpIHRocm93IG5ldyBFcnJvcihcInRoaXMuc29ydEFsZ29yaXRobXMgbXVzdCBiZSBkZWZpbmVkIGJlZm9yZSBjYWxsaW5nIHNldExpc3RPcmRlcmluZ1wiKTtcbiAgICAgICAgaWYgKCF0aGlzLmxpc3RBbGdvcml0aG1zKSB0aHJvdyBuZXcgRXJyb3IoXCJ0aGlzLmxpc3RBbGdvcml0aG1zIG11c3QgYmUgZGVmaW5lZCBiZWZvcmUgY2FsbGluZyBzZXRMaXN0T3JkZXJpbmdcIik7XG4gICAgICAgIGlmICghdGhpcy5hbGdvcml0aG1zKSB0aHJvdyBuZXcgRXJyb3IoXCJ0aGlzLmFsZ29yaXRobXMgbXVzdCBiZSBkZWZpbmVkIGJlZm9yZSBjYWxsaW5nIHNldExpc3RPcmRlcmluZ1wiKTtcbiAgICAgICAgdGhpcy5saXN0QWxnb3JpdGhtc1t0YWdJZF0gPSBvcmRlcjtcblxuICAgICAgICBjb25zdCBhbGdvcml0aG0gPSBnZXRMaXN0QWxnb3JpdGhtSW5zdGFuY2Uob3JkZXIsIHRhZ0lkLCB0aGlzLnNvcnRBbGdvcml0aG1zW3RhZ0lkXSk7XG4gICAgICAgIHRoaXMuYWxnb3JpdGhtc1t0YWdJZF0gPSBhbGdvcml0aG07XG5cbiAgICAgICAgYWxnb3JpdGhtLnNldFJvb21zKHRoaXMuX2NhY2hlZFJvb21zW3RhZ0lkXSk7XG4gICAgICAgIHRoaXMuX2NhY2hlZFJvb21zW3RhZ0lkXSA9IGFsZ29yaXRobS5vcmRlcmVkUm9vbXM7XG4gICAgICAgIHRoaXMucmVjYWxjdWxhdGVTdGlja3lSb29tKHRhZ0lkKTsgLy8gdXBkYXRlIHN0aWNreSByb29tIHRvIG1ha2Ugc3VyZSBpdCBhcHBlYXJzIGlmIG5lZWRlZFxuICAgICAgICB0aGlzLnJlY2FsY3VsYXRlQWN0aXZlQ2FsbFJvb21zKHRhZ0lkKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIHVwZGF0ZVN0aWNreVJvb20odmFsOiBSb29tIHwgbnVsbCk6IHZvaWQge1xuICAgICAgICB0aGlzLmRvVXBkYXRlU3RpY2t5Um9vbSh2YWwpO1xuICAgICAgICB0aGlzLl9sYXN0U3RpY2t5Um9vbSA9IG51bGw7IC8vIGNsZWFyIHRvIGluZGljYXRlIHdlJ3JlIGRvbmUgY2hhbmdpbmdcbiAgICB9XG5cbiAgICBwcml2YXRlIGRvVXBkYXRlU3RpY2t5Um9vbSh2YWw6IFJvb20gfCBudWxsKTogdm9pZCB7XG4gICAgICAgIGlmICh2YWw/LmlzU3BhY2VSb29tKCkgJiYgdmFsLmdldE15TWVtYmVyc2hpcCgpICE9PSBLbm93bk1lbWJlcnNoaXAuSW52aXRlKSB7XG4gICAgICAgICAgICAvLyBuby1vcCBzdGlja3kgcm9vbXMgZm9yIHNwYWNlcyAtIHRoZXkncmUgZWZmZWN0aXZlbHkgdmlydHVhbCByb29tc1xuICAgICAgICAgICAgdmFsID0gbnVsbDtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh2YWwgJiYgIVZpc2liaWxpdHlQcm92aWRlci5pbnN0YW5jZS5pc1Jvb21WaXNpYmxlKHZhbCkpIHtcbiAgICAgICAgICAgIHZhbCA9IG51bGw7IC8vIHRoZSByb29tIGlzbid0IHZpc2libGUgLSBsaWUgdG8gdGhlIHJlc3Qgb2YgdGhpcyBmdW5jdGlvblxuICAgICAgICB9XG5cbiAgICAgICAgLy8gU2V0IHRoZSBsYXN0IHN0aWNreSByb29tIHRvIGluZGljYXRlIHRoYXQgd2UncmUgaW4gYSBjaGFuZ2UuIFRoZSBjb2RlIHRocm91Z2hvdXQgdGhlXG4gICAgICAgIC8vIGNsYXNzIGNhbiBzYWZlbHkgaGFuZGxlIGEgbnVsbCByb29tLCBzbyB0aGlzIHNob3VsZCBiZSBzYWZlIHRvIGRvIGFzIGEgYmFja3VwLlxuICAgICAgICB0aGlzLl9sYXN0U3RpY2t5Um9vbSA9IHRoaXMuX3N0aWNreVJvb20gfHwgPElTdGlja3lSb29tPnt9O1xuXG4gICAgICAgIC8vIEl0J3MgcG9zc2libGUgdG8gaGF2ZSBubyBzZWxlY3RlZCByb29tLiBJbiB0aGF0IGNhc2UsIGNsZWFyIHRoZSBzdGlja3kgcm9vbVxuICAgICAgICBpZiAoIXZhbCkge1xuICAgICAgICAgICAgaWYgKHRoaXMuX3N0aWNreVJvb20pIHtcbiAgICAgICAgICAgICAgICBjb25zdCBzdGlja3lSb29tID0gdGhpcy5fc3RpY2t5Um9vbS5yb29tO1xuICAgICAgICAgICAgICAgIHRoaXMuX3N0aWNreVJvb20gPSBudWxsOyAvLyBjbGVhciBiZWZvcmUgd2UgZ28gdG8gdXBkYXRlIHRoZSBhbGdvcml0aG1cblxuICAgICAgICAgICAgICAgIC8vIExpZSB0byB0aGUgYWxnb3JpdGhtIGFuZCByZS1hZGQgdGhlIHJvb20gdG8gdGhlIGFsZ29yaXRobVxuICAgICAgICAgICAgICAgIHRoaXMuaGFuZGxlUm9vbVVwZGF0ZShzdGlja3lSb29tLCBSb29tVXBkYXRlQ2F1c2UuTmV3Um9vbSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gV2hlbiB3ZSBkbyBoYXZlIGEgcm9vbSB0aG91Z2gsIHdlIGV4cGVjdCB0byBiZSBhYmxlIHRvIGZpbmQgaXRcbiAgICAgICAgbGV0IHRhZyA9IHRoaXMucm9vbUlkc1RvVGFnc1t2YWwucm9vbUlkXT8uWzBdO1xuICAgICAgICBpZiAoIXRhZykgdGhyb3cgbmV3IEVycm9yKGAke3ZhbC5yb29tSWR9IGRvZXMgbm90IGJlbG9uZyB0byBhIHRhZyBhbmQgY2Fubm90IGJlIHN0aWNreWApO1xuXG4gICAgICAgIC8vIFdlIHNwZWNpZmljYWxseSBkbyBOT1QgdXNlIHRoZSBvcmRlcmVkIHJvb21zIHNldCBhcyBpdCBjb250YWlucyB0aGUgc3RpY2t5IHJvb20sIHdoaWNoXG4gICAgICAgIC8vIG1lYW5zIHdlJ2xsIGJlIG9mZiBieSAxIHdoZW4gdGhlIHVzZXIgaXMgc3dpdGNoaW5nIHJvb21zLiBUaGlzIGxlYWRzIHRvIHZpc3VhbCBqdW1waW5nXG4gICAgICAgIC8vIHdoZW4gdGhlIHVzZXIgaXMgbW92aW5nIHNvdXRoIGluIHRoZSBsaXN0IChub3Qgbm9ydGgsIGJlY2F1c2Ugb2YgbWF0aCkuXG4gICAgICAgIGNvbnN0IHRhZ0xpc3QgPSB0aGlzLmdldE9yZGVyZWRSb29tc1dpdGhvdXRTdGlja3koKVt0YWddIHx8IFtdOyAvLyBjYW4gYmUgbnVsbCBpZiBmaWx0ZXJpbmdcbiAgICAgICAgbGV0IHBvc2l0aW9uID0gdGFnTGlzdC5pbmRleE9mKHZhbCk7XG5cbiAgICAgICAgLy8gV2UgZG8gd2FudCB0byBzZWUgaWYgYSB0YWcgY2hhbmdlIGhhcHBlbmVkIHRob3VnaCAtIGlmIHRoaXMgZGlkIGhhcHBlbiB0aGVuIHdlJ2xsIHdhbnRcbiAgICAgICAgLy8gdG8gZm9yY2UgdGhlIHBvc2l0aW9uIHRvIHplcm8gKHRvcCkgdG8gZW5zdXJlIHdlIGNhbiBwcm9wZXJseSBoYW5kbGUgaXQuXG4gICAgICAgIGNvbnN0IHdhc1N0aWNreSA9IHRoaXMuX2xhc3RTdGlja3lSb29tLnJvb20gPyB0aGlzLl9sYXN0U3RpY2t5Um9vbS5yb29tLnJvb21JZCA9PT0gdmFsLnJvb21JZCA6IGZhbHNlO1xuICAgICAgICBpZiAodGhpcy5fbGFzdFN0aWNreVJvb20udGFnICYmIHRhZyAhPT0gdGhpcy5fbGFzdFN0aWNreVJvb20udGFnICYmIHdhc1N0aWNreSAmJiBwb3NpdGlvbiA8IDApIHtcbiAgICAgICAgICAgIGxvZ2dlci53YXJuKGBTdGlja3kgcm9vbSAke3ZhbC5yb29tSWR9IGNoYW5nZWQgdGFncyBkdXJpbmcgc3RpY2t5IHJvb20gaGFuZGxpbmdgKTtcbiAgICAgICAgICAgIHBvc2l0aW9uID0gMDtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFNhbml0eSBjaGVjayB0aGUgcG9zaXRpb24gdG8gbWFrZSBzdXJlIHRoZSByb29tIGlzIHF1YWxpZmllZCBmb3IgYmVpbmcgc3RpY2t5XG4gICAgICAgIGlmIChwb3NpdGlvbiA8IDApIHRocm93IG5ldyBFcnJvcihgJHt2YWwucm9vbUlkfSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUga25vd24gYW5kIGNhbm5vdCBiZSBzdGlja3lgKTtcblxuICAgICAgICAvLyDwn5CJIEhlcmUgYmUgZHJhZ29ucy5cbiAgICAgICAgLy8gQmVmb3JlIHdlIGNhbiBnbyB0aHJvdWdoIHdpdGggbHlpbmcgdG8gdGhlIHVuZGVybHlpbmcgYWxnb3JpdGhtIGFib3V0IGEgcm9vbVxuICAgICAgICAvLyB3ZSBuZWVkIHRvIGVuc3VyZSB0aGF0IHdoZW4gd2UgZG8gd2UncmUgcmVhZHkgZm9yIHRoZSBpbmV2aXRhYmxlIHN0aWNreSByb29tXG4gICAgICAgIC8vIHVwZGF0ZSB3ZSdsbCByZWNlaXZlLiBUbyBwcmVwYXJlIGZvciB0aGF0LCB3ZSBmaXJzdCByZW1vdmUgdGhlIHN0aWNreSByb29tIGFuZFxuICAgICAgICAvLyByZWNhbGN1bGF0ZSB0aGUgc3RhdGUgb3Vyc2VsdmVzIHNvIHRoYXQgd2hlbiB0aGUgdW5kZXJseWluZyBhbGdvcml0aG0gY2FsbHMgZm9yXG4gICAgICAgIC8vIHRoZSBzYW1lIHRoaW5nIGl0IG5vLW9wcy4gQWZ0ZXIgd2UncmUgZG9uZSBjYWxsaW5nIHRoZSBhbGdvcml0aG0sIHdlJ2xsIGlzc3VlXG4gICAgICAgIC8vIGEgbmV3IHVwZGF0ZSBmb3Igb3Vyc2VsdmVzLlxuICAgICAgICBjb25zdCBsYXN0U3RpY2t5Um9vbSA9IHRoaXMuX3N0aWNreVJvb207XG4gICAgICAgIHRoaXMuX3N0aWNreVJvb20gPSBudWxsOyAvLyBjbGVhciBiZWZvcmUgd2UgdXBkYXRlIHRoZSBhbGdvcml0aG1cbiAgICAgICAgdGhpcy5yZWNhbGN1bGF0ZVN0aWNreVJvb20oKTtcblxuICAgICAgICAvLyBXaGVuIHdlIGRvIGhhdmUgdGhlIHJvb20sIHJlLWFkZCB0aGUgb2xkIHJvb20gKGlmIG5lZWRlZCkgdG8gdGhlIGFsZ29yaXRobVxuICAgICAgICAvLyBhbmQgcmVtb3ZlIHRoZSBzdGlja3kgcm9vbSBmcm9tIHRoZSBhbGdvcml0aG0uIFRoaXMgaXMgc28gdGhlIHVuZGVybHlpbmdcbiAgICAgICAgLy8gYWxnb3JpdGhtIGRvZXNuJ3QgdHJ5IGFuZCBjb25mdXNlIGl0c2VsZiB3aXRoIHRoZSBzdGlja3kgcm9vbSBjb25jZXB0LlxuICAgICAgICAvLyBXZSBkb24ndCBhZGQgdGhlIG5ldyByb29tIGlmIHRoZSBzdGlja3kgcm9vbSBpc24ndCBjaGFuZ2luZyBiZWNhdXNlIHRoYXQnc1xuICAgICAgICAvLyBhbiBlYXN5IHdheSB0byBjYXVzZSBkdXBsaWNhdGlvbi4gV2UgaGF2ZSB0byBkbyByb29tIElEIGNoZWNrcyBpbnN0ZWFkIG9mXG4gICAgICAgIC8vIHJlZmVyZW50aWFsIGNoZWNrcyBhcyB0aGUgcmVmZXJlbmNlcyBjYW4gZGlmZmVyIHRocm91Z2ggdGhlIGxpZmVjeWNsZS5cbiAgICAgICAgaWYgKGxhc3RTdGlja3lSb29tICYmIGxhc3RTdGlja3lSb29tLnJvb20gJiYgbGFzdFN0aWNreVJvb20ucm9vbS5yb29tSWQgIT09IHZhbC5yb29tSWQpIHtcbiAgICAgICAgICAgIC8vIExpZSB0byB0aGUgYWxnb3JpdGhtIGFuZCByZS1hZGQgdGhlIHJvb20gdG8gdGhlIGFsZ29yaXRobVxuICAgICAgICAgICAgdGhpcy5oYW5kbGVSb29tVXBkYXRlKGxhc3RTdGlja3lSb29tLnJvb20sIFJvb21VcGRhdGVDYXVzZS5OZXdSb29tKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBMaWUgdG8gdGhlIGFsZ29yaXRobSBhbmQgcmVtb3ZlIHRoZSByb29tIGZyb20gaXQncyBmaWVsZCBvZiB2aWV3XG4gICAgICAgIHRoaXMuaGFuZGxlUm9vbVVwZGF0ZSh2YWwsIFJvb21VcGRhdGVDYXVzZS5Sb29tUmVtb3ZlZCk7XG5cbiAgICAgICAgLy8gaGFuZGxlUm9vbVVwZGF0ZSBtYXkgaGF2ZSBtb2RpZmllZCB0aGlzLl9zdGlja3lSb29tLiBDb252aW5jZSB0aGVcbiAgICAgICAgLy8gY29tcGlsZXIgb2YgdGhpcyBmYWN0LlxuICAgICAgICB0aGlzLl9zdGlja3lSb29tID0gdGhpcy5zdGlja3lSb29tTWlnaHRCZU1vZGlmaWVkKCk7XG5cbiAgICAgICAgLy8gQ2hlY2sgZm9yIHRhZyAmIHBvc2l0aW9uIGNoYW5nZXMgd2hpbGUgd2UncmUgaGVyZS4gV2UgYWxzbyBjaGVjayB0aGUgcm9vbSB0byBlbnN1cmVcbiAgICAgICAgLy8gaXQgaXMgc3RpbGwgdGhlIHNhbWUgcm9vbS5cbiAgICAgICAgaWYgKHRoaXMuX3N0aWNreVJvb20pIHtcbiAgICAgICAgICAgIGlmICh0aGlzLl9zdGlja3lSb29tLnJvb20gIT09IHZhbCkge1xuICAgICAgICAgICAgICAgIC8vIENoZWNrIHRoZSByb29tIElEcyBqdXN0IGluIGNhc2VcbiAgICAgICAgICAgICAgICBpZiAodGhpcy5fc3RpY2t5Um9vbS5yb29tLnJvb21JZCA9PT0gdmFsLnJvb21JZCkge1xuICAgICAgICAgICAgICAgICAgICBsb2dnZXIud2FybihcIlN0aWNreSByb29tIGNoYW5nZWQgcmVmZXJlbmNlc1wiKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJTdGlja3kgcm9vbSBjaGFuZ2VkIHdoaWxlIHRoZSBzdGlja3kgcm9vbSB3YXMgY2hhbmdpbmdcIik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBsb2dnZXIud2FybihcbiAgICAgICAgICAgICAgICBgU3RpY2t5IHJvb20gY2hhbmdlZCB0YWcgJiBwb3NpdGlvbiBmcm9tICR7dGFnfSAvICR7cG9zaXRpb259IGAgK1xuICAgICAgICAgICAgICAgICAgICBgdG8gJHt0aGlzLl9zdGlja3lSb29tLnRhZ30gLyAke3RoaXMuX3N0aWNreVJvb20ucG9zaXRpb259YCxcbiAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgIHRhZyA9IHRoaXMuX3N0aWNreVJvb20udGFnO1xuICAgICAgICAgICAgcG9zaXRpb24gPSB0aGlzLl9zdGlja3lSb29tLnBvc2l0aW9uO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gTm93IHRoYXQgd2UncmUgZG9uZSBseWluZyB0byB0aGUgYWxnb3JpdGhtLCB3ZSBuZWVkIHRvIHVwZGF0ZSBvdXIgcG9zaXRpb25cbiAgICAgICAgLy8gbWFya2VyIG9ubHkgaWYgdGhlIHVzZXIgaXM