UNPKG

@jsprismarine/prismarine

Version:

Dedicated Minecraft Bedrock Edition server written in TypeScript

570 lines (568 loc) • 83.3 kB
import { creativeitems } from '@jsprismarine/bedrock-data'; import { Gametype } from '@jsprismarine/minecraft'; import Heap from 'heap'; import { CommandArgumentEntity, CommandArgumentGamemode } from '../command/CommandArguments.es.js'; import { WindowIds } from '../inventory/WindowIds.es.js'; import { Item } from '../item/Item.es.js'; import UUID from '../utils/UUID.es.js'; import BlockPosition from '../world/BlockPosition.es.js'; import CoordinateUtils from '../world/CoordinateUtils.es.js'; import Chunk from '../world/chunk/Chunk.es.js'; import './packet/ActorFallPacket.es.js'; import './packet/AddActorPacket.es.js'; import './packet/AddItemActorPacket.es.js'; import AddPlayerPacket from './packet/AddPlayerPacket.es.js'; import './packet/AnimatePacket.es.js'; import './packet/AvailableActorIdentifiersPacket.es.js'; import AvailableCommandsPacket from './packet/AvailableCommandsPacket.es.js'; import BatchPacket from './packet/BatchPacket.es.js'; import './packet/BiomeDefinitionListPacket.es.js'; import './packet/ChangeDimensionPacket.es.js'; import ChunkRadiusUpdatedPacket from './packet/ChunkRadiusUpdatedPacket.es.js'; import './packet/CommandRequestPacket.es.js'; import './packet/ContainerClosePacket.es.js'; import './packet/ContainerOpenPacket.es.js'; import CreativeContentPacket from './packet/CreativeContentPacket.es.js'; import '@jsprismarine/jsbinaryutils'; import DisconnectPacket from './packet/DisconnectPacket.es.js'; import './packet/EmoteListPacket.es.js'; import './packet/InteractPacket.es.js'; import InventoryContentPacket from './packet/InventoryContentPacket.es.js'; import './packet/InventoryTransactionPacket.es.js'; import './packet/ItemComponentPacket.es.js'; import './packet/ItemStackRequestPacket.es.js'; import './packet/ItemStackResponsePacket.es.js'; import LevelChunkPacket from './packet/LevelChunkPacket.es.js'; import './packet/LevelSoundEventPacket.es.js'; import './packet/LoginPacket.es.js'; import MobEquipmentPacket from './packet/MobEquipmentPacket.es.js'; import './packet/MoveActorAbsolutePacket.es.js'; import MovePlayerPacket from './packet/MovePlayerPacket.es.js'; import NetworkChunkPublisherUpdatePacket from './packet/NetworkChunkPublisherUpdatePacket.es.js'; import './packet/OnScreenTextureAnimationPacket.es.js'; import './packet/PacketViolationWarningPacket.es.js'; import './packet/PlaySoundPacket.es.js'; import PlayStatusPacket from './packet/PlayStatusPacket.es.js'; import './packet/PlayerActionPacket.es.js'; import PlayerListPacket, { PlayerListEntry, PlayerListAction } from './packet/PlayerListPacket.es.js'; import './packet/PlayerSkinPacket.es.js'; import RemoveActorPacket from './packet/RemoveActorPacket.es.js'; import './packet/RequestChunkRadiusPacket.es.js'; import './packet/RequestNetworkSettingsPacket.es.js'; import './packet/ResourcePackResponsePacket.es.js'; import './packet/ResourcePackStackPacket.es.js'; import './packet/ResourcePacksInfoPacket.es.js'; import './packet/ServerSettingsRequestPacket.es.js'; import SetActorDataPacket from './packet/SetActorDataPacket.es.js'; import './packet/SetDefaultGametypePacket.es.js'; import './packet/SetHealthPacket.es.js'; import './packet/SetLocalPlayerAsInitializedPacket.es.js'; import SetPlayerGametypePacket from './packet/SetPlayerGametypePacket.es.js'; import SetTimePacket from './packet/SetTimePacket.es.js'; import './packet/ShowProfilePacket.es.js'; import './packet/StartGamePacket.es.js'; import TextPacket from './packet/TextPacket.es.js'; import './packet/TickSyncPacket.es.js'; import './packet/TransferPacket.es.js'; import UpdateAdventureSettingsPacket from './packet/UpdateAdventureSettingsPacket.es.js'; import UpdateAttributesPacket from './packet/UpdateAttributesPacket.es.js'; import './packet/UpdateBlockPacket.es.js'; import './packet/WorldEventPacket.es.js'; import UpdateAbilitiesPacket, { AbilityLayerType, AbilityLayerFlag, AbilityLayer } from './packet/UpdateAbilitiesPacket.es.js'; import CommandData from './type/CommandData.es.js'; import { CommandEnum } from './type/CommandEnum.es.js'; import CommandParameter, { CommandParameterType } from './type/CommandParameter.es.js'; import MovementType from './type/MovementType.es.js'; import PermissionType from './type/PermissionType.es.js'; import PlayerPermissionType from './type/PlayerPermissionType.es.js'; import TextType from './type/TextType.es.js'; class PlayerSession { connection; server; player; chunkSendQueue = []; loadedChunks = /* @__PURE__ */ new Set(); loadingChunks = /* @__PURE__ */ new Set(); constructor(server, connection, player) { this.server = server; this.connection = connection; this.player = player; } async update(_tick) { if (this.chunkSendQueue.length > 0) { const chunksToSend = this.chunkSendQueue.splice(0, Math.min(this.chunkSendQueue.length, 50)); const batch = new BatchPacket(); for (const chunk of chunksToSend) { const pk = new LevelChunkPacket(); pk.chunkX = chunk.getX(); pk.chunkZ = chunk.getZ(); pk.clientSubChunkRequestsEnabled = false; pk.subChunkCount = chunk.getTopEmpty() + 4; pk.data = chunk.networkSerialize(); batch.addPacket(pk); const hash = Chunk.packXZ(chunk.getX(), chunk.getZ()); this.loadedChunks.add(hash); this.loadingChunks.delete(hash); } this.connection.sendBatch(batch, false); } this.player.viewDistance && await this.needNewChunks(); } async send(packet) { this.connection.sendDataPacket(packet); } /** * Notify a client about change(s) to the adventure settings. * * @param player - The client-controlled entity */ async sendSettings(player) { const target = player ?? this.player; const packet = new UpdateAdventureSettingsPacket(); packet.worldImmutable = target.gamemode === Gametype.SPECTATOR; packet.noAttackingPlayers = target.gamemode === Gametype.SPECTATOR; packet.noAttackingMobs = target.gamemode === Gametype.SPECTATOR; packet.autoJump = true; packet.showNameTags = true; await this.connection.sendDataPacket(packet); } async sendAbilities(player) { const target = player ?? this.player; const mainLayer = new AbilityLayer(); mainLayer.layerType = AbilityLayerType.BASE; mainLayer.layerFlags = /* @__PURE__ */ new Map([ [AbilityLayerFlag.FLY_SPEED, true], [AbilityLayerFlag.WALK_SPEED, true], [AbilityLayerFlag.MAY_FLY, target.metadata.canFly], [AbilityLayerFlag.FLYING, target.isFlying()], [AbilityLayerFlag.NO_CLIP, target.gamemode === Gametype.SPECTATOR], [AbilityLayerFlag.OPERATOR_COMMANDS, target.isOp()], [AbilityLayerFlag.TELEPORT, target.isOp()], [AbilityLayerFlag.INVULNERABLE, target.gamemode === Gametype.SPECTATOR], [AbilityLayerFlag.MUTED, false], [AbilityLayerFlag.WORLD_BUILDER, false], [AbilityLayerFlag.INSTABUILD, target.gamemode === Gametype.SPECTATOR], [AbilityLayerFlag.LIGHTNING, false], [AbilityLayerFlag.BUILD, target.gamemode !== Gametype.SPECTATOR], [AbilityLayerFlag.MINE, target.gamemode !== Gametype.SPECTATOR], [AbilityLayerFlag.DOORS_AND_SWITCHES, target.gamemode !== Gametype.SPECTATOR], [AbilityLayerFlag.OPEN_CONTAINERS, target.gamemode !== Gametype.SPECTATOR], [AbilityLayerFlag.ATTACK_PLAYERS, target.gamemode !== Gametype.SPECTATOR], [AbilityLayerFlag.ATTACK_MOBS, target.gamemode !== Gametype.SPECTATOR] ]); mainLayer.flySpeed = 0.05; mainLayer.walkSpeed = 0.1; const packet = new UpdateAbilitiesPacket(); packet.commandPermission = target.isOp() ? PermissionType.Operator : PermissionType.Normal; packet.playerPermission = target.isOp() ? PlayerPermissionType.Operator : PlayerPermissionType.Member; packet.targetActorUniqueId = target.getRuntimeId(); packet.abilityLayers = [mainLayer]; await this.send(packet); } async needNewChunks(forceResend = false, dist) { const currentXChunk = CoordinateUtils.fromBlockToChunk(this.player.getX()); const currentZChunk = CoordinateUtils.fromBlockToChunk(this.player.getZ()); const viewDistance = this.player.viewDistance || dist || 0; const chunksToSendHeap = new Heap((a, b) => { const distXFirst = Math.abs(a[0] - currentXChunk); const distXSecond = Math.abs(b[0] - currentXChunk); const distZFirst = Math.abs(a[1] - currentZChunk); const distZSecond = Math.abs(b[1] - currentZChunk); return distXFirst + distZFirst > distXSecond + distZSecond ? 1 : -1; }); for (let sendXChunk = -viewDistance; sendXChunk <= viewDistance; sendXChunk++) { for (let sendZChunk = -viewDistance; sendZChunk <= viewDistance; sendZChunk++) { if (sendXChunk * sendXChunk + sendZChunk * sendZChunk > viewDistance * viewDistance) continue; const newChunk = [currentXChunk + sendXChunk, currentZChunk + sendZChunk]; const hash = Chunk.packXZ(newChunk[0], newChunk[1]); if (forceResend) { chunksToSendHeap.push(newChunk); } else if (!this.loadedChunks.has(hash) && !this.loadingChunks.has(hash)) { chunksToSendHeap.push(newChunk); } } } while (chunksToSendHeap.size() > 0) { const closestChunk = chunksToSendHeap.pop(); const hash = Chunk.packXZ(closestChunk[0], closestChunk[1]); if (forceResend) { if (!this.loadedChunks.has(hash) && !this.loadingChunks.has(hash)) { this.loadingChunks.add(hash); await this.requestChunk(closestChunk[0], closestChunk[1]); } else { const loadedChunk = await this.player.getWorld().getChunk(closestChunk[0], closestChunk[1]); await this.sendChunk(loadedChunk); } } else { this.loadingChunks.add(hash); await this.requestChunk(closestChunk[0], closestChunk[1]); } } let unloaded = false; for (const hash of this.loadedChunks) { const [x, z] = Chunk.unpackXZ(hash); if (Math.abs(x - currentXChunk) > viewDistance || Math.abs(z - currentZChunk) > viewDistance) { unloaded = true; this.loadedChunks.delete(hash); } } for (const hash of this.loadingChunks) { const [x, z] = Chunk.unpackXZ(hash); if (Math.abs(x - currentXChunk) > viewDistance || Math.abs(z - currentZChunk) > viewDistance) { this.loadingChunks.delete(hash); } } if (!unloaded && this.chunkSendQueue.length !== 0) { await this.sendNetworkChunkPublisher(dist ?? viewDistance, []); } } async requestChunk(x, z) { const chunk = await this.player.getWorld().getChunk(x, z); this.chunkSendQueue.push(chunk); } /** * Clear the currently loaded and loading chunks. * * @remarks * Usually used for changing dimension, world, etc. */ async clearChunks() { this.loadedChunks.clear(); this.loadingChunks.clear(); } /** * @TODO: Implement this. */ async sendInventory() { const pk = new InventoryContentPacket(); pk.items = this.player.getInventory().getItems(true); pk.windowId = WindowIds.INVENTORY; } async sendCreativeContents(empty = false) { const pk = new CreativeContentPacket(); if (empty) { await this.connection.sendDataPacket(pk); return; } const entries = [ ...this.player.getServer().getBlockManager().getBlocks(), ...this.player.getServer().getItemManager().getItems() ]; creativeitems.forEach((item) => { pk.items.push( ...entries.filter((entry) => { return entry.meta === (item.damage ?? 0) && entry.getId() === item.id; }).map( (entry) => new Item({ id: entry.getId(), name: entry.getName(), meta: entry.meta }) ) ); }); await this.connection.sendDataPacket(pk); } /** * Sets the item in the player hand. * @param {Item} item - The entity. */ async sendHandItem(item) { const pk = new MobEquipmentPacket(); pk.runtimeEntityId = this.player.getRuntimeId(); pk.item = item; pk.inventorySlot = this.player.getInventory().getHandSlotIndex(); pk.hotbarSlot = this.player.getInventory().getHandSlotIndex(); pk.windowId = 0; await this.connection.sendDataPacket(pk); } /** * Send the current tick to a client. * @param {number} tick - The tick */ async sendTime(tick) { const pk = new SetTimePacket(); pk.time = tick; await this.connection.sendDataPacket(pk); } /** * Send gamemode to a client. * @param {Gametype} gamemode - the numeric gamemode ID. */ async sendGamemode(gamemode) { const packet = new SetPlayerGametypePacket(); packet.gametype = gamemode ?? this.player.gamemode; await this.connection.sendDataPacket(packet); } async sendNetworkChunkPublisher(distance, savedChunks) { const pk = new NetworkChunkPublisherUpdatePacket(); pk.position = BlockPosition.fromVector3(this.player.getPosition()); pk.radius = distance << 4; pk.savedChunks = savedChunks; await this.connection.sendDataPacket(pk); } async sendAvailableCommands() { const playerEnum = new CommandEnum(); playerEnum.soft = true; playerEnum.name = "Player"; playerEnum.values = this.player.getServer().getSessionManager().getAllPlayers().map((player) => player.getName()); const pk = new AvailableCommandsPacket(); pk.softEnums = [playerEnum]; this.server.getCommandManager().getCommandsList().forEach((command) => { const commandClass = Array.from(this.server.getCommandManager().getCommands().values()).find( (cmd2) => cmd2.name === command[0] ); if (!commandClass) { this.player.getServer().getLogger().warn(`Can't find corresponding command class for "${command[0]}"`); return; } if (!this.player.getServer().getPermissionManager().can(this.player).execute(commandClass.permission)) return; const cmd = new CommandData(); cmd.commandName = command[0]; cmd.commandDescription = commandClass.description; if (commandClass.aliases.length > 0) { const cmdAliases = new CommandEnum(); cmdAliases.name = `${command[0]}Aliases`; cmdAliases.values = commandClass.aliases.concat(command[0]); cmd.aliases = cmdAliases; } command[2].forEach((arg, index) => { const parameters = arg.map((parameter) => { if (!parameter || !parameter?.getParameters) return []; const parameters2 = parameter.getParameters(this.server); if (parameters2) return Array.from(parameters2.values()); if (parameter instanceof CommandArgumentEntity) return [ new CommandParameter({ paramName: "target", paramType: CommandParameterType.Target }) ]; if (parameter instanceof CommandArgumentGamemode) return [ new CommandParameter({ paramName: "gamemode", paramType: CommandParameterType.String }) ]; if (parameter.constructor.name === "StringArgumentType") return [ new CommandParameter({ paramName: "value", paramType: CommandParameterType.String }) ]; if (parameter.constructor.name === "IntegerArgumentType") return [ new CommandParameter({ paramName: "number", paramType: CommandParameterType.Int }) ]; this.server.getLogger().warn(`Invalid parameter ${parameter.constructor.name}`); return [ new CommandParameter({ paramName: "value", paramType: CommandParameterType.String }) ]; }).flat(); cmd.overloads[index] = parameters; }); pk.commandData.push(cmd); }); await this.getConnection().sendDataPacket(pk); } /** * Set the client's maximum view distance. * * @param distance - The view distance */ async setViewDistance(distance) { const packet = new ChunkRadiusUpdatedPacket(); packet.radius = this.player.viewDistance = distance; await this.connection.sendDataPacket(packet); } async sendAttributes(attributes) { const value = attributes?.getAttributes() ?? this.player.attributes.getAttributes(); const packet = new UpdateAttributesPacket(); packet.runtimeEntityId = this.player.getRuntimeId(); packet.attributes = value.length > 0 ? value : this.player.attributes.getDefaults(); packet.tick = BigInt(this.player.getServer().getTick()); await this.connection.sendDataPacket(packet); } async sendMetadata(metadata) { const packet = new SetActorDataPacket(); packet.runtimeEntityId = this.player.getRuntimeId(); packet.metadata = metadata ?? this.player.metadata; packet.tick = BigInt(this.player.getServer().getTick()); await this.connection.sendDataPacket(packet); } /** * Send a chat message to the client. * @param {object} options - The options for the message. * @param {string} options.message - The message to send. * @param {string} [options.sourceName=''] - The source of the message. * @param {string} [options.xuid=''] - The XUID of the player. * @param {string} [options.platformChatId=''] - The platform chat ID. * @param {string[]} [options.parameters=[]] - The parameters for the message. * @param {boolean} [options.needsTranslation=false] - Whether the message needs translation. * @param {TextType} [options.type=TextType.Raw] - The type of the message. * @returns {Promise<void>} A promise that resolves when the message is sent. */ async sendMessage({ message, sourceName = "", xuid = "", platformChatId = "", parameters = [], needsTranslation = false, type = TextType.Raw }) { if (!message) throw new Error("A message is required"); const pk = new TextPacket(); pk.message = message; pk.filtered = message; pk.sourceName = sourceName; pk.xuid = xuid; pk.platformChatId = platformChatId; pk.parameters = parameters; pk.needsTranslation = needsTranslation; pk.type = type; await this.connection.sendDataPacket(pk); } async sendChunk(chunk) { const hash = Chunk.packXZ(chunk.getX(), chunk.getZ()); const packet = new LevelChunkPacket(); packet.chunkX = chunk.getX(); packet.chunkZ = chunk.getZ(); packet.clientSubChunkRequestsEnabled = false; packet.subChunkCount = chunk.getTopEmpty() + 4; packet.data = chunk.networkSerialize(); await this.send(packet); this.loadingChunks.delete(hash); this.loadedChunks.add(hash); } /** * Broadcast the movement to a defined player */ async broadcastMove(player, mode = MovementType.Normal) { const packet = new MovePlayerPacket(); packet.runtimeEntityId = player.getRuntimeId(); packet.position = player.getPosition(); packet.pitch = player.pitch; packet.yaw = player.yaw; packet.headYaw = player.headYaw; packet.mode = mode; packet.onGround = player.isOnGround(); if (mode === MovementType.Teleport) { packet.teleportCause = 0; packet.teleportItemId = 0; } packet.ridingEntityRuntimeId = BigInt(0); packet.tick = BigInt(this.player.getServer().getTick()); await this.send(packet); } /** * Adds the client to the player list of every player inside * the server and also to the player itself. */ async addToPlayerList() { const entry = new PlayerListEntry({ uuid: UUID.fromString(this.player.getUUID()), runtimeId: this.player.getRuntimeId(), name: this.player.getName(), xuid: this.player.xuid, platformChatId: this.player.platformChatId, buildPlatform: this.player.device?.os ?? -1, skin: this.player.skin, isTeacher: false, isHost: false }); this.server.getSessionManager().getPlayerList().set(this.player.getUUID(), entry); const packet = new PlayerListPacket(); packet.type = PlayerListAction.TYPE_ADD; packet.entries.push(entry); await this.server.broadcastPacket(packet); } /** * Removes a player from other players list */ async removeFromPlayerList() { const entry = new PlayerListEntry({ uuid: UUID.fromString(this.player.getUUID()) }); this.server.getSessionManager().getPlayerList().delete(this.player.getUUID()); const packet = new PlayerListPacket(); packet.type = PlayerListAction.TYPE_REMOVE; packet.entries.push(entry); await this.server.broadcastPacket(packet); } /** * Sends the full player list to the player. */ async sendPlayerList() { const packet = new PlayerListPacket(); packet.type = PlayerListAction.TYPE_ADD; packet.entries = Array.from(this.server.getSessionManager().getPlayerList().values()); await this.send(packet); } /** * Spawn the player for another player * * @param player - the player to send the AddPlayerPacket to */ async sendSpawn(player) { if (!player.getUUID()) { this.server.getLogger().error(`UUID for player ${player.getName()} is undefined`); return; } const pk = new AddPlayerPacket(); pk.uuid = UUID.fromString(this.player.getUUID()); pk.runtimeEntityId = this.player.getRuntimeId(); pk.name = this.player.getName(); pk.positionX = this.player.getX(); pk.positionY = this.player.getY(); pk.positionZ = this.player.getZ(); pk.motionX = 0; pk.motionY = 0; pk.motionZ = 0; pk.pitch = this.player.pitch; pk.yaw = this.player.yaw; pk.headYaw = this.player.headYaw; pk.gamemode = this.player.gamemode; pk.item = this.player.getInventory().getItemInHand(); pk.deviceId = this.player.device?.id ?? ""; pk.metadata = this.player.metadata; await player.getNetworkSession().send(pk); await this.sendSettings(player); } /** * Despawn the player entity from another player */ async sendDespawn(player) { const pk = new RemoveActorPacket(); pk.uniqueEntityId = this.player.getRuntimeId(); await player.getNetworkSession().getConnection().sendDataPacket(pk); } async sendPlayStatus(status) { const pk = new PlayStatusPacket(); pk.status = status; await this.connection.sendDataPacket(pk, true); } async kick(reason = "unknown reason") { const pk = new DisconnectPacket(); pk.skipMessage = false; pk.message = reason; await this.connection.sendDataPacket(pk, true); } getConnection() { return this.connection; } getPlayer() { return this.player; } } export { PlayerSession as default }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGxheWVyU2Vzc2lvbi5lcy5qcyIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL25ldHdvcmsvUGxheWVyU2Vzc2lvbi50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBjcmVhdGl2ZWl0ZW1zIGFzIENyZWF0aXZlSXRlbXMgfSBmcm9tICdAanNwcmlzbWFyaW5lL2JlZHJvY2stZGF0YSc7XG5pbXBvcnQgeyBHYW1ldHlwZSB9IGZyb20gJ0Bqc3ByaXNtYXJpbmUvbWluZWNyYWZ0JztcbmltcG9ydCBIZWFwIGZyb20gJ2hlYXAnO1xuaW1wb3J0IHR5cGUgUGxheWVyIGZyb20gJy4uL1BsYXllcic7XG5pbXBvcnQgdHlwZSBTZXJ2ZXIgZnJvbSAnLi4vU2VydmVyJztcbmltcG9ydCB0eXBlIHsgQ29tbWFuZEFyZ3VtZW50IH0gZnJvbSAnLi4vY29tbWFuZC9Db21tYW5kQXJndW1lbnRzJztcbmltcG9ydCB7IENvbW1hbmRBcmd1bWVudEVudGl0eSwgQ29tbWFuZEFyZ3VtZW50R2FtZW1vZGUgfSBmcm9tICcuLi9jb21tYW5kL0NvbW1hbmRBcmd1bWVudHMnO1xuaW1wb3J0IHR5cGUgeyBBdHRyaWJ1dGVzIH0gZnJvbSAnLi4vZW50aXR5L0F0dHJpYnV0ZSc7XG5pbXBvcnQgdHlwZSB7IE1ldGFkYXRhIH0gZnJvbSAnLi4vZW50aXR5L01ldGFkYXRhJztcbmltcG9ydCB7IFdpbmRvd0lkcyB9IGZyb20gJy4uL2ludmVudG9yeS9XaW5kb3dJZHMnO1xuaW1wb3J0IHsgSXRlbSB9IGZyb20gJy4uL2l0ZW0vSXRlbSc7XG5pbXBvcnQgVVVJRCBmcm9tICcuLi91dGlscy9VVUlEJztcbmltcG9ydCBCbG9ja1Bvc2l0aW9uIGZyb20gJy4uL3dvcmxkL0Jsb2NrUG9zaXRpb24nO1xuaW1wb3J0IENvb3JkaW5hdGVVdGlscyBmcm9tICcuLi93b3JsZC9Db29yZGluYXRlVXRpbHMnO1xuaW1wb3J0IENodW5rIGZyb20gJy4uL3dvcmxkL2NodW5rL0NodW5rJztcbmltcG9ydCB0eXBlIENsaWVudENvbm5lY3Rpb24gZnJvbSAnLi9DbGllbnRDb25uZWN0aW9uJztcbmltcG9ydCB0eXBlIHsgRGF0YVBhY2tldCB9IGZyb20gJy4vUGFja2V0cyc7XG5pbXBvcnQgeyBCYXRjaFBhY2tldCB9IGZyb20gJy4vUGFja2V0cyc7XG5pbXBvcnQgQWRkUGxheWVyUGFja2V0IGZyb20gJy4vcGFja2V0L0FkZFBsYXllclBhY2tldCc7XG5pbXBvcnQgQXZhaWxhYmxlQ29tbWFuZHNQYWNrZXQgZnJvbSAnLi9wYWNrZXQvQXZhaWxhYmxlQ29tbWFuZHNQYWNrZXQnO1xuaW1wb3J0IENodW5rUmFkaXVzVXBkYXRlZFBhY2tldCBmcm9tICcuL3BhY2tldC9DaHVua1JhZGl1c1VwZGF0ZWRQYWNrZXQnO1xuaW1wb3J0IENyZWF0aXZlQ29udGVudFBhY2tldCBmcm9tICcuL3BhY2tldC9DcmVhdGl2ZUNvbnRlbnRQYWNrZXQnO1xuaW1wb3J0IERpc2Nvbm5lY3RQYWNrZXQgZnJvbSAnLi9wYWNrZXQvRGlzY29ubmVjdFBhY2tldCc7XG5pbXBvcnQgSW52ZW50b3J5Q29udGVudFBhY2tldCBmcm9tICcuL3BhY2tldC9JbnZlbnRvcnlDb250ZW50UGFja2V0JztcbmltcG9ydCBMZXZlbENodW5rUGFja2V0IGZyb20gJy4vcGFja2V0L0xldmVsQ2h1bmtQYWNrZXQnO1xuaW1wb3J0IE1vYkVxdWlwbWVudFBhY2tldCBmcm9tICcuL3BhY2tldC9Nb2JFcXVpcG1lbnRQYWNrZXQnO1xuaW1wb3J0IE1vdmVQbGF5ZXJQYWNrZXQgZnJvbSAnLi9wYWNrZXQvTW92ZVBsYXllclBhY2tldCc7XG5pbXBvcnQgdHlwZSB7IENodW5rQ29vcmQgfSBmcm9tICcuL3BhY2tldC9OZXR3b3JrQ2h1bmtQdWJsaXNoZXJVcGRhdGVQYWNrZXQnO1xuaW1wb3J0IE5ldHdvcmtDaHVua1B1Ymxpc2hlclVwZGF0ZVBhY2tldCBmcm9tICcuL3BhY2tldC9OZXR3b3JrQ2h1bmtQdWJsaXNoZXJVcGRhdGVQYWNrZXQnO1xuaW1wb3J0IFBsYXlTdGF0dXNQYWNrZXQgZnJvbSAnLi9wYWNrZXQvUGxheVN0YXR1c1BhY2tldCc7XG5pbXBvcnQgUGxheWVyTGlzdFBhY2tldCwgeyBQbGF5ZXJMaXN0QWN0aW9uLCBQbGF5ZXJMaXN0RW50cnkgfSBmcm9tICcuL3BhY2tldC9QbGF5ZXJMaXN0UGFja2V0JztcbmltcG9ydCBSZW1vdmVBY3RvclBhY2tldCBmcm9tICcuL3BhY2tldC9SZW1vdmVBY3RvclBhY2tldCc7XG5pbXBvcnQgU2V0QWN0b3JEYXRhUGFja2V0IGZyb20gJy4vcGFja2V0L1NldEFjdG9yRGF0YVBhY2tldCc7XG5pbXBvcnQgU2V0UGxheWVyR2FtZXR5cGVQYWNrZXQgZnJvbSAnLi9wYWNrZXQvU2V0UGxheWVyR2FtZXR5cGVQYWNrZXQnO1xuaW1wb3J0IFNldFRpbWVQYWNrZXQgZnJvbSAnLi9wYWNrZXQvU2V0VGltZVBhY2tldCc7XG5pbXBvcnQgVGV4dFBhY2tldCBmcm9tICcuL3BhY2tldC9UZXh0UGFja2V0JztcbmltcG9ydCBVcGRhdGVBYmlsaXRpZXNQYWNrZXQsIHtcbiAgICBBYmlsaXR5TGF5ZXIsXG4gICAgQWJpbGl0eUxheWVyRmxhZyxcbiAgICBBYmlsaXR5TGF5ZXJUeXBlXG59IGZyb20gJy4vcGFja2V0L1VwZGF0ZUFiaWxpdGllc1BhY2tldCc7XG5pbXBvcnQgVXBkYXRlQWR2ZW50dXJlU2V0dGluZ3NQYWNrZXQgZnJvbSAnLi9wYWNrZXQvVXBkYXRlQWR2ZW50dXJlU2V0dGluZ3NQYWNrZXQnO1xuaW1wb3J0IFVwZGF0ZUF0dHJpYnV0ZXNQYWNrZXQgZnJvbSAnLi9wYWNrZXQvVXBkYXRlQXR0cmlidXRlc1BhY2tldCc7XG5pbXBvcnQgQ29tbWFuZERhdGEgZnJvbSAnLi90eXBlL0NvbW1hbmREYXRhJztcbmltcG9ydCB7IENvbW1hbmRFbnVtIH0gZnJvbSAnLi90eXBlL0NvbW1hbmRFbnVtJztcbmltcG9ydCBDb21tYW5kUGFyYW1ldGVyLCB7IENvbW1hbmRQYXJhbWV0ZXJUeXBlIH0gZnJvbSAnLi90eXBlL0NvbW1hbmRQYXJhbWV0ZXInO1xuaW1wb3J0IE1vdmVtZW50VHlwZSBmcm9tICcuL3R5cGUvTW92ZW1lbnRUeXBlJztcbmltcG9ydCBQZXJtaXNzaW9uVHlwZSBmcm9tICcuL3R5cGUvUGVybWlzc2lvblR5cGUnO1xuaW1wb3J0IFBsYXllclBlcm1pc3Npb25UeXBlIGZyb20gJy4vdHlwZS9QbGF5ZXJQZXJtaXNzaW9uVHlwZSc7XG5pbXBvcnQgVGV4dFR5cGUgZnJvbSAnLi90eXBlL1RleHRUeXBlJztcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgUGxheWVyU2Vzc2lvbiB7XG4gICAgcHJpdmF0ZSBjb25uZWN0aW9uOiBDbGllbnRDb25uZWN0aW9uO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgc2VydmVyOiBTZXJ2ZXI7XG4gICAgcHJpdmF0ZSBwbGF5ZXI6IFBsYXllcjtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgY2h1bmtTZW5kUXVldWU6IENodW5rW10gPSBbXTtcbiAgICBwcml2YXRlIHJlYWRvbmx5IGxvYWRlZENodW5rczogU2V0PGJpZ2ludD4gPSBuZXcgU2V0KCk7XG4gICAgcHJpdmF0ZSByZWFkb25seSBsb2FkaW5nQ2h1bmtzOiBTZXQ8YmlnaW50PiA9IG5ldyBTZXQoKTtcblxuICAgIHB1YmxpYyBjb25zdHJ1Y3RvcihzZXJ2ZXI6IFNlcnZlciwgY29ubmVjdGlvbjogQ2xpZW50Q29ubmVjdGlvbiwgcGxheWVyOiBQbGF5ZXIpIHtcbiAgICAgICAgdGhpcy5zZXJ2ZXIgPSBzZXJ2ZXI7XG4gICAgICAgIHRoaXMuY29ubmVjdGlvbiA9IGNvbm5lY3Rpb247XG4gICAgICAgIHRoaXMucGxheWVyID0gcGxheWVyO1xuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyB1cGRhdGUoX3RpY2s6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBpZiAodGhpcy5jaHVua1NlbmRRdWV1ZS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBjb25zdCBjaHVua3NUb1NlbmQgPSB0aGlzLmNodW5rU2VuZFF1ZXVlLnNwbGljZSgwLCBNYXRoLm1pbih0aGlzLmNodW5rU2VuZFF1ZXVlLmxlbmd0aCwgNTApKTtcbiAgICAgICAgICAgIGNvbnN0IGJhdGNoID0gbmV3IEJhdGNoUGFja2V0KCk7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGNodW5rIG9mIGNodW5rc1RvU2VuZCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHBrID0gbmV3IExldmVsQ2h1bmtQYWNrZXQoKTtcbiAgICAgICAgICAgICAgICBway5jaHVua1ggPSBjaHVuay5nZXRYKCk7XG4gICAgICAgICAgICAgICAgcGsuY2h1bmtaID0gY2h1bmsuZ2V0WigpO1xuICAgICAgICAgICAgICAgIHBrLmNsaWVudFN1YkNodW5rUmVxdWVzdHNFbmFibGVkID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgcGsuc3ViQ2h1bmtDb3VudCA9IGNodW5rLmdldFRvcEVtcHR5KCkgKyA0O1xuICAgICAgICAgICAgICAgIHBrLmRhdGEgPSBjaHVuay5uZXR3b3JrU2VyaWFsaXplKCk7XG4gICAgICAgICAgICAgICAgYmF0Y2guYWRkUGFja2V0KHBrKTtcblxuICAgICAgICAgICAgICAgIGNvbnN0IGhhc2ggPSBDaHVuay5wYWNrWFooY2h1bmsuZ2V0WCgpLCBjaHVuay5nZXRaKCkpO1xuICAgICAgICAgICAgICAgIHRoaXMubG9hZGVkQ2h1bmtzLmFkZChoYXNoKTtcbiAgICAgICAgICAgICAgICB0aGlzLmxvYWRpbmdDaHVua3MuZGVsZXRlKGhhc2gpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLnNlbmRCYXRjaChiYXRjaCwgZmFsc2UpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5wbGF5ZXIudmlld0Rpc3RhbmNlICYmIChhd2FpdCB0aGlzLm5lZWROZXdDaHVua3MoKSk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHNlbmQocGFja2V0OiBEYXRhUGFja2V0KSB7XG4gICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kRGF0YVBhY2tldChwYWNrZXQpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE5vdGlmeSBhIGNsaWVudCBhYm91dCBjaGFuZ2UocykgdG8gdGhlIGFkdmVudHVyZSBzZXR0aW5ncy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBwbGF5ZXIgLSBUaGUgY2xpZW50LWNvbnRyb2xsZWQgZW50aXR5XG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIHNlbmRTZXR0aW5ncyhwbGF5ZXI/OiBQbGF5ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgdGFyZ2V0ID0gcGxheWVyID8/IHRoaXMucGxheWVyO1xuXG4gICAgICAgIGNvbnN0IHBhY2tldCA9IG5ldyBVcGRhdGVBZHZlbnR1cmVTZXR0aW5nc1BhY2tldCgpO1xuICAgICAgICBwYWNrZXQud29ybGRJbW11dGFibGUgPSB0YXJnZXQuZ2FtZW1vZGUgPT09IEdhbWV0eXBlLlNQRUNUQVRPUjtcbiAgICAgICAgcGFja2V0Lm5vQXR0YWNraW5nUGxheWVycyA9IHRhcmdldC5nYW1lbW9kZSA9PT0gR2FtZXR5cGUuU1BFQ1RBVE9SO1xuICAgICAgICBwYWNrZXQubm9BdHRhY2tpbmdNb2JzID0gdGFyZ2V0LmdhbWVtb2RlID09PSBHYW1ldHlwZS5TUEVDVEFUT1I7XG4gICAgICAgIHBhY2tldC5hdXRvSnVtcCA9IHRydWU7XG4gICAgICAgIHBhY2tldC5zaG93TmFtZVRhZ3MgPSB0cnVlO1xuICAgICAgICBhd2FpdCB0aGlzLmNvbm5lY3Rpb24uc2VuZERhdGFQYWNrZXQocGFja2V0KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc2VuZEFiaWxpdGllcyhwbGF5ZXI/OiBQbGF5ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgdGFyZ2V0ID0gcGxheWVyID8/IHRoaXMucGxheWVyO1xuXG4gICAgICAgIGNvbnN0IG1haW5MYXllciA9IG5ldyBBYmlsaXR5TGF5ZXIoKTtcbiAgICAgICAgbWFpbkxheWVyLmxheWVyVHlwZSA9IEFiaWxpdHlMYXllclR5cGUuQkFTRTtcbiAgICAgICAgbWFpbkxheWVyLmxheWVyRmxhZ3MgPSBuZXcgTWFwKFtcbiAgICAgICAgICAgIFtBYmlsaXR5TGF5ZXJGbGFnLkZMWV9TUEVFRCwgdHJ1ZV0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5XQUxLX1NQRUVELCB0cnVlXSxcbiAgICAgICAgICAgIFtBYmlsaXR5TGF5ZXJGbGFnLk1BWV9GTFksIHRhcmdldC5tZXRhZGF0YS5jYW5GbHldLFxuICAgICAgICAgICAgW0FiaWxpdHlMYXllckZsYWcuRkxZSU5HLCB0YXJnZXQuaXNGbHlpbmcoKV0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5OT19DTElQLCB0YXJnZXQuZ2FtZW1vZGUgPT09IEdhbWV0eXBlLlNQRUNUQVRPUl0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5PUEVSQVRPUl9DT01NQU5EUywgdGFyZ2V0LmlzT3AoKV0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5URUxFUE9SVCwgdGFyZ2V0LmlzT3AoKV0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5JTlZVTE5FUkFCTEUsIHRhcmdldC5nYW1lbW9kZSA9PT0gR2FtZXR5cGUuU1BFQ1RBVE9SXSxcbiAgICAgICAgICAgIFtBYmlsaXR5TGF5ZXJGbGFnLk1VVEVELCBmYWxzZV0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5XT1JMRF9CVUlMREVSLCBmYWxzZV0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5JTlNUQUJVSUxELCB0YXJnZXQuZ2FtZW1vZGUgPT09IEdhbWV0eXBlLlNQRUNUQVRPUl0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5MSUdIVE5JTkcsIGZhbHNlXSxcbiAgICAgICAgICAgIFtBYmlsaXR5TGF5ZXJGbGFnLkJVSUxELCB0YXJnZXQuZ2FtZW1vZGUgIT09IEdhbWV0eXBlLlNQRUNUQVRPUl0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5NSU5FLCB0YXJnZXQuZ2FtZW1vZGUgIT09IEdhbWV0eXBlLlNQRUNUQVRPUl0sXG4gICAgICAgICAgICBbQWJpbGl0eUxheWVyRmxhZy5ET09SU19BTkRfU1dJVENIRVMsIHRhcmdldC5nYW1lbW9kZSAhPT0gR2FtZXR5cGUuU1BFQ1RBVE9SXSxcbiAgICAgICAgICAgIFtBYmlsaXR5TGF5ZXJGbGFnLk9QRU5fQ09OVEFJTkVSUywgdGFyZ2V0LmdhbWVtb2RlICE9PSBHYW1ldHlwZS5TUEVDVEFUT1JdLFxuICAgICAgICAgICAgW0FiaWxpdHlMYXllckZsYWcuQVRUQUNLX1BMQVlFUlMsIHRhcmdldC5nYW1lbW9kZSAhPT0gR2FtZXR5cGUuU1BFQ1RBVE9SXSxcbiAgICAgICAgICAgIFtBYmlsaXR5TGF5ZXJGbGFnLkFUVEFDS19NT0JTLCB0YXJnZXQuZ2FtZW1vZGUgIT09IEdhbWV0eXBlLlNQRUNUQVRPUl1cbiAgICAgICAgXSk7XG4gICAgICAgIG1haW5MYXllci5mbHlTcGVlZCA9IDAuMDU7XG4gICAgICAgIG1haW5MYXllci53YWxrU3BlZWQgPSAwLjE7XG5cbiAgICAgICAgY29uc3QgcGFja2V0ID0gbmV3IFVwZGF0ZUFiaWxpdGllc1BhY2tldCgpO1xuICAgICAgICBwYWNrZXQuY29tbWFuZFBlcm1pc3Npb24gPSB0YXJnZXQuaXNPcCgpID8gUGVybWlzc2lvblR5cGUuT3BlcmF0b3IgOiBQZXJtaXNzaW9uVHlwZS5Ob3JtYWw7XG4gICAgICAgIHBhY2tldC5wbGF5ZXJQZXJtaXNzaW9uID0gdGFyZ2V0LmlzT3AoKSA/IFBsYXllclBlcm1pc3Npb25UeXBlLk9wZXJhdG9yIDogUGxheWVyUGVybWlzc2lvblR5cGUuTWVtYmVyO1xuICAgICAgICBwYWNrZXQudGFyZ2V0QWN0b3JVbmlxdWVJZCA9IHRhcmdldC5nZXRSdW50aW1lSWQoKTtcbiAgICAgICAgcGFja2V0LmFiaWxpdHlMYXllcnMgPSBbbWFpbkxheWVyXTtcbiAgICAgICAgYXdhaXQgdGhpcy5zZW5kKHBhY2tldCk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIG5lZWROZXdDaHVua3MoZm9yY2VSZXNlbmQgPSBmYWxzZSwgZGlzdD86IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBjdXJyZW50WENodW5rID0gQ29vcmRpbmF0ZVV0aWxzLmZyb21CbG9ja1RvQ2h1bmsodGhpcy5wbGF5ZXIuZ2V0WCgpKTtcbiAgICAgICAgY29uc3QgY3VycmVudFpDaHVuayA9IENvb3JkaW5hdGVVdGlscy5mcm9tQmxvY2tUb0NodW5rKHRoaXMucGxheWVyLmdldFooKSk7XG5cbiAgICAgICAgY29uc3Qgdmlld0Rpc3RhbmNlID0gdGhpcy5wbGF5ZXIudmlld0Rpc3RhbmNlIHx8IGRpc3QgfHwgMDtcblxuICAgICAgICBjb25zdCBjaHVua3NUb1NlbmRIZWFwID0gbmV3IEhlYXAoKGE6IG51bWJlcltdLCBiOiBudW1iZXJbXSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgZGlzdFhGaXJzdCA9IE1hdGguYWJzKGFbMF0hIC0gY3VycmVudFhDaHVuayk7XG4gICAgICAgICAgICBjb25zdCBkaXN0WFNlY29uZCA9IE1hdGguYWJzKGJbMF0hIC0gY3VycmVudFhDaHVuayk7XG5cbiAgICAgICAgICAgIGNvbnN0IGRpc3RaRmlyc3QgPSBNYXRoLmFicyhhWzFdISAtIGN1cnJlbnRaQ2h1bmspO1xuICAgICAgICAgICAgY29uc3QgZGlzdFpTZWNvbmQgPSBNYXRoLmFicyhiWzFdISAtIGN1cnJlbnRaQ2h1bmspO1xuXG4gICAgICAgICAgICByZXR1cm4gZGlzdFhGaXJzdCArIGRpc3RaRmlyc3QgPiBkaXN0WFNlY29uZCArIGRpc3RaU2Vjb25kID8gMSA6IC0xO1xuICAgICAgICB9KTtcblxuICAgICAgICBmb3IgKGxldCBzZW5kWENodW5rID0gLXZpZXdEaXN0YW5jZTsgc2VuZFhDaHVuayA8PSB2aWV3RGlzdGFuY2U7IHNlbmRYQ2h1bmsrKykge1xuICAgICAgICAgICAgZm9yIChsZXQgc2VuZFpDaHVuayA9IC12aWV3RGlzdGFuY2U7IHNlbmRaQ2h1bmsgPD0gdmlld0Rpc3RhbmNlOyBzZW5kWkNodW5rKyspIHtcbiAgICAgICAgICAgICAgICBpZiAoc2VuZFhDaHVuayAqIHNlbmRYQ2h1bmsgKyBzZW5kWkNodW5rICogc2VuZFpDaHVuayA+IHZpZXdEaXN0YW5jZSAqIHZpZXdEaXN0YW5jZSkgY29udGludWU7IC8vIGVhcmx5IGV4aXQgaWYgY2h1bmsgaXMgb3V0c2lkZSBvZiB2aWV3IGRpc3RhbmNlXG4gICAgICAgICAgICAgICAgY29uc3QgbmV3Q2h1bmsgPSBbY3VycmVudFhDaHVuayArIHNlbmRYQ2h1bmssIGN1cnJlbnRaQ2h1bmsgKyBzZW5kWkNodW5rXTtcbiAgICAgICAgICAgICAgICBjb25zdCBoYXNoID0gQ2h1bmsucGFja1haKG5ld0NodW5rWzBdISwgbmV3Q2h1bmtbMV0hKTtcblxuICAgICAgICAgICAgICAgIGlmIChmb3JjZVJlc2VuZCkge1xuICAgICAgICAgICAgICAgICAgICBjaHVua3NUb1NlbmRIZWFwLnB1c2gobmV3Q2h1bmspO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAoIXRoaXMubG9hZGVkQ2h1bmtzLmhhcyhoYXNoKSAmJiAhdGhpcy5sb2FkaW5nQ2h1bmtzLmhhcyhoYXNoKSkge1xuICAgICAgICAgICAgICAgICAgICBjaHVua3NUb1NlbmRIZWFwLnB1c2gobmV3Q2h1bmspO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHdoaWxlIChjaHVua3NUb1NlbmRIZWFwLnNpemUoKSA+IDApIHtcbiAgICAgICAgICAgIGNvbnN0IGNsb3Nlc3RDaHVuayA9IGNodW5rc1RvU2VuZEhlYXAucG9wKCkhO1xuICAgICAgICAgICAgY29uc3QgaGFzaCA9IENodW5rLnBhY2tYWihjbG9zZXN0Q2h1bmtbMF0hLCBjbG9zZXN0Q2h1bmtbMV0hKTtcbiAgICAgICAgICAgIGlmIChmb3JjZVJlc2VuZCkge1xuICAgICAgICAgICAgICAgIGlmICghdGhpcy5sb2FkZWRDaHVua3MuaGFzKGhhc2gpICYmICF0aGlzLmxvYWRpbmdDaHVua3MuaGFzKGhhc2gpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMubG9hZGluZ0NodW5rcy5hZGQoaGFzaCk7XG4gICAgICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMucmVxdWVzdENodW5rKGNsb3Nlc3RDaHVua1swXSEsIGNsb3Nlc3RDaHVua1sxXSEpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGxvYWRlZENodW5rID0gYXdhaXQgdGhpcy5wbGF5ZXIuZ2V0V29ybGQoKS5nZXRDaHVuayhjbG9zZXN0Q2h1bmtbMF0hLCBjbG9zZXN0Q2h1bmtbMV0hKTtcbiAgICAgICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5zZW5kQ2h1bmsobG9hZGVkQ2h1bmspO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhpcy5sb2FkaW5nQ2h1bmtzLmFkZChoYXNoKTtcbiAgICAgICAgICAgICAgICBhd2FpdCB0aGlzLnJlcXVlc3RDaHVuayhjbG9zZXN0Q2h1bmtbMF0hLCBjbG9zZXN0Q2h1bmtbMV0hKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGxldCB1bmxvYWRlZCA9IGZhbHNlO1xuXG4gICAgICAgIGZvciAoY29uc3QgaGFzaCBvZiB0aGlzLmxvYWRlZENodW5rcykge1xuICAgICAgICAgICAgY29uc3QgW3gsIHpdID0gQ2h1bmsudW5wYWNrWFooaGFzaCk7XG5cbiAgICAgICAgICAgIGlmIChNYXRoLmFicyh4ISAtIGN1cnJlbnRYQ2h1bmspID4gdmlld0Rpc3RhbmNlIHx8IE1hdGguYWJzKHohIC0gY3VycmVudFpDaHVuaykgPiB2aWV3RGlzdGFuY2UpIHtcbiAgICAgICAgICAgICAgICB1bmxvYWRlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgdGhpcy5sb2FkZWRDaHVua3MuZGVsZXRlKGhhc2gpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgZm9yIChjb25zdCBoYXNoIG9mIHRoaXMubG9hZGluZ0NodW5rcykge1xuICAgICAgICAgICAgY29uc3QgW3gsIHpdID0gQ2h1bmsudW5wYWNrWFooaGFzaCk7XG5cbiAgICAgICAgICAgIGlmIChNYXRoLmFicyh4ISAtIGN1cnJlbnRYQ2h1bmspID4gdmlld0Rpc3RhbmNlIHx8IE1hdGguYWJzKHohIC0gY3VycmVudFpDaHVuaykgPiB2aWV3RGlzdGFuY2UpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmxvYWRpbmdDaHVua3MuZGVsZXRlKGhhc2gpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCF1bmxvYWRlZCAmJiB0aGlzLmNodW5rU2VuZFF1ZXVlLmxlbmd0aCAhPT0gMCkge1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5zZW5kTmV0d29ya0NodW5rUHVibGlzaGVyKGRpc3QgPz8gdmlld0Rpc3RhbmNlLCBbXSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgcmVxdWVzdENodW5rKHg6IG51bWJlciwgejogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IGNodW5rID0gYXdhaXQgdGhpcy5wbGF5ZXIuZ2V0V29ybGQoKS5nZXRDaHVuayh4LCB6KTtcbiAgICAgICAgdGhpcy5jaHVua1NlbmRRdWV1ZS5wdXNoKGNodW5rKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDbGVhciB0aGUgY3VycmVudGx5IGxvYWRlZCBhbmQgbG9hZGluZyBjaHVua3MuXG4gICAgICpcbiAgICAgKiBAcmVtYXJrc1xuICAgICAqIFVzdWFsbHkgdXNlZCBmb3IgY2hhbmdpbmcgZGltZW5zaW9uLCB3b3JsZCwgZXRjLlxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBjbGVhckNodW5rcygpIHtcbiAgICAgICAgdGhpcy5sb2FkZWRDaHVua3MuY2xlYXIoKTtcbiAgICAgICAgdGhpcy5sb2FkaW5nQ2h1bmtzLmNsZWFyKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQFRPRE86IEltcGxlbWVudCB0aGlzLlxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBzZW5kSW52ZW50b3J5KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBwayA9IG5ldyBJbnZlbnRvcnlDb250ZW50UGFja2V0KCk7XG4gICAgICAgIHBrLml0ZW1zID0gdGhpcy5wbGF5ZXIuZ2V0SW52ZW50b3J5KCkuZ2V0SXRlbXModHJ1ZSk7XG4gICAgICAgIHBrLndpbmRvd0lkID0gV2luZG93SWRzLklOVkVOVE9SWTsgLy8gSW52ZW50b3J5IHdpbmRvd1xuICAgICAgICAvL2F3YWl0IHRoaXMuY29ubmVjdGlvbi5zZW5kRGF0YVBhY2tldChwayk7XG4gICAgICAgIC8vYXdhaXQgdGhpcy5zZW5kSGFuZEl0ZW0odGhpcy5wbGF5ZXIuZ2V0SW52ZW50b3J5KCkuZ2V0SXRlbUluSGFuZCgpKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc2VuZENyZWF0aXZlQ29udGVudHMoZW1wdHkgPSBmYWxzZSk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBwayA9IG5ldyBDcmVhdGl2ZUNvbnRlbnRQYWNrZXQoKTtcbiAgICAgICAgaWYgKGVtcHR5KSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLmNvbm5lY3Rpb24uc2VuZERhdGFQYWNrZXQocGspO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgZW50cmllcyA9IFtcbiAgICAgICAgICAgIC4uLnRoaXMucGxheWVyLmdldFNlcnZlcigpLmdldEJsb2NrTWFuYWdlcigpLmdldEJsb2NrcygpLFxuICAgICAgICAgICAgLi4udGhpcy5wbGF5ZXIuZ2V0U2VydmVyKCkuZ2V0SXRlbU1hbmFnZXIoKS5nZXRJdGVtcygpXG4gICAgICAgIF07XG5cbiAgICAgICAgLy8gU29ydCBiYXNlZCBvbiBQbW1QIEJlZHJvY2stZGF0YVxuICAgICAgICBDcmVhdGl2ZUl0ZW1zLmZvckVhY2goKGl0ZW06IGFueSkgPT4ge1xuICAgICAgICAgICAgcGsuaXRlbXMucHVzaChcbiAgICAgICAgICAgICAgICAuLi5lbnRyaWVzXG4gICAgICAgICAgICAgICAgICAgIC5maWx0ZXIoKGVudHJ5OiBhbnkpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBlbnRyeS5tZXRhID09PSAoaXRlbS5kYW1hZ2UgPz8gMCkgJiYgZW50cnkuZ2V0SWQoKSA9PT0gaXRlbS5pZDtcbiAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgICAgLm1hcChcbiAgICAgICAgICAgICAgICAgICAgICAgIChlbnRyeSkgPT5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXcgSXRlbSh7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkOiBlbnRyeS5nZXRJZCgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lOiBlbnRyeS5nZXROYW1lKCksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGE6IGVudHJ5Lm1ldGFcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgICApXG4gICAgICAgICAgICApO1xuICAgICAgICB9KTtcblxuICAgICAgICBhd2FpdCB0aGlzLmNvbm5lY3Rpb24uc2VuZERhdGFQYWNrZXQocGspO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIGl0ZW0gaW4gdGhlIHBsYXllciBoYW5kLlxuICAgICAqIEBwYXJhbSB7SXRlbX0gaXRlbSAtIFRoZSBlbnRpdHkuXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIHNlbmRIYW5kSXRlbShpdGVtOiBJdGVtKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IHBrID0gbmV3IE1vYkVxdWlwbWVudFBhY2tldCgpO1xuICAgICAgICBway5ydW50aW1lRW50aXR5SWQgPSB0aGlzLnBsYXllci5nZXRSdW50aW1lSWQoKTtcbiAgICAgICAgcGsuaXRlbSA9IGl0ZW07XG4gICAgICAgIHBrLmludmVudG9yeVNsb3QgPSB0aGlzLnBsYXllci5nZXRJbnZlbnRvcnkoKS5nZXRIYW5kU2xvdEluZGV4KCk7XG4gICAgICAgIHBrLmhvdGJhclNsb3QgPSB0aGlzLnBsYXllci5nZXRJbnZlbnRvcnkoKS5nZXRIYW5kU2xvdEluZGV4KCk7XG4gICAgICAgIHBrLndpbmRvd0lkID0gMDsgLy8gSW52ZW50b3J5IElEXG4gICAgICAgIGF3YWl0IHRoaXMuY29ubmVjdGlvbi5zZW5kRGF0YVBhY2tldChwayk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2VuZCB0aGUgY3VycmVudCB0aWNrIHRvIGEgY2xpZW50LlxuICAgICAqIEBwYXJhbSB7bnVtYmVyfSB0aWNrIC0gVGhlIHRpY2tcbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgc2VuZFRpbWUodGljazogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IHBrID0gbmV3IFNldFRpbWVQYWNrZXQoKTtcbiAgICAgICAgcGsudGltZSA9IHRpY2s7XG4gICAgICAgIGF3YWl0IHRoaXMuY29ubmVjdGlvbi5zZW5kRGF0YVBhY2tldChwayk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2VuZCBnYW1lbW9kZSB0byBhIGNsaWVudC5cbiAgICAgKiBAcGFyYW0ge0dhbWV0eXBlfSBnYW1lbW9kZSAtIHRoZSBudW1lcmljIGdhbWVtb2RlIElELlxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBzZW5kR2FtZW1vZGUoZ2FtZW1vZGU/OiBHYW1ldHlwZSk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBwYWNrZXQgPSBuZXcgU2V0UGxheWVyR2FtZXR5cGVQYWNrZXQoKTtcbiAgICAgICAgcGFja2V0LmdhbWV0eXBlID0gZ2FtZW1vZGUgPz8gdGhpcy5wbGF5ZXIuZ2FtZW1vZGU7XG4gICAgICAgIGF3YWl0IHRoaXMuY29ubmVjdGlvbi5zZW5kRGF0YVBhY2tldChwYWNrZXQpO1xuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyBzZW5kTmV0d29ya0NodW5rUHVibGlzaGVyKGRpc3RhbmNlOiBudW1iZXIsIHNhdmVkQ2h1bmtzOiBDaHVua0Nvb3JkW10pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgcGsgPSBuZXcgTmV0d29ya0NodW5rUHVibGlzaGVyVXBkYXRlUGFja2V0KCk7XG4gICAgICAgIHBrLnBvc2l0aW9uID0gQmxvY2tQb3NpdGlvbi5mcm9tVmVjdG9yMyh0aGlzLnBsYXllci5nZXRQb3NpdGlvbigpKTtcbiAgICAgICAgcGsucmFkaXVzID0gZGlzdGFuY2UgPDwgNDtcbiAgICAgICAgcGsuc2F2ZWRDaHVua3MgPSBzYXZlZENodW5rcztcbiAgICAgICAgYXdhaXQgdGhpcy5jb25uZWN0aW9uLnNlbmREYXRhUGFja2V0KHBrKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc2VuZEF2YWlsYWJsZUNvbW1hbmRzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBwbGF5ZXJFbnVtID0gbmV3IENvbW1hbmRFbnVtKCk7XG4gICAgICAgIHBsYXllckVudW0uc29mdCA9IHRydWU7XG4gICAgICAgIHBsYXllckVudW0ubmFtZSA9ICdQbGF5ZXInO1xuICAgICAgICBwbGF5ZXJFbnVtLnZhbHVlcyA9IHRoaXMucGxheWVyXG4gICAgICAgICAgICAuZ2V0U2VydmVyKClcbiAgICAgICAgICAgIC5nZXRTZXNzaW9uTWFuYWdlcigpXG4gICAgICAgICAgICAuZ2V0QWxsUGxheWVycygpXG4gICAgICAgICAgICAubWFwKChwbGF5ZXIpID0+IHBsYXllci5nZXROYW1lKCkpO1xuXG4gICAgICAgIGNvbnN0IHBrID0gbmV3IEF2YWlsYWJsZUNvbW1hbmRzUGFja2V0KCk7XG4gICAgICAgIHBrLnNvZnRFbnVtcyA9IFtwbGF5ZXJFbnVtXTtcbiAgICAgICAgdGhpcy5zZXJ2ZXJcbiAgICAgICAgICAgIC5nZXRDb21tYW5kTWFuYWdlcigpXG4gICAgICAgICAgICAuZ2V0Q29tbWFuZHNMaXN0KClcbiAgICAgICAgICAgIC5mb3JFYWNoKChjb21tYW5kKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgY29tbWFuZENsYXNzID0gQXJyYXkuZnJvbSh0aGlzLnNlcnZlci5nZXRDb21tYW5kTWFuYWdlcigpLmdldENvbW1hbmRzKCkudmFsdWVzKCkpLmZpbmQoXG4gICAgICAgICAgICAgICAgICAgIChjbWQpID0+IGNtZC5uYW1lID09PSBjb21tYW5kWzBdXG4gICAgICAgICAgICAgICAgKTtcblxuICAgICAgICAgICAgICAgIGlmICghY29tbWFuZENsYXNzKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMucGxheWVyXG4gICAgICAgICAgICAgICAgICAgICAgICAuZ2V0U2VydmVyKClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5nZXRMb2dnZXIoKVxuICAgICAgICAgICAgICAgICAgICAgICAgLndhcm4oYENhbid0IGZpbmQgY29ycmVzcG9uZGluZyBjb21tYW5kIGNsYXNzIGZvciBcIiR7Y29tbWFuZFswXX1cImApO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKCF0aGlzLnBsYXllci5nZXRTZXJ2ZXIoKS5nZXRQZXJtaXNzaW9uTWFuYWdlcigpLmNhbih0aGlzLnBsYXllcikuZXhlY3V0ZShjb21tYW5kQ2xhc3MucGVybWlzc2lvbikpXG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcblxuICAgICAgICAgICAgICAgIGNvbnN0IGNtZCA9IG5ldyBDb21tYW5kRGF0YSgpO1xuICAgICAgICAgICAgICAgIGNtZC5jb21tYW5kTmFtZSA9IGNvbW1hbmRbMF07XG4gICAgICAgICAgICAgICAgY21kLmNvbW1hbmREZXNjcmlwdGlvbiA9IGNvbW1hbmRDbGFzcy5kZXNjcmlwdGlvbjtcbiAgICAgICAgICAgICAgICBpZiAoY29tbWFuZENsYXNzLmFsaWFzZXMhLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgY21kQWxpYXNlcyA9IG5ldyBDb21tYW5kRW51bSgpO1xuICAgICAgICAgICAgICAgICAgICBjbWRBbGlhc2VzLm5hbWUgPSBgJHtjb21tYW5kWzBdfUFsaWFzZXNgO1xuICAgICAgICAgICAgICAgICAgICBjbWRBbGlhc2VzLnZhbHVlcyA9IGNvbW1hbmRDbGFzcy5hbGlhc2VzIS5jb25jYXQoY29tbWFuZFswXSk7XG4gICAgICAgICAgICAgICAgICAgIGNtZC5hbGlhc2VzID0gY21kQWxpYXNlcztcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBjb21tYW5kWzJdLmZvckVhY2goKGFyZywgaW5kZXgpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgcGFyYW1ldGVycyA9IGFyZ1xuICAgICAgICAgICAgICAgICAgICAgICAgLm1hcCgocGFyYW1ldGVyOiBDb21tYW5kQXJndW1lbnQgfCBudWxsKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFwYXJhbWV0ZXIgfHwgIShwYXJhbWV0ZXIgYXMgYW55KT8uZ2V0UGFyYW1ldGVycykgcmV0dXJuIFtdO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcGFyYW1ldGVycyA9IHBhcmFtZXRlci5nZXRQYXJhbWV0ZXJzKHRoaXMuc2VydmVyKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAocGFyYW1ldGVycykgcmV0dXJuIEFycmF5LmZyb20ocGFyYW1ldGVycy52YWx1ZXMoKSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAocGFyYW1ldGVyIGluc3RhbmNlb2YgQ29tbWFuZEFyZ3VtZW50RW50aXR5KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gW1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IENvbW1hbmRQYXJhbWV0ZXIoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtTmFtZTogJ3RhcmdldCcsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYW1UeXBlOiBDb21tYW5kUGFyYW1ldGVyVHlwZS5UYXJnZXRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHBhcmFtZXRlciBpbnN0YW5jZW9mIENvbW1hbmRBcmd1bWVudEdhbWVtb2RlKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gW1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IENvbW1hbmRQYXJhbWV0ZXIoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtTmFtZTogJ2dhbWVtb2RlJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbVR5cGU6IENvbW1hbmRQYXJhbWV0ZXJUeXBlLlN0cmluZ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAocGFyYW1ldGVyLmNvbnN0cnVjdG9yLm5hbWUgPT09ICdTdHJpbmdBcmd1bWVudFR5cGUnKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gW1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IENvbW1hbmRQYXJhbWV0ZXIoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtTmFtZTogJ3ZhbHVlJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbVR5cGU6IENvbW1hbmRQYXJhbWV0ZXJUeXBlLlN0cmluZ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAocGFyYW1ldGVyLmNvbnN0cnVjdG9yLm5hbWUgPT09ICdJbnRlZ2VyQXJndW1lbnRUeXBlJylcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIFtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ldyBDb21tYW5kUGFyYW1ldGVyKHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbU5hbWU6ICdudW1iZXInLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtVHlwZTogQ29tbWFuZFBhcmFtZXRlclR5cGUuSW50XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBdO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5zZXJ2ZXIuZ2V0TG9nZ2VyKCkud2FybihgSW52YWxpZCBwYXJhbWV0ZXIgJHtwYXJhbWV0ZXIuY29uc3RydWN0b3IubmFtZX1gKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gW1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXcgQ29tbWFuZFBhcmFtZXRlcih7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbU5hbWU6ICd2YWx1ZScsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbVR5cGU6IENvbW1hbmRQYXJhbWV0ZXJUeXBlLlN0cmluZ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIF07XG4gICAgICAgICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgICAgICAgLmZsYXQoKTtcbiAgICAgICAgICAgICAgICAgICAgY21kLm92ZXJsb2Fkc1tpbmRleF0gPSBwYXJhbWV0ZXJzO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIHBrLmNvbW1hbmREYXRhLnB1c2goY21kKTtcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgIGF3YWl0IHRoaXMuZ2V0Q29ubmVjdGlvbigpLnNlbmREYXRhUGFja2V0KHBrKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdGhlIGNsaWVudCdzIG1heGltdW0gdmlldyBkaXN0YW5jZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBkaXN0YW5jZSAtIFRoZSB2aWV3IGRpc3RhbmNlXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIHNldFZpZXdEaXN0YW5jZShkaXN0YW5jZTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IHBhY2tldCA9IG5ldyBDaHVua1JhZGl1c1VwZGF0ZWRQYWNrZXQoKTtcbiAgICAgICAgcGFja2V0LnJhZGl1cyA9IHRoaXMucGxheWVyLnZpZXdEaXN0YW5jZSA9IGRpc3RhbmNlO1xuICAgICAgICBhd2FpdCB0aGlzLmNvbm5lY3Rpb24uc2VuZERhdGFQYWNrZXQocGFja2V0KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc2VuZEF0dHJpYnV0ZXMoYXR0cmlidXRlcz86IEF0dHJpYnV0ZXMpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgdmFsdWUgPSBhdHRyaWJ1dGVzPy5nZXRBdHRyaWJ1dGVzKCkgPz8gdGhpcy5wbGF5ZXIuYXR0cmlidXRlcy5nZXRBdHRyaWJ1dGVzKCk7XG5cbiAgICAgICAgY29uc3QgcGFja2V0ID0gbmV3IFVwZGF0ZUF0dHJpYnV0ZXNQYWNrZXQoKTtcbiAgICAgICAgcGFja2V0LnJ1bnRpbWVFbnRpdHlJZCA9IHRoaXMucGxheWVyLmdldFJ1bnRpbWVJZCgpO1xuICAgICAgICBwYWNrZXQuYXR0cmlidXRlcyA9IHZhbHVlLmxlbmd0aCA+IDAgPyB2YWx1ZSA6IHRoaXMucGxheWVyLmF0dHJpYnV0ZXMuZ2V0RGVmYXVsdHMoKTtcbiAgICAgICAgcGFja2V0LnRpY2sgPSBCaWdJbnQodGhpcy5wbGF5ZXIuZ2V0U2VydmVyKCkuZ2V0VGljaygpKTtcbiAgICAgICAgYXdhaXQgdGhpcy5jb25uZWN0aW9uLnNlbmREYXRhUGFja2V0KHBhY2tldCk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHNlbmRNZXRhZGF0YShtZXRhZGF0YT86IE1ldGFkYXRhKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IHBhY2tldCA9IG5ldyBTZXRBY3RvckRhdGFQYWNrZXQoKTtcbiAgICAgICAgcGFja2V0LnJ1bnRpbWVFbnRpdHlJZCA9IHRoaXMucGxheWVyLmdldFJ1bnRpbWVJZCgpO1xuICAgICAgICBwYWNrZXQubWV0YWRhdGEgPSBtZXRhZGF0YSA/PyB0aGlzLnBsYXllci5tZXRhZGF0YTtcbiAgICAgICAgcGFja2V0LnRpY2sgPSBCaWdJbnQodGhpcy5wbGF5ZXIuZ2V0U2VydmVyKCkuZ2V0VGljaygpKTtcbiAgICAgICAgYXdhaXQgdGhpcy5jb25uZWN0aW9uLnNlbmREYXRhUGFja2V0KHBhY2tldCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2VuZCBhIGNoYXQgbWVzc2FnZSB0byB0aGUgY2xpZW50LlxuICAgICAqIEBwYXJhbSB7b2JqZWN0fSBvcHRpb25zIC0gVGhlIG9wdGlvbnMgZm9yIHRoZSBtZXNzYWdlLlxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBvcHRpb25zLm1lc3NhZ2UgLSBUaGUgbWVzc2FnZSB0byBzZW5kLlxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5zb3VyY2VOYW1lPScnXSAtIFRoZSBzb3VyY2Ugb2YgdGhlIG1lc3NhZ2UuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IFtvcHRpb25zLnh1aWQ9JyddIC0gVGhlIFhVSUQgb2YgdGhlIHBsYXllci5cbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMucGxhdGZvcm1DaGF0SWQ9JyddIC0gVGhlIHBsYXRmb3JtIGNoYXQgSUQuXG4gICAgICogQHBhcmFtIHtzdHJpbmdbXX0gW29wdGlvbnMucGFyYW1ldGVycz1bXV0gLSBUaGUgcGFyYW1ldGVycyBmb3IgdGhlIG1lc3NhZ2UuXG4gICAgICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy5uZWVkc1RyYW5zbGF0aW9uPWZhbHNlXSAtIFdoZXRoZXIgdGhlIG1lc3NhZ2UgbmVlZHMgdHJhbnNsYXRpb24uXG4gICAgICogQHBhcmFtIHtUZXh0VHlwZX0gW29wdGlvbnMudHlwZT1UZXh0VHlwZS5SYXddIC0gVGhlIHR5cGUgb2YgdGh